Targetprocess MCP Server

by aaronsb
Verified
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import { TPService } from '../../api/client/tp.service.js'; import { exec } from 'child_process'; import { promisify } from 'util'; import path from 'path'; const execAsync = promisify(exec); // Input schema for inspect object tool export const inspectObjectSchema = z.object({ action: z.enum(['list_types', 'get_properties', 'get_property_details', 'discover_api_structure']), entityType: z.string().optional(), propertyName: z.string().optional(), }); export type InspectObjectInput = z.infer<typeof inspectObjectSchema>; /** * Handler for the inspect object tool */ export class InspectObjectTool { constructor(private service: TPService) {} async execute(args: unknown) { try { const { action, entityType, propertyName } = inspectObjectSchema.parse(args); switch (action) { case 'list_types': return await this.listEntityTypes(); case 'get_properties': if (!entityType) { throw new McpError( ErrorCode.InvalidParams, 'entityType is required for get_properties action' ); } return await this.getEntityProperties(entityType); case 'get_property_details': if (!entityType || !propertyName) { throw new McpError( ErrorCode.InvalidParams, 'entityType and propertyName are required for get_property_details action' ); } return await this.getPropertyDetails(entityType, propertyName); case 'discover_api_structure': return await this.discoverApiStructure(); default: throw new McpError( ErrorCode.InvalidParams, `Unknown action: ${action}` ); } } catch (error) { if (error instanceof z.ZodError) { throw new McpError( ErrorCode.InvalidParams, `Invalid inspect object parameters: ${error.message}` ); } if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InvalidRequest, `Inspect object failed: ${error instanceof Error ? error.message : String(error)}` ); } } /** * List all available entity types */ private async listEntityTypes() { try { // Fetch metadata from the API const response = await this.service.fetchMetadata(); // Extract entity types from the metadata const entityTypes = this.extractEntityTypes(response); return { content: [ { type: 'text', text: JSON.stringify(entityTypes, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InvalidRequest, `Failed to list entity types: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Get properties for a specific entity type */ private async getEntityProperties(entityType: string) { try { // Fetch metadata from the API const response = await this.service.fetchMetadata(); // Extract properties for the specified entity type const properties = this.extractEntityProperties(response, entityType); // Search documentation for additional context const docContext = await this.searchDocumentation(entityType); return { content: [ { type: 'text', text: JSON.stringify({ entityType, properties, documentation: docContext, }, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InvalidRequest, `Failed to get properties for entity type ${entityType}: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Get detailed information about a specific property */ private async getPropertyDetails(entityType: string, propertyName: string) { try { // Fetch metadata from the API const response = await this.service.fetchMetadata(); // Extract property details const propertyDetails = this.extractPropertyDetails(response, entityType, propertyName); // Search documentation for additional context const docContext = await this.searchDocumentation(`${entityType} ${propertyName}`); return { content: [ { type: 'text', text: JSON.stringify({ entityType, propertyName, details: propertyDetails, documentation: docContext, }, null, 2), }, ], }; } catch (error) { throw new McpError( ErrorCode.InvalidRequest, `Failed to get details for property ${propertyName} of entity type ${entityType}: ${error instanceof Error ? error.message : String(error)}` ); } } /** * Extract entity types from metadata */ private extractEntityTypes(metadata: any): string[] { // Implementation will depend on the structure of the metadata // This is a placeholder const entityTypes: string[] = []; if (metadata && metadata.Items) { for (const item of metadata.Items) { if (item.Name && !entityTypes.includes(item.Name)) { entityTypes.push(item.Name); } } } return entityTypes.sort(); } /** * Extract properties for a specific entity type */ private extractEntityProperties(metadata: any, entityType: string): Record<string, any> { // Implementation will depend on the structure of the metadata // This is a placeholder const properties: Record<string, any> = {}; if (metadata && metadata.Items) { const entityMeta = metadata.Items.find((item: any) => item.Name === entityType); if (entityMeta && entityMeta.Fields) { for (const field of entityMeta.Fields) { properties[field.Name] = { type: field.Type, isRequired: field.IsRequired, isReadOnly: field.IsReadOnly, }; } } } return properties; } /** * Extract detailed information about a specific property */ private extractPropertyDetails(metadata: any, entityType: string, propertyName: string): any { // Implementation will depend on the structure of the metadata // This is a placeholder let propertyDetails = null; if (metadata && metadata.Items) { const entityMeta = metadata.Items.find((item: any) => item.Name === entityType); if (entityMeta && entityMeta.Fields) { const field = entityMeta.Fields.find((f: any) => f.Name === propertyName); if (field) { propertyDetails = { name: field.Name, type: field.Type, isRequired: field.IsRequired, isReadOnly: field.IsReadOnly, description: field.Description, allowedValues: field.AllowedValues, }; } } } return propertyDetails; } /** * Search documentation for additional context */ private async searchDocumentation(searchTerm: string): Promise<string> { try { const docsPath = path.resolve(__dirname, '../../../resources/target-process-docs'); const { stdout } = await execAsync(`cd ${docsPath} && ./search-docs.sh "${searchTerm}"`); // Extract relevant information from the search results return this.extractDocumentationContext(stdout); } catch (error) { console.error('Error searching documentation:', error); return 'Documentation search failed or no results found.'; } } /** * Extract relevant information from documentation search results */ private extractDocumentationContext(searchResults: string): string { // Extract the most relevant parts of the search results // This is a simple implementation that returns the first 1000 characters return searchResults.substring(0, 1000) + (searchResults.length > 1000 ? '...' : ''); } /** * Discover API structure through controlled error triggering * This method intentionally triggers an error to extract entity type information */ private async discoverApiStructure() { try { // First try to get entity types directly if possible try { const metadata = await this.service.fetchMetadata(); const entityTypes = this.extractEntityTypes(metadata); if (entityTypes.length > 0) { return { content: [ { type: 'text', text: JSON.stringify({ entityTypes }, null, 2), }, ], }; } } catch (metadataError) { console.error('Failed to fetch metadata directly:', metadataError); } // If direct method failed, try to trigger an informative error // by attempting to get a non-existent entity type try { await this.service.getEntity('NonExistentType', 1); // If no error, return empty result return { content: [ { type: 'text', text: JSON.stringify({ entityTypes: [] }, null, 2), }, ], }; } catch (error) { // Extract entity types from the error message const errorMessage = error instanceof Error ? error.message : String(error); const entityTypeMatch = errorMessage.match(/Valid entity types are: (.*)/); const entityTypes = entityTypeMatch && entityTypeMatch[1] ? entityTypeMatch[1].split(', ') : []; // Return just the extracted data return { content: [ { type: 'text', text: JSON.stringify({ entityTypes }, null, 2), }, ], }; } } catch (error) { // Handle any unexpected errors console.error('Error in discoverApiStructure:', error); return { content: [ { type: 'text', text: JSON.stringify({ error: 'Failed to discover API structure', message: error instanceof Error ? error.message : String(error), entityTypes: [] }, null, 2), }, ], }; } } /** * Get tool definition for MCP */ static getDefinition() { return { name: 'inspect_object', description: 'Inspect Target Process objects and properties through the API. This tool also provides API discovery capabilities through error messages when used with unsupported entity types.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['list_types', 'get_properties', 'get_property_details', 'discover_api_structure'], description: 'Action to perform', }, entityType: { type: 'string', description: 'Type of entity to inspect (required for get_properties and get_property_details)', }, propertyName: { type: 'string', description: 'Name of property to get details for (required for get_property_details)', }, }, required: ['action'], }, } as const; } }