mcp-servicenow
Version:
ServiceNow MCP server for Claude AI integration
274 lines (273 loc) • 12.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
const dotenv = __importStar(require("dotenv"));
dotenv.config();
const logger_1 = require("./utils/logger");
const mcp_1 = require("./utils/mcp");
const tools_1 = require("./tools");
/**
* Validate environment variables on startup
*/
function validateEnvironment() {
const requiredVars = [
'SERVICENOW_INSTANCE_URL',
'SERVICENOW_USERNAME',
'SERVICENOW_PASSWORD'
];
const missing = requiredVars.filter(varName => !process.env[varName]);
if (missing.length > 0) {
(0, logger_1.log)(`ERROR: Missing required environment variables: ${missing.join(', ')}`);
(0, logger_1.log)('Please set the following environment variables:');
(0, logger_1.log)('- SERVICENOW_INSTANCE_URL: Your ServiceNow instance URL');
(0, logger_1.log)('- SERVICENOW_USERNAME: ServiceNow username');
(0, logger_1.log)('- SERVICENOW_PASSWORD: ServiceNow password');
(0, logger_1.log)('- SERVICENOW_DEFAULT_SENDER_EMAIL: Default email sender (optional)');
return false;
}
// Validate instance URL format
const instanceUrl = process.env.SERVICENOW_INSTANCE_URL;
if (instanceUrl && !instanceUrl.startsWith('https://')) {
(0, logger_1.log)('WARNING: SERVICENOW_INSTANCE_URL should start with https://');
}
(0, logger_1.log)('Environment variables validated successfully');
return true;
}
/**
* Process incoming JSON-RPC message
*/
async function processMessage(message) {
(0, logger_1.log)(`Processing message: ${JSON.stringify(message)}`);
if (typeof message.id === 'undefined' || message.id === null) {
(0, logger_1.log)(`Received notification: ${message.method}`);
return;
}
(0, logger_1.log)(`Processing method: ${message.method} with id: ${message.id}`);
try {
switch (message.method) {
case 'initialize':
(0, logger_1.log)('HANDLING INITIALIZE REQUEST');
(0, mcp_1.sendResponse)(message.id, {
protocolVersion: message.params?.protocolVersion || '2024-11-05',
capabilities: {
tools: {},
prompts: {},
resources: {}
},
serverInfo: {
name: 'servicenow-mcp',
version: '1.0.0'
}
});
break;
case 'prompts/list':
(0, logger_1.log)('HANDLING PROMPTS/LIST REQUEST');
(0, mcp_1.sendResponse)(message.id, {
prompts: []
});
break;
case 'resources/list':
(0, logger_1.log)('HANDLING RESOURCES/LIST REQUEST');
(0, mcp_1.sendResponse)(message.id, {
resources: []
});
break;
case 'tools/list':
(0, logger_1.log)('HANDLING TOOLS/LIST REQUEST');
(0, logger_1.log)(`Available tools: ${tools_1.allToolDefinitions.map(t => t.name).join(', ')}`);
(0, mcp_1.sendResponse)(message.id, {
tools: tools_1.allToolDefinitions
});
break;
case 'tools/call': // Alias for tools/execute
case 'tools/execute':
const toolName = message.params?.name;
if (!toolName) {
(0, logger_1.log)('ERROR: No tool name provided');
(0, mcp_1.sendError)(message.id, -32602, 'Missing required parameter: name');
break;
}
(0, logger_1.log)(`HANDLING TOOLS/EXECUTE REQUEST: ${toolName}`);
(0, logger_1.log)(`Raw message.params: ${JSON.stringify(message.params)}`);
// Try different parameter locations with priority order
let toolParams = {};
if (message.params?.arguments) {
toolParams = message.params.arguments;
(0, logger_1.log)(`Using arguments: ${JSON.stringify(toolParams)}`);
}
else if (message.params?.parameters) {
toolParams = message.params.parameters;
(0, logger_1.log)(`Using parameters: ${JSON.stringify(toolParams)}`);
}
else {
// Sometimes parameters are directly in the params object
const { name, ...otherParams } = message.params || {};
if (Object.keys(otherParams).length > 0) {
toolParams = otherParams;
(0, logger_1.log)(`Using direct params: ${JSON.stringify(toolParams)}`);
}
else {
toolParams = {};
(0, logger_1.log)(`No parameters found, using empty object`);
}
}
(0, logger_1.log)(`Final tool parameters: ${JSON.stringify(toolParams)}`);
const tool = tools_1.allTools[toolName];
if (tool) {
try {
(0, logger_1.log)(`Executing tool: ${toolName}`);
const startTime = Date.now();
const result = await tool.execute(toolParams);
const endTime = Date.now();
(0, logger_1.log)(`Tool execution completed in ${endTime - startTime}ms`);
(0, logger_1.log)(`Tool execution result: ${JSON.stringify(result)}`);
// Validate result format
if (!result || typeof result !== 'object') {
(0, logger_1.log)(`WARNING: Tool ${toolName} returned invalid result format`);
(0, mcp_1.sendError)(message.id, -32603, `Tool ${toolName} returned invalid result`);
}
else if (!result.content || !Array.isArray(result.content)) {
(0, logger_1.log)(`WARNING: Tool ${toolName} returned result without proper content array`);
(0, mcp_1.sendError)(message.id, -32603, `Tool ${toolName} returned malformed result`);
}
else {
(0, mcp_1.sendResponse)(message.id, result);
}
}
catch (toolError) {
(0, logger_1.log)(`Tool execution error: ${toolError.message}`);
(0, logger_1.log)(`Tool error stack: ${toolError.stack}`);
// Provide more specific error messages
let errorMessage = `Tool execution error: ${toolError.message}`;
if (toolError.code === 'ECONNREFUSED') {
errorMessage = 'Cannot connect to ServiceNow instance. Please check SERVICENOW_INSTANCE_URL.';
}
else if (toolError.response?.status === 401) {
errorMessage = 'Authentication failed. Please check ServiceNow credentials.';
}
else if (toolError.response?.status === 403) {
errorMessage = 'Permission denied. Please check ServiceNow user roles.';
}
else if (toolError.response?.status === 404) {
errorMessage = 'ServiceNow endpoint not found. Please check instance URL and API availability.';
}
(0, mcp_1.sendError)(message.id, -32603, errorMessage);
}
}
else {
(0, logger_1.log)(`Tool not found: ${toolName}`);
(0, logger_1.log)(`Available tools: ${Object.keys(tools_1.allTools).join(', ')}`);
(0, mcp_1.sendError)(message.id, -32601, `Tool not found: ${toolName}. Available tools: ${Object.keys(tools_1.allTools).join(', ')}`);
}
break;
default:
(0, logger_1.log)(`Unknown method: ${message.method}`);
(0, mcp_1.sendError)(message.id, -32601, `Method not found: ${message.method}`);
}
}
catch (error) {
(0, logger_1.log)(`Error processing message: ${error.message}`);
(0, logger_1.log)(`Error stack: ${error.stack}`);
// More specific error handling
let errorMessage = `Internal error: ${error.message}`;
if (error instanceof SyntaxError) {
errorMessage = 'Invalid JSON in request';
}
else if (error.code === 'ENOTFOUND') {
errorMessage = 'Network error: Cannot resolve ServiceNow hostname';
}
(0, mcp_1.sendError)(message.id, -32603, errorMessage);
}
}
/**
* Main message processing loop
*/
function startServer() {
(0, logger_1.log)('Server started. Validating environment...');
// Validate environment before starting
if (!validateEnvironment()) {
(0, logger_1.log)('Server startup failed due to missing environment variables');
process.exit(1);
}
(0, logger_1.log)(`Loaded ${Object.keys(tools_1.allTools).length} tools: ${Object.keys(tools_1.allTools).join(', ')}`);
(0, logger_1.log)('Waiting for messages...');
let buffer = '';
process.stdin.on('data', (chunk) => {
buffer += chunk.toString();
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
if (line.trim()) {
try {
const message = JSON.parse(line);
processMessage(message);
}
catch (error) {
(0, logger_1.log)(`Error parsing JSON: ${error.message}`);
(0, logger_1.log)(`Problematic line: ${line}`);
// Don't crash the server on JSON parse errors
}
}
}
});
process.stdin.on('end', () => {
(0, logger_1.log)('Input stream ended.');
});
process.stdin.on('error', (error) => {
(0, logger_1.log)(`Stdin error: ${error.message}`);
});
process.on('uncaughtException', (err) => {
(0, logger_1.log)(`Uncaught exception: ${err.message}`);
(0, logger_1.log)(`Stack trace: ${err.stack}`);
// Don't exit immediately, log and continue
});
process.on('unhandledRejection', (reason, promise) => {
(0, logger_1.log)(`Unhandled rejection at: ${promise}, reason: ${reason}`);
// Don't exit immediately, log and continue
});
process.on('SIGTERM', () => {
(0, logger_1.log)('Received SIGTERM, shutting down gracefully.');
process.exit(0);
});
process.on('SIGINT', () => {
(0, logger_1.log)('Received SIGINT, shutting down gracefully.');
process.exit(0);
});
(0, logger_1.log)('MCP server ready to receive messages');
}
// Start the server
startServer();