ABAP-ADT-API MCP-Server

by mario-andreschak
Verified
  • src
#!/usr/bin/env node import { config } from 'dotenv'; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { ADTClient, session_types } from "abap-adt-api"; import path from 'path'; import { AuthHandlers } from './handlers/AuthHandlers.js'; import { TransportHandlers } from './handlers/TransportHandlers.js'; import { ObjectHandlers } from './handlers/ObjectHandlers.js'; import { ClassHandlers } from './handlers/ClassHandlers.js'; import { CodeAnalysisHandlers } from './handlers/CodeAnalysisHandlers.js'; import { ObjectLockHandlers } from './handlers/ObjectLockHandlers.js'; import { ObjectSourceHandlers } from './handlers/ObjectSourceHandlers.js'; import { ObjectDeletionHandlers } from './handlers/ObjectDeletionHandlers.js'; import { ObjectManagementHandlers } from './handlers/ObjectManagementHandlers.js'; import { ObjectRegistrationHandlers } from './handlers/ObjectRegistrationHandlers.js'; import { NodeHandlers } from './handlers/NodeHandlers.js'; import { DiscoveryHandlers } from './handlers/DiscoveryHandlers.js'; import { UnitTestHandlers } from './handlers/UnitTestHandlers.js'; import { PrettyPrinterHandlers } from './handlers/PrettyPrinterHandlers.js'; import { GitHandlers } from './handlers/GitHandlers.js'; import { DdicHandlers } from './handlers/DdicHandlers.js'; import { ServiceBindingHandlers } from './handlers/ServiceBindingHandlers.js'; import { QueryHandlers } from './handlers/QueryHandlers.js'; import { FeedHandlers } from './handlers/FeedHandlers.js'; import { DebugHandlers } from './handlers/DebugHandlers.js'; import { RenameHandlers } from './handlers/RenameHandlers.js'; import { AtcHandlers } from './handlers/AtcHandlers.js'; import { TraceHandlers } from './handlers/TraceHandlers.js'; import { RefactorHandlers } from './handlers/RefactorHandlers.js'; import { RevisionHandlers } from './handlers/RevisionHandlers.js'; config({ path: path.resolve(__dirname, '../.env') }); export class AbapAdtServer extends Server { private adtClient: ADTClient; private authHandlers: AuthHandlers; private transportHandlers: TransportHandlers; private objectHandlers: ObjectHandlers; private classHandlers: ClassHandlers; private codeAnalysisHandlers: CodeAnalysisHandlers; private objectLockHandlers: ObjectLockHandlers; private objectSourceHandlers: ObjectSourceHandlers; private objectDeletionHandlers: ObjectDeletionHandlers; private objectManagementHandlers: ObjectManagementHandlers; private objectRegistrationHandlers: ObjectRegistrationHandlers; private nodeHandlers: NodeHandlers; private discoveryHandlers: DiscoveryHandlers; private unitTestHandlers: UnitTestHandlers; private prettyPrinterHandlers: PrettyPrinterHandlers; private gitHandlers: GitHandlers; private ddicHandlers: DdicHandlers; private serviceBindingHandlers: ServiceBindingHandlers; private queryHandlers: QueryHandlers; private feedHandlers: FeedHandlers; private debugHandlers: DebugHandlers; private renameHandlers: RenameHandlers; private atcHandlers: AtcHandlers; private traceHandlers: TraceHandlers; private refactorHandlers: RefactorHandlers; private revisionHandlers: RevisionHandlers; constructor() { super( { name: "mcp-abap-abap-adt-api", version: "0.1.0", }, { capabilities: { tools: {}, }, } ); const missingVars = ['SAP_URL', 'SAP_USER', 'SAP_PASSWORD'].filter(v => !process.env[v]); if (missingVars.length > 0) { throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`); } this.adtClient = new ADTClient( process.env.SAP_URL as string, process.env.SAP_USER as string, process.env.SAP_PASSWORD as string, process.env.SAP_CLIENT as string, process.env.SAP_LANGUAGE as string ); this.adtClient.stateful = session_types.stateful // Initialize handlers this.authHandlers = new AuthHandlers(this.adtClient); this.transportHandlers = new TransportHandlers(this.adtClient); this.objectHandlers = new ObjectHandlers(this.adtClient); this.classHandlers = new ClassHandlers(this.adtClient); this.codeAnalysisHandlers = new CodeAnalysisHandlers(this.adtClient); this.objectLockHandlers = new ObjectLockHandlers(this.adtClient); this.objectSourceHandlers = new ObjectSourceHandlers(this.adtClient); this.objectDeletionHandlers = new ObjectDeletionHandlers(this.adtClient); this.objectManagementHandlers = new ObjectManagementHandlers(this.adtClient); this.objectRegistrationHandlers = new ObjectRegistrationHandlers(this.adtClient); this.nodeHandlers = new NodeHandlers(this.adtClient); this.discoveryHandlers = new DiscoveryHandlers(this.adtClient); this.unitTestHandlers = new UnitTestHandlers(this.adtClient); this.prettyPrinterHandlers = new PrettyPrinterHandlers(this.adtClient); this.gitHandlers = new GitHandlers(this.adtClient); this.ddicHandlers = new DdicHandlers(this.adtClient); this.serviceBindingHandlers = new ServiceBindingHandlers(this.adtClient); this.queryHandlers = new QueryHandlers(this.adtClient); this.feedHandlers = new FeedHandlers(this.adtClient); this.debugHandlers = new DebugHandlers(this.adtClient); this.renameHandlers = new RenameHandlers(this.adtClient); this.atcHandlers = new AtcHandlers(this.adtClient); this.traceHandlers = new TraceHandlers(this.adtClient); this.refactorHandlers = new RefactorHandlers(this.adtClient); this.revisionHandlers = new RevisionHandlers(this.adtClient); // Setup tool handlers this.setupToolHandlers(); } private serializeResult(result: any) { try { return { content: [{ type: 'text', text: JSON.stringify(result, (key, value) => typeof value === 'bigint' ? value.toString() : value ) }] }; } catch (error) { return this.handleError(new McpError( ErrorCode.InternalError, 'Failed to serialize result' )); } } private handleError(error: unknown) { if (!(error instanceof Error)) { error = new Error(String(error)); } if (error instanceof McpError) { return { content: [{ type: 'text', text: JSON.stringify({ error: error.message, code: error.code }) }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify({ error: 'Internal server error', code: ErrorCode.InternalError }) }], isError: true }; } private setupToolHandlers() { this.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ ...this.authHandlers.getTools(), ...this.transportHandlers.getTools(), ...this.objectHandlers.getTools(), ...this.classHandlers.getTools(), ...this.codeAnalysisHandlers.getTools(), ...this.objectLockHandlers.getTools(), ...this.objectSourceHandlers.getTools(), ...this.objectDeletionHandlers.getTools(), ...this.objectManagementHandlers.getTools(), ...this.objectRegistrationHandlers.getTools(), ...this.nodeHandlers.getTools(), ...this.discoveryHandlers.getTools(), ...this.unitTestHandlers.getTools(), ...this.prettyPrinterHandlers.getTools(), ...this.gitHandlers.getTools(), ...this.ddicHandlers.getTools(), ...this.serviceBindingHandlers.getTools(), ...this.queryHandlers.getTools(), ...this.feedHandlers.getTools(), ...this.debugHandlers.getTools(), ...this.renameHandlers.getTools(), ...this.atcHandlers.getTools(), ...this.traceHandlers.getTools(), ...this.refactorHandlers.getTools(), ...this.revisionHandlers.getTools(), { name: 'healthcheck', description: 'Check server health and connectivity', inputSchema: { type: 'object', properties: {} } } ] }; }); this.setRequestHandler(CallToolRequestSchema, async (request) => { try { let result: any; switch (request.params.name) { case 'login': case 'logout': case 'dropSession': result = await this.authHandlers.handle(request.params.name, request.params.arguments); break; case 'transportInfo': case 'createTransport': case 'hasTransportConfig': case 'transportConfigurations': case 'getTransportConfiguration': case 'setTransportsConfig': case 'createTransportsConfig': case 'userTransports': case 'transportsByConfig': case 'transportDelete': case 'transportRelease': case 'transportSetOwner': case 'transportAddUser': case 'systemUsers': case 'transportReference': result = await this.transportHandlers.handle(request.params.name, request.params.arguments); break; case 'lock': case 'unLock': result = await this.objectLockHandlers.handle(request.params.name, request.params.arguments); break; case 'objectStructure': case 'searchObject': case 'findObjectPath': case 'objectTypes': case 'reentranceTicket': result = await this.objectHandlers.handle(request.params.name, request.params.arguments); break; case 'classIncludes': case 'classComponents': result = await this.classHandlers.handle(request.params.name, request.params.arguments); break; case 'syntaxCheckCode': case 'syntaxCheckCdsUrl': case 'codeCompletion': case 'findDefinition': case 'usageReferences': case 'syntaxCheckTypes': case 'codeCompletionFull': case 'runClass': case 'codeCompletionElement': case 'usageReferenceSnippets': case 'fixProposals': case 'fixEdits': case 'fragmentMappings': case 'abapDocumentation': result = await this.codeAnalysisHandlers.handle(request.params.name, request.params.arguments); break; case 'getObjectSource': case 'setObjectSource': result = await this.objectSourceHandlers.handle(request.params.name, request.params.arguments); break; case 'deleteObject': result = await this.objectDeletionHandlers.handle(request.params.name, request.params.arguments); break; case 'activateObjects': case 'activateByName': case 'inactiveObjects': result = await this.objectManagementHandlers.handle(request.params.name, request.params.arguments); break; case 'objectRegistrationInfo': case 'validateNewObject': case 'createObject': result = await this.objectRegistrationHandlers.handle(request.params.name, request.params.arguments); break; case 'nodeContents': case 'mainPrograms': result = await this.nodeHandlers.handle(request.params.name, request.params.arguments); break; case 'featureDetails': case 'collectionFeatureDetails': case 'findCollectionByUrl': case 'loadTypes': case 'adtDiscovery': case 'adtCoreDiscovery': case 'adtCompatibiliyGraph': result = await this.discoveryHandlers.handle(request.params.name, request.params.arguments); break; case 'unitTestRun': case 'unitTestEvaluation': case 'unitTestOccurrenceMarkers': case 'createTestInclude': result = await this.unitTestHandlers.handle(request.params.name, request.params.arguments); break; case 'prettyPrinterSetting': case 'setPrettyPrinterSetting': case 'prettyPrinter': result = await this.prettyPrinterHandlers.handle(request.params.name, request.params.arguments); break; case 'gitRepos': case 'gitExternalRepoInfo': case 'gitCreateRepo': case 'gitPullRepo': case 'gitUnlinkRepo': case 'stageRepo': case 'pushRepo': case 'checkRepo': case 'remoteRepoInfo': case 'switchRepoBranch': result = await this.gitHandlers.handle(request.params.name, request.params.arguments); break; case 'annotationDefinitions': case 'ddicElement': case 'ddicRepositoryAccess': case 'packageSearchHelp': result = await this.ddicHandlers.handle(request.params.name, request.params.arguments); break; case 'publishServiceBinding': case 'unPublishServiceBinding': case 'bindingDetails': result = await this.serviceBindingHandlers.handle(request.params.name, request.params.arguments); break; case 'tableContents': case 'runQuery': result = await this.queryHandlers.handle(request.params.name, request.params.arguments); break; case 'feeds': case 'dumps': result = await this.feedHandlers.handle(request.params.name, request.params.arguments); break; case 'debuggerListeners': case 'debuggerListen': case 'debuggerDeleteListener': case 'debuggerSetBreakpoints': case 'debuggerDeleteBreakpoints': case 'debuggerAttach': case 'debuggerSaveSettings': case 'debuggerStackTrace': case 'debuggerVariables': case 'debuggerChildVariables': case 'debuggerStep': case 'debuggerGoToStack': case 'debuggerSetVariableValue': result = await this.debugHandlers.handle(request.params.name, request.params.arguments); break; case 'renameEvaluate': case 'renamePreview': case 'renameExecute': result = await this.renameHandlers.handle(request.params.name, request.params.arguments); break; case 'atcCustomizing': case 'atcCheckVariant': case 'createAtcRun': case 'atcWorklists': case 'atcUsers': case 'atcExemptProposal': case 'atcRequestExemption': case 'isProposalMessage': case 'atcContactUri': case 'atcChangeContact': result = await this.atcHandlers.handle(request.params.name, request.params.arguments); break; case 'tracesList': case 'tracesListRequests': case 'tracesHitList': case 'tracesDbAccess': case 'tracesStatements': case 'tracesSetParameters': case 'tracesCreateConfiguration': case 'tracesDeleteConfiguration': case 'tracesDelete': result = await this.traceHandlers.handle(request.params.name, request.params.arguments); break; case 'extractMethodEvaluate': case 'extractMethodPreview': case 'extractMethodExecute': result = await this.refactorHandlers.handle(request.params.name, request.params.arguments); break; case 'revisions': result = await this.revisionHandlers.handle(request.params.name, request.params.arguments); break; case 'healthcheck': result = { status: 'healthy', timestamp: new Date().toISOString() }; break; default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } return this.serializeResult(result); } catch (error) { return this.handleError(error); } }); } async run() { const transport = new StdioServerTransport(); await this.connect(transport); console.error('MCP ABAP ADT API server running on stdio'); // Handle shutdown process.on('SIGINT', async () => { await this.close(); process.exit(0); }); process.on('SIGTERM', async () => { await this.close(); process.exit(0); }); // Handle errors this.onerror = (error) => { console.error('[MCP Error]', error); }; } } // Create and run server instance const server = new AbapAdtServer(); server.run().catch((error) => { console.error('Failed to start MCP server:', error); process.exit(1); });