UNPKG

leanix-pathfinder-mcp-server

Version:

MCP Server for LeanIX Pathfinder API - Enterprise Architecture Management Platform

325 lines 11.4 kB
#!/usr/bin/env node /** * LeanIX Pathfinder MCP Server - Streamable Transport for Microsoft Copilot Studio * * This server provides the streamable HTTP transport required by Microsoft Copilot Studio. * It wraps the existing MCP server functionality with an HTTP endpoint. */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { LeanIXClient } from './client.js'; import express from 'express'; import cors from 'cors'; // Server configuration const server = new Server({ name: 'leanix-pathfinder-streamable-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); // Initialize LeanIX client let leanixClient = null; function initializeClient() { if (!leanixClient) { const config = { baseUrl: process.env.LEANIX_BASE_URL || '', clientId: process.env.LEANIX_CLIENT_ID || '', clientSecret: process.env.LEANIX_CLIENT_SECRET || '', workspaceId: process.env.LEANIX_WORKSPACE_ID, }; if (!config.baseUrl || !config.clientId || !config.clientSecret) { throw new Error('Missing required LeanIX configuration. Please set LEANIX_BASE_URL, LEANIX_CLIENT_ID, and LEANIX_CLIENT_SECRET environment variables.'); } leanixClient = new LeanIXClient(config); } return leanixClient; } // Tool definitions (same as before) const tools = [ { name: 'leanix_get_suggestions', description: 'Get search suggestions for a given term', inputSchema: { type: 'object', properties: { q: { type: 'string', description: 'Search term (2-1000 characters)', }, count: { type: 'number', description: 'Number of suggestions (1-100)', minimum: 1, maximum: 100, }, object: { type: 'string', description: 'Suggestions object type (default: factSheet)', }, perType: { type: 'boolean', description: 'Group suggestions per object type', }, useCaseId: { type: 'string', description: 'Optional use case ID', }, }, required: ['q'], }, }, { name: 'leanix_list_fact_sheets', description: 'List fact sheets with optional filtering', inputSchema: { type: 'object', properties: { ids: { type: 'string', description: 'Comma-separated list of fact sheet IDs', }, types: { type: 'string', description: 'Comma-separated list of fact sheet types to filter by', }, pageSize: { type: 'number', description: 'Number of results per page', }, cursor: { type: 'string', description: 'Cursor for pagination', }, relations: { type: 'string', description: 'Include relations in response', }, }, }, }, { name: 'leanix_get_fact_sheet', description: 'Get a specific fact sheet by ID', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The ID of the fact sheet to retrieve', }, version: { type: 'number', description: 'Optional version number', }, }, required: ['id'], }, }, { name: 'leanix_create_fact_sheet', description: 'Create a new fact sheet', inputSchema: { type: 'object', properties: { factSheet: { type: 'object', description: 'The fact sheet data to create', properties: { name: { type: 'string' }, type: { type: 'string' }, displayName: { type: 'string' }, description: { type: 'string' }, }, required: ['name', 'type'], }, }, required: ['factSheet'], }, }, { name: 'leanix_update_fact_sheet', description: 'Update an existing fact sheet', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The ID of the fact sheet to update', }, factSheet: { type: 'object', description: 'The fact sheet data to update', }, relationTypes: { type: 'string', description: 'Comma-separated list of relation types to update', }, }, required: ['id', 'factSheet'], }, }, { name: 'leanix_delete_fact_sheet', description: 'Delete a fact sheet', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'The ID of the fact sheet to delete', }, comment: { type: 'string', description: 'Optional comment for the deletion', }, }, required: ['id'], }, }, { name: 'leanix_execute_graphql', description: 'Execute a GraphQL query or mutation', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The GraphQL query or mutation string', }, variables: { type: 'object', description: 'Optional variables for the GraphQL query', }, operationName: { type: 'string', description: 'Optional operation name for multi-operation queries', }, }, required: ['query'], }, }, ]; // Register list tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Register call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const client = initializeClient(); let result; // Same tool handling logic as the original server switch (name) { case 'leanix_get_suggestions': { const { q, count, object, perType, useCaseId } = args; const response = await client.get('/services/pathfinder/v1/suggestions', { headers: { 'Accept': 'application/json', }, }); result = { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; break; } case 'leanix_list_fact_sheets': { const { ids, types, pageSize, cursor, relations } = args; const queryParams = new URLSearchParams(); if (ids) queryParams.append('ids', ids); if (types) queryParams.append('types', types); if (pageSize) queryParams.append('pageSize', pageSize.toString()); if (cursor) queryParams.append('cursor', cursor); if (relations) queryParams.append('relations', relations); const response = await client.get(`/services/pathfinder/v1/factSheets?${queryParams}`); result = { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; break; } case 'leanix_get_fact_sheet': { const { id, version } = args; const queryParams = version ? `?version=${version}` : ''; const response = await client.get(`/services/pathfinder/v1/factSheets/${id}${queryParams}`); result = { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; break; } default: throw new Error(`Unknown tool: ${name}`); } return result; } catch (error) { const errorMessage = error.response?.data?.errorMessage || error.message || 'Unknown error occurred'; const statusCode = error.response?.status || 500; return { content: [ { type: 'text', text: `Error ${statusCode}: ${errorMessage}`, }, ], isError: true, }; } }); // Create Express app for HTTP/SSE transport const app = express(); app.use(cors()); app.use(express.json()); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }); }); // Start the streamable server async function main() { const port = process.env.PORT || 3000; // Create HTTP server const httpServer = app.listen(port, () => { console.error(`LeanIX Pathfinder Streamable MCP Server started on port ${port}`); }); // Setup SSE endpoint for MCP communication app.get('/sse', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control' }); // Create a simple SSE connection const transport = new SSEServerTransport('/sse', res); server.connect(transport).catch(console.error); }); console.error('Streamable MCP Server ready for connections on /sse endpoint'); } main().catch((error) => { console.error('Server failed to start:', error); process.exit(1); }); //# sourceMappingURL=streamable-server.js.map