snow-flow
Version:
Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A
802 lines ⢠42.1 kB
JavaScript
;
/**
* ServiceNow Platform Development MCP Server
* Handles core platform development artifacts with full dynamic discovery
* NO HARDCODED VALUES - All tables, fields, and configurations discovered dynamically
*/
Object.defineProperty(exports, "__esModule", { value: true });
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const servicenow_client_js_1 = require("../utils/servicenow-client.js");
const mcp_auth_middleware_js_1 = require("../utils/mcp-auth-middleware.js");
const mcp_config_manager_js_1 = require("../utils/mcp-config-manager.js");
const logger_js_1 = require("../utils/logger.js");
class ServiceNowPlatformDevelopmentMCP {
constructor() {
this.tableCache = new Map();
this.server = new index_js_1.Server({
name: 'servicenow-platform-development',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.client = new servicenow_client_js_1.ServiceNowClient();
this.logger = new logger_js_1.Logger('ServiceNowPlatformDevelopmentMCP');
this.config = mcp_config_manager_js_1.mcpConfig.getConfig();
this.setupHandlers();
}
setupHandlers() {
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
tools: [
{
name: 'snow_create_ui_page',
description: 'Creates UI pages with HTML, JavaScript, and CSS. Supports server-side processing scripts and client-side interactions.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'UI Page name' },
title: { type: 'string', description: 'Page title' },
html: { type: 'string', description: 'HTML content' },
processingScript: { type: 'string', description: 'Server-side processing script' },
clientScript: { type: 'string', description: 'Client-side script' },
css: { type: 'string', description: 'CSS styles' },
category: { type: 'string', description: 'Page category' }
},
required: ['name', 'title', 'html']
}
},
{
name: 'snow_create_script_include',
description: 'Creates reusable Script Includes for server-side logic. Supports client-callable scripts and API exposure.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Script Include name' },
script: { type: 'string', description: 'JavaScript code' },
description: { type: 'string', description: 'Description of functionality' },
clientCallable: { type: 'boolean', description: 'Can be called from client' },
apiName: { type: 'string', description: 'API name for external calls' }
},
required: ['name', 'script']
}
},
{
name: 'snow_create_business_rule',
description: 'Creates business rules for automated data processing. Configurable timing (before/after/async) and conditional execution.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Business Rule name' },
tableName: { type: 'string', description: 'Target table name or sys_id' },
script: { type: 'string', description: 'JavaScript code' },
when: { type: 'string', description: 'When to execute: before, after, async, display' },
condition: { type: 'string', description: 'Condition script' },
description: { type: 'string', description: 'Rule description' }
},
required: ['name', 'tableName', 'script', 'when']
}
},
{
name: 'snow_create_client_script',
description: 'Creates client-side scripts for form interactions. Supports onLoad, onChange, onSubmit, and onCellEdit events.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Client Script name' },
tableName: { type: 'string', description: 'Target table name or sys_id' },
script: { type: 'string', description: 'JavaScript code' },
type: { type: 'string', description: 'Script type: onLoad, onChange, onSubmit, onCellEdit' },
fieldName: { type: 'string', description: 'Field name for onChange scripts' },
condition: { type: 'string', description: 'Condition script' },
description: { type: 'string', description: 'Script description' }
},
required: ['name', 'tableName', 'script', 'type']
}
},
{
name: 'snow_create_ui_policy',
description: 'Creates UI policies to control field behavior and visibility. Supports conditional logic and reversible actions.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'UI Policy name' },
tableName: { type: 'string', description: 'Target table name or sys_id' },
condition: { type: 'string', description: 'Condition script' },
description: { type: 'string', description: 'Policy description' },
runScripts: { type: 'boolean', description: 'Run scripts when policy applies' },
reverseWhenFalse: { type: 'boolean', description: 'Reverse actions when condition is false' }
},
required: ['name', 'tableName', 'condition']
}
},
{
name: 'snow_create_ui_action',
description: 'Creates custom buttons and menu items for forms and lists. Includes conditional visibility and action scripts.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'UI Action name' },
tableName: { type: 'string', description: 'Target table name or sys_id' },
script: { type: 'string', description: 'JavaScript code' },
condition: { type: 'string', description: 'Condition script' },
actionName: { type: 'string', description: 'Action name for forms' },
formButton: { type: 'boolean', description: 'Show as form button' },
listButton: { type: 'boolean', description: 'Show as list button' },
description: { type: 'string', description: 'Action description' }
},
required: ['name', 'tableName', 'script']
}
},
{
name: 'snow_discover_platform_tables',
description: 'Discovers platform development tables categorized by type (UI, script, policy, security, system).',
inputSchema: {
type: 'object',
properties: {
category: { type: 'string', description: 'Filter by category: ui, script, policy, action, all' }
}
}
},
{
name: 'snow_discover_table_fields',
description: 'Retrieves complete field information for any ServiceNow table including types, labels, and constraints.',
inputSchema: {
type: 'object',
properties: {
tableName: { type: 'string', description: 'Table name to discover fields for' }
},
required: ['tableName']
}
},
{
name: 'snow_table_schema_discovery',
description: 'Performs comprehensive table schema analysis including structure, relationships, indexes, and inheritance hierarchy.',
inputSchema: {
type: 'object',
properties: {
tableName: { type: 'string', description: 'Table name to analyze' },
includeRelated: { type: 'boolean', description: 'Include related table information' },
includeIndexes: { type: 'boolean', description: 'Include index information' },
includeExtensions: { type: 'boolean', description: 'Include table extensions/hierarchy' },
maxDepth: { type: 'number', description: 'Max depth for relationship discovery (default: 2)' }
},
required: ['tableName']
}
}
]
}));
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
// Ensure authentication
const authResult = await mcp_auth_middleware_js_1.mcpAuth.ensureAuthenticated();
if (!authResult.success) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, authResult.error || 'Authentication required');
}
switch (name) {
case 'snow_create_ui_page':
return await this.createUIPage(args);
case 'snow_create_script_include':
return await this.createScriptInclude(args);
case 'snow_create_business_rule':
return await this.createBusinessRule(args);
case 'snow_create_client_script':
return await this.createClientScript(args);
case 'snow_create_ui_policy':
return await this.createUIPolicy(args);
case 'snow_create_ui_action':
return await this.createUIAction(args);
case 'snow_discover_platform_tables':
return await this.discoverPlatformTables(args);
case 'snow_discover_table_fields':
return await this.discoverTableFields(args);
case 'snow_table_schema_discovery':
return await this.discoverTableSchema(args);
default:
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
this.logger.error(`Error in ${request.params.name}:`, error);
throw error;
}
});
}
/**
* Dynamically discover all platform development tables
*/
async discoverPlatformTables(args) {
try {
this.logger.info('Discovering platform development tables...');
// Define table categories based on actual ServiceNow schema
const tableQueries = [
{ category: 'ui', query: 'nameSTARTSWITHsys_ui^ORnameSTARTSWITHsp_' },
{ category: 'script', query: 'nameSTARTSWITHsys_script^ORnameSTARTSWITHsys_processor' },
{ category: 'policy', query: 'nameSTARTSWITHsys_ui_policy^ORnameSTARTSWITHsys_ui_action' },
{ category: 'security', query: 'nameSTARTSWITHsys_security^ORnameSTARTSWITHsys_user' },
{ category: 'system', query: 'nameSTARTSWITHsys_dictionary^ORnameSTARTSWITHsys_choice' }
];
const category = args?.category || 'all';
const discoveredTables = [];
for (const tableQuery of tableQueries) {
if (category === 'all' || category === tableQuery.category) {
const tablesResponse = await this.client.searchRecords('sys_db_object', tableQuery.query, 50);
if (tablesResponse.success && tablesResponse.data) {
discoveredTables.push({
category: tableQuery.category,
tables: tablesResponse.data.result.map((table) => ({
name: table.name,
label: table.label,
super_class: table.super_class,
is_extendable: table.is_extendable,
sys_id: table.sys_id
}))
});
}
}
}
return {
content: [{
type: 'text',
text: `š Discovered Platform Development Tables:\n\n${discoveredTables.map(cat => `**${cat.category.toUpperCase()} Tables:**\n${cat.tables.map(table => `- ${table.name} (${table.label})`).join('\n')}`).join('\n\n')}\n\n⨠All tables discovered dynamically from ServiceNow schema - no hardcoded values!`
}]
};
}
catch (error) {
this.logger.error('Failed to discover platform tables:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover tables: ${error}`);
}
}
/**
* Dynamically discover table fields
*/
async discoverTableFields(args) {
try {
const tableName = args.tableName;
this.logger.info(`Discovering fields for table: ${tableName}`);
// First, resolve table name to sys_id if needed
const tableInfo = await this.getTableInfo(tableName);
if (!tableInfo) {
throw new Error(`Table not found: ${tableName}`);
}
// Get all fields for this table
const fieldsResponse = await this.client.searchRecords('sys_dictionary', `nameSTARTSWITH${tableInfo.name}^element!=NULL`, 100);
if (!fieldsResponse.success || !fieldsResponse.data) {
throw new Error(`Failed to get fields for table: ${tableName}`);
}
const fields = fieldsResponse.data.result.map((field) => ({
name: field.element,
type: field.internal_type,
label: field.column_label,
mandatory: field.mandatory === 'true',
display: field.display === 'true',
max_length: field.max_length,
reference: field.reference,
choice: field.choice
}));
// Cache the table info
this.tableCache.set(tableName, {
name: tableInfo.name,
label: tableInfo.label,
fields: fields
});
return {
content: [{
type: 'text',
text: `š Fields for ${tableInfo.label} (${tableInfo.name}):\n\n${fields.map((field) => `- **${field.name}** (${field.label})\n Type: ${field.type}${field.mandatory ? ' *Required*' : ''}${field.reference ? ` -> ${field.reference}` : ''}`).join('\n')}\n\nš Total fields: ${fields.length}\n⨠All fields discovered dynamically from ServiceNow schema!`
}]
};
}
catch (error) {
this.logger.error('Failed to discover table fields:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover fields: ${error}`);
}
}
/**
* Get table information dynamically
*/
async getTableInfo(tableName) {
try {
this.logger.debug(`Looking up table info for: ${tableName}`);
// First, check if this is a known standard table that may not appear in sys_db_object
const standardTables = {
'incident': { label: 'Incident' },
'problem': { label: 'Problem' },
'change_request': { label: 'Change Request' },
'sc_request': { label: 'Request' },
'sc_req_item': { label: 'Requested Item' },
'sc_task': { label: 'Catalog Task' },
'task': { label: 'Task' }
};
if (standardTables[tableName]) {
this.logger.debug(`Using known standard table: ${tableName}`);
return {
name: tableName,
label: standardTables[tableName].label,
sys_id: `standard_table_${tableName}` // Placeholder sys_id for standard tables
};
}
// Try direct lookup first
const tableResponse = await this.client.searchRecords('sys_db_object', `name=${tableName}`, 1);
if (tableResponse.success && tableResponse.data?.result?.length > 0) {
const table = tableResponse.data.result[0];
return {
name: table.name,
label: table.label,
sys_id: table.sys_id
};
}
// Log the actual response for debugging
this.logger.debug(`Table lookup response for ${tableName}: ${JSON.stringify(tableResponse)}`);
// Try by sys_id
const tableByIdResponse = await this.client.searchRecords('sys_db_object', `sys_id=${tableName}`, 1);
if (tableByIdResponse.success && tableByIdResponse.data?.result?.length > 0) {
const table = tableByIdResponse.data.result[0];
return {
name: table.name,
label: table.label,
sys_id: table.sys_id
};
}
// Try partial match
const tableByPartialResponse = await this.client.searchRecords('sys_db_object', `nameCONTAINS${tableName}^ORlabelCONTAINS${tableName}`, 5);
if (tableByPartialResponse.success && tableByPartialResponse.data?.result?.length > 0) {
const table = tableByPartialResponse.data.result[0];
return {
name: table.name,
label: table.label,
sys_id: table.sys_id
};
}
return null;
}
catch (error) {
this.logger.error(`Failed to get table info for ${tableName}:`, error);
return null;
}
}
/**
* Create UI Page with dynamic field discovery
*/
async createUIPage(args) {
try {
this.logger.info('Creating UI Page...');
// Get UI Page table structure dynamically
const uiPageFields = await this.discoverRequiredFields('sys_ui_page');
const uiPageData = {
name: args.name,
title: args.title,
html: args.html,
processing_script: args.processingScript || '',
client_script: args.clientScript || '',
css: args.css || '',
category: args.category || 'general'
};
// Ensure we have Update Set
const updateSetResult = await this.client.ensureUpdateSet();
const response = await this.client.createRecord('sys_ui_page', uiPageData);
if (!response.success) {
throw new Error(`Failed to create UI Page: ${response.error}`);
}
return {
content: [{
type: 'text',
text: `ā
UI Page created successfully!\n\nš **${args.title}** (${args.name})\nš sys_id: ${response.data.sys_id}\n\nš View in ServiceNow: Open UI Page editor\n\n⨠Created with dynamic field discovery - no hardcoded values!`
}]
};
}
catch (error) {
this.logger.error('Failed to create UI Page:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create UI Page: ${error}`);
}
}
/**
* Create Script Include with dynamic discovery
*/
async createScriptInclude(args) {
try {
this.logger.info('Creating Script Include...');
const scriptIncludeData = {
name: args.name,
script: args.script,
description: args.description || '',
client_callable: args.clientCallable || false,
api_name: args.apiName || args.name
};
const updateSetResult = await this.client.ensureUpdateSet();
const response = await this.client.createRecord('sys_script_include', scriptIncludeData);
if (!response.success) {
throw new Error(`Failed to create Script Include: ${response.error}`);
}
return {
content: [{
type: 'text',
text: `ā
Script Include created successfully!\n\nš **${args.name}**\nš sys_id: ${response.data.sys_id}\nš§ Client Callable: ${args.clientCallable ? 'Yes' : 'No'}\n\nš Description: ${args.description || 'No description provided'}\n\n⨠Created with dynamic field discovery!`
}]
};
}
catch (error) {
this.logger.error('Failed to create Script Include:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create Script Include: ${error}`);
}
}
/**
* Create Business Rule with dynamic table discovery
*/
async createBusinessRule(args) {
try {
this.logger.info('Creating Business Rule...');
// Resolve table name dynamically
const tableInfo = await this.getTableInfo(args.tableName);
if (!tableInfo) {
throw new Error(`Table not found: ${args.tableName}`);
}
const businessRuleData = {
name: args.name,
table: tableInfo.name,
script: args.script,
when: args.when,
condition: args.condition || '',
description: args.description || ''
};
const updateSetResult = await this.client.ensureUpdateSet();
const response = await this.client.createRecord('sys_script', businessRuleData);
if (!response.success) {
throw new Error(`Failed to create Business Rule: ${response.error}`);
}
return {
content: [{
type: 'text',
text: `ā
Business Rule created successfully!\n\nš **${args.name}**\nš sys_id: ${response.data.sys_id}\nš Table: ${tableInfo.label} (${tableInfo.name})\nā° When: ${args.when}\n\nš Description: ${args.description || 'No description provided'}\n\n⨠Created with dynamic table discovery!`
}]
};
}
catch (error) {
this.logger.error('Failed to create Business Rule:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create Business Rule: ${error}`);
}
}
/**
* Create Client Script with dynamic discovery
*/
async createClientScript(args) {
try {
this.logger.info('Creating Client Script...');
const tableInfo = await this.getTableInfo(args.tableName);
if (!tableInfo) {
throw new Error(`Table not found: ${args.tableName}`);
}
const clientScriptData = {
name: args.name,
table: tableInfo.name,
script: args.script,
type: args.type,
field: args.fieldName || '',
condition: args.condition || '',
description: args.description || ''
};
const updateSetResult = await this.client.ensureUpdateSet();
const response = await this.client.createRecord('sys_script_client', clientScriptData);
if (!response.success) {
throw new Error(`Failed to create Client Script: ${response.error}`);
}
return {
content: [{
type: 'text',
text: `ā
Client Script created successfully!\n\nš **${args.name}**\nš sys_id: ${response.data.sys_id}\nš Table: ${tableInfo.label} (${tableInfo.name})\nš§ Type: ${args.type}\n${args.fieldName ? `š·ļø Field: ${args.fieldName}\n` : ''}\nš Description: ${args.description || 'No description provided'}\n\n⨠Created with dynamic table discovery!`
}]
};
}
catch (error) {
this.logger.error('Failed to create Client Script:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create Client Script: ${error}`);
}
}
/**
* Create UI Policy with dynamic discovery
*/
async createUIPolicy(args) {
try {
this.logger.info('Creating UI Policy...');
const tableInfo = await this.getTableInfo(args.tableName);
if (!tableInfo) {
throw new Error(`Table not found: ${args.tableName}`);
}
const uiPolicyData = {
name: args.name,
table: tableInfo.name,
conditions: args.condition,
description: args.description || '',
run_scripts: args.runScripts || false,
reverse_if_false: args.reverseWhenFalse || false
};
const updateSetResult = await this.client.ensureUpdateSet();
const response = await this.client.createRecord('sys_ui_policy', uiPolicyData);
if (!response.success) {
throw new Error(`Failed to create UI Policy: ${response.error}`);
}
return {
content: [{
type: 'text',
text: `ā
UI Policy created successfully!\n\nš **${args.name}**\nš sys_id: ${response.data.sys_id}\nš Table: ${tableInfo.label} (${tableInfo.name})\nš§ Run Scripts: ${args.runScripts ? 'Yes' : 'No'}\n\nš Description: ${args.description || 'No description provided'}\n\n⨠Created with dynamic table discovery!`
}]
};
}
catch (error) {
this.logger.error('Failed to create UI Policy:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create UI Policy: ${error}`);
}
}
/**
* Create UI Action with dynamic discovery
*/
async createUIAction(args) {
try {
this.logger.info('Creating UI Action...');
const tableInfo = await this.getTableInfo(args.tableName);
if (!tableInfo) {
throw new Error(`Table not found: ${args.tableName}`);
}
const uiActionData = {
name: args.name,
table: tableInfo.name,
script: args.script,
condition: args.condition || '',
action_name: args.actionName || args.name,
form_button: args.formButton || false,
list_button: args.listButton || false,
description: args.description || ''
};
const updateSetResult = await this.client.ensureUpdateSet();
const response = await this.client.createRecord('sys_ui_action', uiActionData);
if (!response.success) {
throw new Error(`Failed to create UI Action: ${response.error}`);
}
return {
content: [{
type: 'text',
text: `ā
UI Action created successfully!\n\nšÆ **${args.name}**\nš sys_id: ${response.data.sys_id}\nš Table: ${tableInfo.label} (${tableInfo.name})\nš Form Button: ${args.formButton ? 'Yes' : 'No'}\nš List Button: ${args.listButton ? 'Yes' : 'No'}\n\nš Description: ${args.description || 'No description provided'}\n\n⨠Created with dynamic table discovery!`
}]
};
}
catch (error) {
this.logger.error('Failed to create UI Action:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to create UI Action: ${error}`);
}
}
/**
* Discover required fields for a table dynamically
*/
async discoverRequiredFields(tableName) {
try {
const fieldsResponse = await this.client.searchRecords('sys_dictionary', `nameSTARTSWITH${tableName}^element!=NULL^mandatory=true`, 50);
if (fieldsResponse.success && fieldsResponse.data) {
return fieldsResponse.data.result.map((field) => field.element);
}
return [];
}
catch (error) {
this.logger.error(`Failed to discover required fields for ${tableName}:`, error);
return [];
}
}
/**
* Comprehensive table schema discovery
*/
async discoverTableSchema(args) {
try {
const { tableName, includeRelated = true, includeIndexes = true, includeExtensions = true, maxDepth = 2 } = args;
this.logger.info(`Discovering comprehensive schema for table: ${tableName}`);
// Validate authentication
if (!this.client) {
throw new Error('ServiceNow client not initialized');
}
// Get table information
const tableInfo = await this.getTableInfo(tableName);
if (!tableInfo) {
throw new Error(`Table not found: ${tableName}. Searched in sys_db_object table.`);
}
this.logger.debug(`Found table info: ${JSON.stringify(tableInfo)}`);
// Get detailed table metadata
this.logger.debug(`Attempting to fetch table details for sys_id: ${tableInfo.sys_id}`);
// Check if this is a standard table with placeholder sys_id
const isStandardTable = tableInfo.sys_id.startsWith('standard_table_');
let tableDetailsResponse = { success: false };
if (!isStandardTable) {
tableDetailsResponse = await this.client.getRecord('sys_db_object', tableInfo.sys_id);
}
// Declare the variable once with proper type
let tableDetails;
if (!tableDetailsResponse.success || isStandardTable) {
const errorMessage = tableDetailsResponse.error ||
JSON.stringify(tableDetailsResponse) ||
'Unknown error occurred while fetching table details';
this.logger.error(`Table details fetch failed for ${tableInfo.sys_id}:`, tableDetailsResponse);
// Fallback: try using basic table info if detailed fetch fails
this.logger.warn(`Falling back to basic table info for ${tableInfo.name}`);
tableDetails = {
name: tableInfo.name,
label: tableInfo.label,
sys_id: tableInfo.sys_id,
is_extendable: 'unknown',
access: 'unknown',
sys_created_on: 'unknown',
sys_updated_on: 'unknown',
row_count: 'unknown',
super_class: null,
extension_model: 'unknown',
sys_scope: null
};
}
else {
tableDetails = tableDetailsResponse.data;
}
// Get all fields with detailed information
const fieldsResponse = await this.client.searchRecords('sys_dictionary', `name=${tableInfo.name}^element!=NULL`, 200);
if (!fieldsResponse.success || !fieldsResponse.data) {
const errorMessage = fieldsResponse.error ||
'No data returned from fields query' ||
JSON.stringify(fieldsResponse);
this.logger.error(`Fields fetch failed for ${tableInfo.name}:`, fieldsResponse);
throw new Error(`Failed to get fields for table ${tableName}: ${errorMessage}`);
}
const fields = fieldsResponse.data.result.map((field) => ({
name: field.element,
label: field.column_label,
type: field.internal_type,
dataType: field.internal_type,
maxLength: field.max_length,
mandatory: field.mandatory === 'true',
readOnly: field.read_only === 'true',
display: field.display === 'true',
active: field.active === 'true',
array: field.array === 'true',
reference: field.reference,
referenceQual: field.reference_qual,
defaultValue: field.default_value,
choice: field.choice,
calculated: field.virtual === 'true',
attributes: field.attributes,
comments: field.comments
}));
// Analyze relationships
const relationships = fields
.filter((field) => field.reference)
.map((field) => ({
field: field.name,
targetTable: field.reference,
label: field.label,
referenceQual: field.referenceQual
}));
// Get table hierarchy if requested
let hierarchy = null;
if (includeExtensions) {
hierarchy = {
extends: tableDetails.super_class?.display_value || null,
extendsTable: tableDetails.super_class?.value || null,
isExtendable: tableDetails.is_extendable === 'true',
extensionModel: tableDetails.extension_model
};
// Find tables that extend this one
const childTablesResponse = await this.client.searchRecords('sys_db_object', `super_class=${tableInfo.sys_id}`, 50);
if (childTablesResponse.success && childTablesResponse.data) {
hierarchy.extendedBy = childTablesResponse.data.result.map((child) => ({
name: child.name,
label: child.label,
sys_id: child.sys_id
}));
}
}
// Get indexes if requested
let indexes = [];
if (includeIndexes) {
const indexResponse = await this.client.searchRecords('sys_db_index', `table=${tableInfo.sys_id}`, 50);
if (indexResponse.success && indexResponse.data) {
indexes = indexResponse.data.result.map((index) => ({
name: index.name,
unique: index.unique === 'true',
clustered: index.clustered === 'true',
fields: index.fields
}));
}
}
// Get related tables if requested
const relatedTables = [];
if (includeRelated && relationships.length > 0) {
const uniqueRelatedTables = [...new Set(relationships.map((rel) => rel.targetTable))];
for (const relTable of uniqueRelatedTables.slice(0, 10)) { // Limit to prevent too many queries
const relTableInfo = await this.getTableInfo(String(relTable));
if (relTableInfo) {
relatedTables.push({
name: relTableInfo.name,
label: relTableInfo.label,
referencedBy: relationships
.filter((rel) => rel.targetTable === relTable)
.map((rel) => rel.field)
});
}
}
}
// Compile comprehensive schema information
const schema = {
table: {
name: tableInfo.name,
label: tableInfo.label,
sys_id: tableInfo.sys_id,
isExtendable: tableDetails.is_extendable === 'true',
isSystemTable: tableDetails.sys_scope?.display_value === 'global',
created: tableDetails.sys_created_on,
updated: tableDetails.sys_updated_on,
recordCount: tableDetails.row_count || 'Unknown',
accessControls: tableDetails.access || 'Not specified'
},
hierarchy,
fields: {
total: fields.length,
mandatory: fields.filter((f) => f.mandatory).length,
references: fields.filter((f) => f.reference).length,
calculated: fields.filter((f) => f.calculated).length,
list: fields
},
relationships: {
total: relationships.length,
list: relationships,
relatedTables
},
indexes: {
total: indexes.length,
list: indexes
}
};
return {
content: [{
type: 'text',
text: `š **Comprehensive Schema Discovery for ${tableInfo.label} (${tableInfo.name})**\n\n` +
`š **Table Overview:**\n` +
`- Label: ${schema.table.label}\n` +
`- Name: ${schema.table.name}\n` +
`- System ID: ${schema.table.sys_id}\n` +
`- Extendable: ${schema.table.isExtendable ? 'Yes' : 'No'}\n` +
`- System Table: ${schema.table.isSystemTable ? 'Yes' : 'No'}\n` +
`- Record Count: ${schema.table.recordCount}\n\n` +
(hierarchy ? `š **Table Hierarchy:**\n` +
`- Extends: ${hierarchy.extends || 'None'}\n` +
`- Extended By: ${hierarchy.extendedBy?.length || 0} tables\n` +
(hierarchy.extendedBy?.length > 0 ?
hierarchy.extendedBy.map((t) => ` - ${t.label} (${t.name})`).join('\n') + '\n' : '') +
'\n' : '') +
`š **Fields Summary:**\n` +
`- Total Fields: ${schema.fields.total}\n` +
`- Mandatory Fields: ${schema.fields.mandatory}\n` +
`- Reference Fields: ${schema.fields.references}\n` +
`- Calculated Fields: ${schema.fields.calculated}\n\n` +
`š **Relationships:**\n` +
`- Total References: ${schema.relationships.total}\n` +
(schema.relationships.list.length > 0 ?
schema.relationships.list.map((rel) => ` - ${rel.field} ā ${rel.targetTable} (${rel.label})`).join('\n') + '\n' : '') +
'\n' +
(indexes.length > 0 ? `š **Indexes:**\n` +
indexes.map((idx) => `- ${idx.name} (${idx.unique ? 'Unique' : 'Non-unique'}) on: ${idx.fields}`).join('\n') + '\n\n' : '') +
`\nš **Full Schema Details:**\n\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\`\n\n` +
`⨠All schema information discovered dynamically from ServiceNow!`
}]
};
}
catch (error) {
this.logger.error('Failed to discover table schema:', error);
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Failed to discover table schema: ${error}`);
}
}
async run() {
const transport = new stdio_js_1.StdioServerTransport();
await this.server.connect(transport);
this.logger.info('ServiceNow Platform Development MCP Server running on stdio');
}
}
const server = new ServiceNowPlatformDevelopmentMCP();
server.run().catch(console.error);
//# sourceMappingURL=servicenow-platform-development-mcp.js.map