UNPKG

mcp-servicenow

Version:

ServiceNow MCP server for Claude AI integration

340 lines (304 loc) 10.4 kB
import axios from 'axios'; import { IncidentParams, IncidentResult, GetIncidentsParams, GetIncidentsResult } from '../models/incident'; import { log } from '../utils/logger'; // Tool definition for creating an incident export const CreateIncidentTool = { name: 'create_incident', description: 'Create a new incident in ServiceNow', inputSchema: { type: 'object', properties: { short_description: { type: 'string', description: 'Short description of the incident' }, description: { type: 'string', description: 'Detailed description of the incident' }, urgency: { type: 'string', enum: ['1', '2', '3'], description: 'Urgency of the incident (1=high, 2=medium, 3=low)' }, impact: { type: 'string', enum: ['1', '2', '3'], description: 'Impact of the incident (1=high, 2=medium, 3=low)' }, priority: { type: 'string', enum: ['1', '2', '3', '4', '5'], description: 'Priority of the incident (1=critical, 5=planning)' }, state: { type: 'string', enum: ['New', 'In Progress', 'Resolved', 'On Hold', 'Closed', 'Cancelled'], description: 'State of the incident' } }, required: ['short_description'] } }; // Logic for creating an incident export async function createIncident(params: any = {}): Promise<any> { try { log(`createIncident called with params: ${JSON.stringify(params)}`); // Extract parameters const short_description = params.short_description; const description = params.description || ''; const urgency = params.urgency; const impact = params.impact; const priority = params.priority; const state = params.state; // Validate required field if (!short_description) { return { content: [{ type: 'text', text: 'Error: short_description parameter is required' }] }; } const instanceUrl = process.env.SERVICENOW_INSTANCE_URL; const username = process.env.SERVICENOW_USERNAME; const password = process.env.SERVICENOW_PASSWORD; const senderEmail = process.env.SERVICENOW_DEFAULT_SENDER_EMAIL; if (!instanceUrl || !username || !password) { return { content: [{ type: 'text', text: 'Error: Missing ServiceNow credentials. Please check environment variables.' }] }; } log(`Creating incident: ${short_description}`); // Build the incident payload - only include fields that have values const incidentPayload: any = { short_description, description, caller_email: senderEmail }; // Only add optional fields if they have values if (urgency) incidentPayload.urgency = urgency; if (impact) incidentPayload.impact = impact; if (priority) incidentPayload.priority = priority; if (state) incidentPayload.state = state; log(`Incident payload: ${JSON.stringify(incidentPayload)}`); const response = await axios.post( `${instanceUrl}/api/now/table/incident`, incidentPayload, { auth: { username, password }, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } } ); const incident = response.data.result; log(`Incident created successfully: ${incident.number}`); return { content: [{ type: 'text', text: `Incident created successfully: - Number: ${incident.number} - System ID: ${incident.sys_id} - Created: ${incident.sys_created_on} - Short Description: ${incident.short_description} - State: ${incident.state || 'New'} - Priority: ${incident.priority || 'Not Set'} - Urgency: ${incident.urgency || 'Not Set'} - Impact: ${incident.impact || 'Not Set'}` }] }; } catch (error: any) { log(`Error creating incident: ${error.message}`); if (error.response) { log(`Error response status: ${error.response.status}`); log(`Error response data: ${JSON.stringify(error.response.data)}`); } let errorMessage = `Error creating incident: ${error.message}`; if (error.response?.status === 401) { errorMessage += '\nAuthentication error. Please verify ServiceNow credentials.'; } else if (error.response?.status === 403) { errorMessage += '\nPermission error. Please verify ServiceNow user roles.'; } else if (error.response?.status === 400) { errorMessage += '\nBad request. Please check the field values and formats.'; if (error.response.data?.error?.message) { errorMessage += `\nServiceNow error: ${error.response.data.error.message}`; } } return { content: [{ type: 'text', text: errorMessage }] }; } } // Tool definition for getting incidents export const GetIncidentsTool = { name: 'get_incidents', description: 'Get incidents from ServiceNow based on specified criteria', inputSchema: { type: 'object', properties: { assignment_group: { type: 'string', description: 'Name of the assignment group' }, active: { type: 'boolean', description: 'Filter by active status' }, description: { type: 'string', description: 'Keyword to search for in the description' }, number: { type: 'string', description: 'Incident number to search for' }, urgency: { type: 'string', description: 'Filter by urgency level (1=high, 2=medium, 3=low)' }, priority: { type: 'string', description: 'Filter by priority level (1=critical, 5=planning)' }, state: { type: 'string', description: 'State of the incident ("New", "In Progress", "Resolved", "On Hold", "Closed", "Cancelled")' }, limit: { type: 'number', description: 'Maximum number of incidents to return (default: 50)' } } } }; // Logic for getting incidents export async function getIncidents(params: any = {}): Promise<any> { try { log(`getIncidents called with params: ${JSON.stringify(params)}`); const instanceUrl = process.env.SERVICENOW_INSTANCE_URL; const username = process.env.SERVICENOW_USERNAME; const password = process.env.SERVICENOW_PASSWORD; if (!instanceUrl || !username || !password) { return { content: [{ type: 'text', text: 'Error: Missing ServiceNow credentials. Please check environment variables.' }] }; } // Build the query string dynamically const queryParts: string[] = []; if (params.number) { queryParts.push(`number=${params.number}`); } if (params.assignment_group) { queryParts.push(`assignment_group.name=${params.assignment_group}`); } if (typeof params.active === 'boolean') { queryParts.push(`active=${params.active}`); } if (params.description) { queryParts.push(`descriptionCONTAINS${params.description}`); } if (params.urgency) { queryParts.push(`urgency=${params.urgency}`); } if (params.priority) { queryParts.push(`priority=${params.priority}`); } if (params.state) { queryParts.push(`state=${params.state}`); } const query = queryParts.join('^'); // Build request params const requestParams: any = { sysparm_fields: 'number,sys_id,short_description,description,state,priority,urgency,assignment_group.display_value,caller_id.display_value,sys_created_on,active', sysparm_limit: params.limit || 50, sysparm_order_by: '-sys_created_on' }; if (query) { requestParams.sysparm_query = query; } log(`Querying incidents with: ${JSON.stringify(requestParams)}`); const response = await axios.get( `${instanceUrl}/api/now/table/incident`, { params: requestParams, auth: { username, password }, headers: { 'Accept': 'application/json' } } ); log(`ServiceNow response status: ${response.status}`); const incidents = response.data.result; if (!incidents) { return { content: [{ type: 'text', text: 'Error: Invalid response from ServiceNow.' }] }; } log(`Found ${incidents.length} incidents`); if (incidents.length === 0) { return { content: [{ type: 'text', text: 'No incidents found matching the specified criteria.' }] }; } // Format incidents for display const formattedIncidents = incidents.map((incident: any) => { return `• **${incident.number}** - ${incident.short_description} State: ${incident.state || 'Unknown'} Priority: ${incident.priority || 'Not Set'} Urgency: ${incident.urgency || 'Not Set'} Assignment Group: ${incident.assignment_group?.display_value || 'Unassigned'} Caller: ${incident.caller_id?.display_value || 'Unknown'} Created: ${incident.sys_created_on} Active: ${incident.active || 'Unknown'}`; }).join('\n\n'); return { content: [{ type: 'text', text: `Found ${incidents.length} incident(s):\n\n${formattedIncidents}` }] }; } catch (error: any) { log(`Error getting incidents: ${error.message}`); if (error.response) { log(`Error response status: ${error.response.status}`); log(`Error response data: ${JSON.stringify(error.response.data)}`); } let errorMessage = `Error retrieving incidents: ${error.message}`; if (error.response?.status === 401) { errorMessage += '\nAuthentication error. Please verify ServiceNow credentials.'; } else if (error.response?.status === 403) { errorMessage += '\nPermission error. Please verify ServiceNow user roles.'; } else if (error.response?.status === 400) { errorMessage += '\nBad request. Please check the query parameters.'; } return { content: [{ type: 'text', text: errorMessage }] }; } } // Collection of all incident-related tools export const incidentTools = { [CreateIncidentTool.name]: { definition: CreateIncidentTool, execute: createIncident }, [GetIncidentsTool.name]: { definition: GetIncidentsTool, execute: getIncidents } };