synaptra
Version:
A high-performance Model Context Protocol server for GraphQL APIs with advanced features, type-safety, and developer experience improvements
194 lines • 8.21 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMcpGraphQLServer = exports.McpGraphQLServer = void 0;
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const types_1 = require("./types");
const graphql_client_1 = require("./services/graphql-client");
const logger_1 = require("./utils/logger");
// Import tools
const introspect_schema_1 = require("./tools/introspect-schema");
const query_graphql_1 = require("./tools/query-graphql");
const analyze_query_1 = require("./tools/analyze-query");
class McpGraphQLServer {
server;
config;
client;
constructor(config) {
this.config = types_1.McpConfigSchema.parse(config);
// Initialize logger
(0, logger_1.createLogger)({
level: this.config.logging.level,
enableQueries: this.config.logging.queries,
enablePerformance: this.config.logging.performance,
});
// Initialize GraphQL client
const clientHeaders = { ...this.config.headers };
// Add default API key to headers if provided and no Authorization header exists
if (this.config.defaultApiKey && !clientHeaders.Authorization && !clientHeaders.authorization) {
clientHeaders.Authorization = `Bearer ${this.config.defaultApiKey}`;
}
this.client = new graphql_client_1.GraphQLClientService({
url: this.config.endpoint,
headers: clientHeaders,
timeout: this.config.timeout,
}, this.config.retries);
// Initialize MCP server
this.server = new index_js_1.Server({
name: this.config.name,
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.setupHandlers();
}
setupHandlers() {
const logger = (0, logger_1.getLogger)();
// List available tools
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
const tools = [introspect_schema_1.introspectSchemaTool, query_graphql_1.queryGraphQLTool, analyze_query_1.analyzeQueryTool];
logger.debug('Listing available tools', {
toolCount: tools.length,
tools: tools.map(t => t.name),
});
return { tools };
});
// Handle tool calls
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info(`Tool called: ${name}`, { args });
try {
switch (name) {
case 'introspect-schema':
const schemaResult = await (0, introspect_schema_1.handleIntrospectSchema)(args, this.client);
return {
content: [
{
type: 'text',
text: JSON.stringify(schemaResult, null, 2),
},
],
};
case 'query-graphql':
const queryResult = await (0, query_graphql_1.handleQueryGraphQL)(args, this.client, this.config.allowMutations, this.config.allowSubscriptions);
return {
content: [
{
type: 'text',
text: JSON.stringify(queryResult, null, 2),
},
],
};
case 'analyze-query':
const analysisResult = await (0, analyze_query_1.handleAnalyzeQuery)(args, this.client);
return {
content: [
{
type: 'text',
text: JSON.stringify(analysisResult, null, 2),
},
],
};
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
logger.error(`Tool execution failed: ${name}`, {
error: error instanceof Error ? error.message : 'Unknown error',
args,
});
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: error instanceof Error ? error.message : 'Unknown error',
tool: name,
args,
}, null, 2),
},
],
isError: true,
};
}
});
}
async start() {
const logger = (0, logger_1.getLogger)();
try {
// Initialize schema if introspection is allowed
if (this.config.security.allowIntrospection) {
logger.info('Initializing GraphQL schema...');
await this.client.introspectSchema();
logger.info('Schema initialized successfully');
}
// Start the server
const transport = new stdio_js_1.StdioServerTransport();
await this.server.connect(transport);
logger.info(`MCP GraphQL server started`, {
name: this.config.name,
endpoint: this.config.endpoint,
allowMutations: this.config.allowMutations,
allowSubscriptions: this.config.allowSubscriptions,
});
}
catch (error) {
logger.error('Failed to start MCP server', {
error: error instanceof Error ? error.message : 'Unknown error',
});
throw error;
}
}
async stop() {
const logger = (0, logger_1.getLogger)();
logger.info('Stopping MCP GraphQL server...');
// Close server connection
await this.server.close();
logger.info('MCP GraphQL server stopped');
}
// Utility methods for configuration updates
updateConfig(newConfig) {
this.config = types_1.McpConfigSchema.parse({ ...this.config, ...newConfig });
// Update logger if logging config changed
if (newConfig.logging) {
const logger = (0, logger_1.getLogger)();
logger.updateConfig({
level: this.config.logging.level,
enableQueries: this.config.logging.queries,
enablePerformance: this.config.logging.performance,
});
}
// Update client endpoint if changed
if (newConfig.endpoint || newConfig.headers || newConfig.timeout || newConfig.defaultApiKey) {
const clientHeaders = { ...this.config.headers };
// Add default API key to headers if provided and no Authorization header exists
if (this.config.defaultApiKey && !clientHeaders.Authorization && !clientHeaders.authorization) {
clientHeaders.Authorization = `Bearer ${this.config.defaultApiKey}`;
}
this.client.updateEndpoint({
url: this.config.endpoint,
headers: clientHeaders,
timeout: this.config.timeout,
});
}
}
getConfig() {
return { ...this.config };
}
getClient() {
return this.client;
}
}
exports.McpGraphQLServer = McpGraphQLServer;
// Factory function for easy server creation
const createMcpGraphQLServer = (config) => {
return new McpGraphQLServer(config);
};
exports.createMcpGraphQLServer = createMcpGraphQLServer;
// Default export for convenience
exports.default = McpGraphQLServer;
//# sourceMappingURL=server.js.map