UNPKG

@toolprint/mcp-graphql-forge

Version:

MCP server that exposes GraphQL APIs to AI tools through automatic schema introspection and tool generation

151 lines 6.04 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { GraphQLClient } from 'graphql-request'; import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; import { createServer } from 'http'; import { generateMCPToolsFromSchema } from './tool-generator.js'; import { introspectGraphQLSchema } from './introspect.js'; import logger from './logger.js'; class GraphQLMCPHTTPServer { server; graphqlClient; tools = []; config; constructor(config) { this.config = config; this.server = new Server({ name: 'fast-mcp-graphql-http', version: '1.0.0', }, { capabilities: { tools: {}, }, }); this.graphqlClient = new GraphQLClient(config.graphqlEndpoint, { headers: config.headers || {} }); this.setupHandlers(); } async loadTools() { try { let introspectionResult; if (this.config.schemaPath && existsSync(this.config.schemaPath)) { const schemaData = readFileSync(this.config.schemaPath, 'utf-8'); introspectionResult = JSON.parse(schemaData); logger.info('Loaded schema from file:', this.config.schemaPath); } else { logger.info('Introspecting GraphQL schema...'); introspectionResult = await introspectGraphQLSchema({ endpoint: this.config.graphqlEndpoint, headers: this.config.headers }); } this.tools = generateMCPToolsFromSchema(introspectionResult); logger.info(`Generated ${this.tools.length} tools from GraphQL schema`); } catch (error) { logger.error('Failed to load tools:', error); throw error; } } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.tools }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const isQuery = name.startsWith('query_'); const isMutation = name.startsWith('mutation_'); if (!isQuery && !isMutation) { throw new Error(`Unknown tool: ${name}`); } const operationName = name.replace(/^(query_|mutation_)/, ''); const operationType = isQuery ? 'query' : 'mutation'; const query = this.buildGraphQLOperation(operationType, operationName, args); const result = await this.graphqlClient.request(query, args); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: 'text', text: `Error executing ${name}: ${errorMessage}` } ], isError: true }; } }); } buildGraphQLOperation(type, operationName, variables) { const variableDeclarations = Object.keys(variables || {}) .map(key => `$${key}: String`) .join(', '); const variableUsage = Object.keys(variables || {}) .map(key => `${key}: $${key}`) .join(', '); return ` ${type} ${operationName}Operation${variableDeclarations ? `(${variableDeclarations})` : ''} { ${operationName}${variableUsage ? `(${variableUsage})` : ''} } `; } async start() { await this.loadTools(); const httpServer = createServer((req, res) => { res.setHeader('Content-Type', 'application/json'); res.setHeader('Access-Control-Allow-Origin', '*'); if (req.method === 'GET' && req.url === '/health') { res.writeHead(200); res.end(JSON.stringify({ status: 'ok', tools: this.tools.length })); return; } res.writeHead(404); res.end(JSON.stringify({ error: 'Not found' })); }); // For now, use stdio transport until SSE is properly configured const transport = new StdioServerTransport(); await this.server.connect(transport); const port = this.config.port || 3000; httpServer.listen(port, () => { logger.info(`GraphQL MCP HTTP Server started on port ${port}`); logger.info(`Health check: http://localhost:${port}/health`); logger.info(`Note: Server uses stdio transport for MCP communication`); }); } } async function main() { const config = { graphqlEndpoint: process.env.GRAPHQL_ENDPOINT || 'http://localhost:4000/graphql', headers: {}, schemaPath: process.env.SCHEMA_PATH || join(process.cwd(), 'schema.json'), port: process.env.PORT ? parseInt(process.env.PORT) : 3000 }; if (process.env.GRAPHQL_AUTH_HEADER) { config.headers.Authorization = process.env.GRAPHQL_AUTH_HEADER; } const server = new GraphQLMCPHTTPServer(config); await server.start(); } if (import.meta.url === `file://${process.argv[1]}`) { main().catch(error => { logger.error('Server failed to start:', error); process.exit(1); }); } //# sourceMappingURL=http-server.js.map