Skip to main content
Glama
index.ts28.9 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { ServiceNowBackgroundScriptClient } from './servicenow/client.js'; import { ServiceNowScriptError, ServiceNowErrorResponse, ERROR_CODES } from './servicenow/types.js'; import { ServiceNowTableClient } from './servicenow/tableClient.js'; import { ServiceNowTableError, TABLE_ERROR_CODES } from './servicenow/tableTypes.js'; import { ServiceNowUpdateSetClient } from './servicenow/updateSetClient.js'; import { ServiceNowUpdateSetError, UPDATE_SET_ERROR_CODES } from './servicenow/updateSetTypes.js'; import { globalContextOverflowPrevention } from './utils/contextOverflowPrevention.js'; import { resolveFilePlaceholders } from './utils/filePlaceholderResolver.js'; import { FilePlaceholderError, FILE_PLACEHOLDER_ERROR_CODES } from './utils/filePlaceholderTypes.js'; // Create the MCP server const server = new Server( { name: 'skyenet-mcp-ace', version: '1.4.0', }, { capabilities: { tools: {}, }, } ); // Initialize ServiceNow clients (will throw if credentials missing) let client: ServiceNowBackgroundScriptClient | null = null; let tableClient: ServiceNowTableClient | null = null; let updateSetClient: ServiceNowUpdateSetClient | null = null; let initError: Error | null = null; try { client = new ServiceNowBackgroundScriptClient(); tableClient = new ServiceNowTableClient(); updateSetClient = new ServiceNowUpdateSetClient(); } catch (error) { initError = error instanceof Error ? error : new Error('Unknown initialization error'); } // Register tool handlers server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === 'execute_background_script') { try { // Check if client was initialized successfully if (!client) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'INITIALIZATION_ERROR', message: 'ServiceNow client failed to initialize', details: initError instanceof Error ? initError.message : 'Unknown error', }, } as ServiceNowErrorResponse), }, ], isError: true, }; } // Extract parameters from tool arguments const args = request.params.arguments as Record<string, unknown> | undefined; const script = args?.script as string | undefined; const scope = args?.scope as string | undefined; const timeoutMs = args?.timeout_ms as number | undefined; const includeHtml = args?.include_html as boolean | undefined; const responseMode = args?.response_mode as string | undefined; // Validate required parameters if (!script) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: ERROR_CODES.MISSING_PARAMETER, message: 'Required parameter "script" is missing', details: 'Please provide the JavaScript code to execute', }, } as ServiceNowErrorResponse), }, ], isError: true, }; } if (!scope) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: ERROR_CODES.MISSING_PARAMETER, message: 'Required parameter "scope" is missing', details: 'Please provide the application scope (e.g., "global")', }, } as ServiceNowErrorResponse), }, ], isError: true, }; } // Resolve file placeholders in the request let resolvedScript = script; let resolvedScope = scope; try { const scriptResolution = resolveFilePlaceholders(script); resolvedScript = scriptResolution.data; if (scope) { const scopeResolution = resolveFilePlaceholders(scope); resolvedScope = scopeResolution.data; } } catch (error) { if (error instanceof FilePlaceholderError) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'FILE_PLACEHOLDER_ERROR', message: 'Failed to resolve file placeholder', details: `${error.placeholder}: ${error.message}`, }, } as ServiceNowErrorResponse), }, ], isError: true, }; } // Re-throw unknown errors throw error; } // Execute the script const result = await client.executeScript({ script: resolvedScript, scope: resolvedScope, timeoutMs, include_html: includeHtml, response_mode: responseMode as any, }); // Apply global context overflow prevention const { response: protectedResult, monitoring } = globalContextOverflowPrevention.monitorResponse(result, 'execute_background_script', responseMode); return { content: [ { type: 'text', text: JSON.stringify(protectedResult, null, 2), }, ], }; } catch (error) { const errorResponse: ServiceNowErrorResponse = { success: false, error: { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred', }, }; if (error instanceof ServiceNowScriptError) { errorResponse.error.code = error.code; errorResponse.error.message = error.message; errorResponse.error.details = `HTTP Status: ${error.statusCode || 'N/A'}`; } else if (error instanceof Error) { errorResponse.error.message = error.message; errorResponse.error.details = 'Check ServiceNow instance connectivity and credentials'; } return { content: [ { type: 'text', text: JSON.stringify(errorResponse), }, ], isError: true, }; } } if (request.params.name === 'execute_table_operation') { try { // Check if table client was initialized successfully if (!tableClient) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'INITIALIZATION_ERROR', message: 'ServiceNow table client failed to initialize', details: initError instanceof Error ? initError.message : 'Unknown error', }, }), }, ], isError: true, }; } // Extract parameters from tool arguments const args = request.params.arguments as Record<string, unknown> | undefined; const operation = args?.operation as string | undefined; const table = args?.table as string | undefined; const sysId = args?.sys_id as string | undefined; const sysIds = args?.sys_ids as string[] | undefined; const query = args?.query as string | undefined; const fields = args?.fields as string | undefined; const limit = args?.limit as number | undefined; const offset = args?.offset as number | undefined; const displayValue = args?.display_value as string | undefined; const excludeReferenceLink = args?.exclude_reference_link as boolean | undefined; const data = args?.data as Record<string, any> | Record<string, any>[] | undefined; const batch = args?.batch as boolean | undefined; const validateFields = args?.validate_fields as boolean | undefined; const contextOverflowPrevention = args?.context_overflow_prevention as boolean | undefined; const strictFields = args?.strict_fields as boolean | undefined; const responseMode = args?.response_mode as string | undefined; // Validate required parameters if (!operation) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: TABLE_ERROR_CODES.MISSING_PARAMETER, message: 'Required parameter "operation" is missing', details: 'Please provide the operation type (GET, POST, PUT, PATCH, DELETE)', }, }), }, ], isError: true, }; } if (!table) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: TABLE_ERROR_CODES.MISSING_PARAMETER, message: 'Required parameter "table" is missing', details: 'Please provide the ServiceNow table name', }, }), }, ], isError: true, }; } // Configure context overflow prevention if specified if (contextOverflowPrevention !== undefined) { tableClient.updateConfig({ enableResultSummarization: contextOverflowPrevention, }); } // Resolve file placeholders in the request arguments let resolvedArgs = { operation: operation as any, table, sys_id: sysId, sys_ids: sysIds, query, fields, limit, offset, display_value: displayValue as any, exclude_reference_link: excludeReferenceLink, data, batch, validate_fields: validateFields, strict_fields: strictFields, response_mode: responseMode as any, }; try { const resolution = resolveFilePlaceholders(resolvedArgs); resolvedArgs = resolution.data; } catch (error) { if (error instanceof FilePlaceholderError) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'FILE_PLACEHOLDER_ERROR', message: 'Failed to resolve file placeholder', details: `${error.placeholder}: ${error.message}`, }, }), }, ], isError: true, }; } // Re-throw unknown errors throw error; } // Execute the table operation const result = await tableClient.executeTableOperation({ operation: resolvedArgs.operation, table: resolvedArgs.table, sys_id: resolvedArgs.sys_id, sys_ids: resolvedArgs.sys_ids, query: resolvedArgs.query, fields: resolvedArgs.fields, limit: resolvedArgs.limit, offset: resolvedArgs.offset, display_value: resolvedArgs.display_value, exclude_reference_link: resolvedArgs.exclude_reference_link, data: resolvedArgs.data, batch: resolvedArgs.batch, validate_fields: resolvedArgs.validate_fields, strict_fields: resolvedArgs.strict_fields, response_mode: responseMode as any, }); // Apply global context overflow prevention (additional layer of protection) const { response: protectedResult, monitoring } = globalContextOverflowPrevention.monitorResponse(result, 'execute_table_operation', responseMode); return { content: [ { type: 'text', text: JSON.stringify(protectedResult, null, 2), }, ], }; } catch (error) { const errorResponse = { success: false, error: { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred', details: undefined as string | undefined, }, }; if (error instanceof ServiceNowTableError) { errorResponse.error.code = error.code; errorResponse.error.message = error.message; errorResponse.error.details = `HTTP Status: ${error.statusCode || 'N/A'}`; } else if (error instanceof Error) { errorResponse.error.message = error.message; } return { content: [ { type: 'text', text: JSON.stringify(errorResponse), }, ], isError: true, }; } } if (request.params.name === 'execute_updateset_operation') { try { // Check if update set client was initialized successfully if (!updateSetClient) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'INITIALIZATION_ERROR', message: 'ServiceNow update set client failed to initialize', details: initError instanceof Error ? initError.message : 'Unknown error', }, }), }, ], isError: true, }; } // Extract parameters from tool arguments const args = request.params.arguments as Record<string, unknown> | undefined; const operation = args?.operation as string | undefined; const name = args?.name as string | undefined; const description = args?.description as string | undefined; const scope = args?.scope as string | undefined; const setAsWorking = args?.set_as_working as boolean | undefined; const updateSetSysId = args?.update_set_sys_id as string | undefined; const table = args?.table as string | undefined; const sysId = args?.sys_id as string | undefined; const data = args?.data as Record<string, any> | Record<string, any>[] | undefined; const batch = args?.batch as boolean | undefined; const xmlSysIds = args?.xml_sys_ids as string[] | undefined; const query = args?.query as string | undefined; const force = args?.force as boolean | undefined; const limit = args?.limit as number | undefined; const offset = args?.offset as number | undefined; const filters = args?.filters as Record<string, any> | undefined; const responseMode = args?.response_mode as string | undefined; const quiet = args?.quiet as boolean | undefined; // Validate required parameters if (!operation) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: UPDATE_SET_ERROR_CODES.MISSING_PARAMETER, message: 'Required parameter "operation" is missing', details: 'Please provide the operation type (create, set_working, show_working, clear_working, insert, update, rehome, contents, recent, list, info, complete, reopen, delete, diff_default)', }, }), }, ], isError: true, }; } // Resolve file placeholders in the request arguments let resolvedArgs = { operation: operation as any, name, description, scope, set_as_working: setAsWorking, update_set_sys_id: updateSetSysId, table, sys_id: sysId, data, batch, xml_sys_ids: xmlSysIds, query, force, limit, offset, filters, response_mode: responseMode as any, quiet, }; try { const resolution = resolveFilePlaceholders(resolvedArgs); resolvedArgs = resolution.data; } catch (error) { if (error instanceof FilePlaceholderError) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: { code: 'FILE_PLACEHOLDER_ERROR', message: 'Failed to resolve file placeholder', details: `${error.placeholder}: ${error.message}`, }, }), }, ], isError: true, }; } // Re-throw unknown errors throw error; } // Set timestamp after file placeholder resolution for accurate XML detection // Subtract 500ms to account for timing between placeholder resolution and record creation const timestampBefore = Date.now() - 500; // Execute the update set operation const result = await updateSetClient.executeUpdateSetOperation({ operation: resolvedArgs.operation, name: resolvedArgs.name, description: resolvedArgs.description, scope: resolvedArgs.scope, set_as_working: resolvedArgs.set_as_working, update_set_sys_id: resolvedArgs.update_set_sys_id, table: resolvedArgs.table, sys_id: resolvedArgs.sys_id, data: resolvedArgs.data, batch: resolvedArgs.batch, xml_sys_ids: resolvedArgs.xml_sys_ids, query: resolvedArgs.query, force: resolvedArgs.force, limit: resolvedArgs.limit, offset: resolvedArgs.offset, filters: resolvedArgs.filters, custom_timestamp_before: timestampBefore, response_mode: responseMode as any, quiet: resolvedArgs.quiet, }); // Apply global context overflow prevention const { response: protectedResult, monitoring } = globalContextOverflowPrevention.monitorResponse(result, 'execute_updateset_operation', responseMode); return { content: [ { type: 'text', text: JSON.stringify(protectedResult, null, 2), }, ], }; } catch (error) { const errorResponse = { success: false, error: { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred', details: undefined as string | undefined, }, }; if (error instanceof ServiceNowUpdateSetError) { errorResponse.error.code = error.code; errorResponse.error.message = error.message; errorResponse.error.details = `HTTP Status: ${error.statusCode || 'N/A'}`; } else if (error instanceof Error) { errorResponse.error.message = error.message; } return { content: [ { type: 'text', text: JSON.stringify(errorResponse), }, ], isError: true, }; } } throw new Error(`Unknown tool: ${request.params.name}`); }); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'execute_background_script', description: 'Execute server-side JavaScript in ServiceNow using Background Scripts. ⚠️ SANDBOX ONLY - executes arbitrary code. 🛡️ Auto-truncates large outputs. 📁 Use {{file:path}} for large scripts.', inputSchema: { type: 'object', properties: { script: { type: 'string', description: 'The server-side JavaScript code to execute (e.g., gs.print("Hello");). Maximum 50,000 characters. Supports {{file:...}} placeholders to load content from local files.', }, scope: { type: 'string', description: 'The application scope (e.g., "global" or specific app scope). Required.', }, timeout_ms: { type: 'number', description: 'Optional timeout in milliseconds (default: 60000, range: 1000-300000)', }, include_html: { type: 'boolean', description: 'Include HTML output in response (default: true). Set to false for text-only mode to reduce response size.', }, response_mode: { type: 'string', enum: ['full', 'minimal', 'compact'], description: 'Response verbosity: full (all data), minimal (essential only), compact (summarized). Default: full', }, }, required: ['script', 'scope'], }, }, { name: 'execute_table_operation', description: 'CRUD operations on ServiceNow tables via Table API. Supports GET/POST/PUT/PATCH/DELETE with query syntax and batch operations. ⚠️ SANDBOX ONLY - reads/modifies data. 🛡️ Auto-limits large results. Use pagination for big datasets. 📁 Use {{file:path}} for large data.', inputSchema: { type: 'object', properties: { operation: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], description: 'The operation to perform on the table. Required.', }, table: { type: 'string', description: 'The ServiceNow table name (e.g., "incident", "sys_user"). Required.', }, sys_id: { type: 'string', description: 'System ID for single record operations (GET, PUT, PATCH, DELETE).', }, sys_ids: { type: 'array', items: { type: 'string' }, description: 'Array of system IDs for batch operations.', }, query: { type: 'string', description: 'ServiceNow encoded query string (e.g., "active=true^priority=1").', }, fields: { type: 'string', description: 'Comma-separated list of fields to return.', }, limit: { type: 'number', description: 'Maximum number of records to return (default: 1000).', }, offset: { type: 'number', description: 'Number of records to skip for pagination.', }, display_value: { type: 'string', enum: ['true', 'false', 'all'], description: 'Return display values for reference fields.', }, exclude_reference_link: { type: 'boolean', description: 'Exclude reference link fields from response.', }, data: { type: 'object', description: 'Record data for POST/PUT/PATCH operations. Can be single object or array for batch operations. Supports {{file:...}} placeholders to load content from local files.', }, batch: { type: 'boolean', description: 'Enable batch mode for multiple record operations.', }, validate_fields: { type: 'boolean', description: 'Enable field validation warnings to catch typos and invalid field names. Default: true (validation enabled by default).', }, context_overflow_prevention: { type: 'boolean', description: 'Enable context overflow prevention to limit large result sets. Default: true. Set to false to disable automatic truncation (use with caution).', }, strict_fields: { type: 'boolean', description: 'Strict field filtering - only return requested fields and strip large fields (script, html, css) unless explicitly requested. Default: false.', }, response_mode: { type: 'string', enum: ['full', 'minimal', 'compact'], description: 'Response verbosity: full (all data), minimal (essential only), compact (summarized). Default: full', }, }, required: ['operation', 'table'], }, }, { name: 'execute_updateset_operation', description: 'Manage ServiceNow update sets with lifecycle operations, XML reassignment, and working set tracking. ⚠️ SANDBOX ONLY - modifies update sets. 🛡️ Auto-limits large results. Use pagination for big datasets. 📁 Use {{file:path}} for large data.', inputSchema: { type: 'object', properties: { operation: { type: 'string', enum: ['create', 'set_working', 'show_working', 'clear_working', 'insert', 'update', 'rehome', 'contents', 'recent', 'list', 'info', 'complete', 'reopen', 'delete', 'diff_default'], description: 'The update set operation to perform. Required.', }, name: { type: 'string', description: 'Update set name (required for create operation).', }, description: { type: 'string', description: 'Update set description (optional for create operation).', }, scope: { type: 'string', description: 'Update set scope (optional, defaults to configured scope).', }, set_as_working: { type: 'boolean', description: 'Set the created update set as working set (for create operation).', }, update_set_sys_id: { type: 'string', description: 'Update set sys_id for operations that require it.', }, table: { type: 'string', description: 'Table name for insert/update operations.', }, sys_id: { type: 'string', description: 'Record sys_id for update operations.', }, data: { type: 'object', description: 'Record data for insert/update operations. Can be single object or array for batch operations. Supports {{file:...}} placeholders to load content from local files.', }, batch: { type: 'boolean', description: 'Enable batch mode for multiple record operations.', }, xml_sys_ids: { type: 'array', items: { type: 'string' }, description: 'Array of XML sys_ids for rehome operations.', }, query: { type: 'string', description: 'ServiceNow encoded query string for rehome operations.', }, force: { type: 'boolean', description: 'Force reassignment even if XML is not in Default update set.', }, limit: { type: 'number', description: 'Maximum number of records to return for list/recent operations.', }, offset: { type: 'number', description: 'Number of records to skip for pagination.', }, filters: { type: 'object', description: 'Filters for list operations (scope, state, created_by, sys_created_on).', }, response_mode: { type: 'string', enum: ['full', 'minimal', 'compact'], description: 'Response verbosity: full (all data), minimal (essential only), compact (summarized). Default: full', }, quiet: { type: 'boolean', description: 'Compact acknowledgment for update operations to avoid RESPONSE_TOO_LARGE errors. Default: false.', }, }, required: ['operation'], }, }, ], }; }); // Start the server with stdio transport async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('SkyeNet MCP ACE Server (ServiceNow Background Scripts) started and ready for stdio communication'); } // Handle process termination gracefully process.on('SIGINT', () => { console.error('Shutting down MCP server...'); process.exit(0); }); process.on('SIGTERM', () => { console.error('Shutting down MCP server...'); process.exit(0); }); // Start the server main().catch((error) => { console.error('Failed to start MCP server:', error); process.exit(1); });

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ClearSkye/SkyeNet-MCP-ACE'

If you have feedback or need assistance with the MCP directory API, please join our Discord server