@letoribo/mcp-graphql-enhanced 3.5.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +82 -11
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -198,8 +198,41 @@ async function performUpdate(force) {
|
|
|
198
198
|
updatePromise = null;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
// --- TOOL
|
|
201
|
+
// --- TOOL REGISTRY ---
|
|
202
202
|
const toolHandlers = new Map();
|
|
203
|
+
// This will store schemas for our dynamic HTTP 'list-tools' response
|
|
204
|
+
const registeredToolsMetadata = [];
|
|
205
|
+
/**
|
|
206
|
+
* Helper to register tools in both MCP Server and our local registry
|
|
207
|
+
* Handles both plain objects and Zod schemas
|
|
208
|
+
*/
|
|
209
|
+
function registerTool(name, description, schema, handler) {
|
|
210
|
+
// 1. Register in official MCP Server
|
|
211
|
+
server.tool(name, description, schema, handler);
|
|
212
|
+
// 2. Save handler for our HTTP routing
|
|
213
|
+
toolHandlers.set(name, handler);
|
|
214
|
+
// 3. Store metadata for dynamic Discovery
|
|
215
|
+
// We treat 'schema' as a plain object for the JSON output
|
|
216
|
+
registeredToolsMetadata.push({
|
|
217
|
+
name,
|
|
218
|
+
description,
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: Object.fromEntries(Object.entries(schema).map(([key, value]) => {
|
|
222
|
+
// Detect type from Zod or default to string
|
|
223
|
+
const typeName = value?._def?.typeName
|
|
224
|
+
? value._def.typeName.replace('Zod', '').toLowerCase()
|
|
225
|
+
: "string";
|
|
226
|
+
return [key, { type: typeName }];
|
|
227
|
+
})),
|
|
228
|
+
// Assume all fields in the object are required unless specified
|
|
229
|
+
required: Object.keys(schema).filter(key => {
|
|
230
|
+
const val = schema[key];
|
|
231
|
+
return val?._def?.typeName !== 'ZodOptional' && !key.includes('?');
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
203
236
|
/** * executionLogs stores the last 5 GraphQL operations.
|
|
204
237
|
* This allows the AI to "inspect" its own generated queries and the raw data
|
|
205
238
|
* for debugging or bridging to 3D visualization tools.
|
|
@@ -255,7 +288,7 @@ const queryGraphqlHandler = async ({ query, variables, headers }) => {
|
|
|
255
288
|
}
|
|
256
289
|
};
|
|
257
290
|
toolHandlers.set("query-graphql", queryGraphqlHandler);
|
|
258
|
-
|
|
291
|
+
registerTool("query-graphql", "Execute a GraphQL query against the endpoint", {
|
|
259
292
|
query: zod_1.default.string(),
|
|
260
293
|
variables: zod_1.default.string().optional(),
|
|
261
294
|
headers: zod_1.default.string().optional(),
|
|
@@ -322,54 +355,92 @@ const introspectHandler = async ({ typeNames }) => {
|
|
|
322
355
|
};
|
|
323
356
|
};
|
|
324
357
|
toolHandlers.set("introspect-schema", introspectHandler);
|
|
325
|
-
|
|
358
|
+
registerTool("introspect-schema", "Introspect the GraphQL schema with optional type filtering", {
|
|
326
359
|
typeNames: zod_1.default.array(zod_1.default.string()).optional(),
|
|
327
360
|
}, introspectHandler);
|
|
328
361
|
// --- HTTP SERVER LOGIC ---
|
|
329
362
|
async function handleHttpRequest(req, res) {
|
|
363
|
+
// Standard CORS headers for cross-origin compatibility
|
|
330
364
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
331
365
|
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
|
|
332
366
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
367
|
+
// Handle preflight requests
|
|
333
368
|
if (req.method === 'OPTIONS') {
|
|
334
369
|
res.writeHead(204);
|
|
335
370
|
res.end();
|
|
336
371
|
return;
|
|
337
372
|
}
|
|
338
373
|
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
339
|
-
//
|
|
374
|
+
// Serve Web GUI (GraphiQL)
|
|
340
375
|
if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/graphiql')) {
|
|
341
376
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
342
|
-
// Pass env.HEADERS to the explorer
|
|
343
377
|
return res.end((0, graphiql_js_1.renderGraphiQL)(env.ENDPOINT, env.HEADERS));
|
|
344
378
|
}
|
|
379
|
+
// Process MCP JSON-RPC Endpoint
|
|
345
380
|
if (url.pathname === '/mcp' && req.method === 'POST') {
|
|
346
381
|
let body = '';
|
|
347
382
|
req.on('data', chunk => { body += chunk; });
|
|
348
383
|
req.on('end', async () => {
|
|
384
|
+
let requestId = null; // Defined early to be accessible in catch block
|
|
349
385
|
try {
|
|
350
|
-
const
|
|
386
|
+
const request = JSON.parse(body);
|
|
387
|
+
const { method, id, params } = request;
|
|
388
|
+
requestId = id;
|
|
351
389
|
console.error(`[HTTP-RPC] Method: ${method} | ID: ${id}`);
|
|
352
|
-
|
|
390
|
+
// --- DYNAMIC DISCOVERY ---
|
|
391
|
+
if (method === "list-tools" || method === "tools/list") {
|
|
392
|
+
// Просто отдаем то, что накопили в нашем реестре при регистрации
|
|
393
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
394
|
+
return res.end(JSON.stringify({
|
|
395
|
+
jsonrpc: '2.0',
|
|
396
|
+
id: requestId,
|
|
397
|
+
result: { tools: registeredToolsMetadata }
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
// --- TOOL EXECUTION (CALL) ---
|
|
401
|
+
let targetMethod = method;
|
|
402
|
+
let toolArgs = params;
|
|
403
|
+
// Support both direct calls and standard MCP "call-tool" structure
|
|
404
|
+
if (method === "call-tool" || method === "tools/call") {
|
|
405
|
+
targetMethod = params.name;
|
|
406
|
+
toolArgs = params.arguments;
|
|
407
|
+
}
|
|
408
|
+
const handler = toolHandlers.get(targetMethod);
|
|
353
409
|
if (!handler) {
|
|
354
410
|
res.writeHead(404);
|
|
355
|
-
return res.end(JSON.stringify({
|
|
411
|
+
return res.end(JSON.stringify({
|
|
412
|
+
jsonrpc: '2.0',
|
|
413
|
+
id: requestId,
|
|
414
|
+
error: { code: -32601, message: `Tool ${targetMethod} not found` }
|
|
415
|
+
}));
|
|
356
416
|
}
|
|
357
|
-
|
|
417
|
+
// Execute the actual business logic for the tool
|
|
418
|
+
const result = await handler(toolArgs);
|
|
358
419
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
359
|
-
res.end(JSON.stringify({
|
|
420
|
+
res.end(JSON.stringify({
|
|
421
|
+
jsonrpc: '2.0',
|
|
422
|
+
id: requestId,
|
|
423
|
+
result
|
|
424
|
+
}));
|
|
360
425
|
}
|
|
361
426
|
catch (e) {
|
|
362
427
|
console.error(`[HTTP-ERROR] ${e.message}`);
|
|
363
428
|
res.writeHead(500);
|
|
364
|
-
res.end(JSON.stringify({
|
|
429
|
+
res.end(JSON.stringify({
|
|
430
|
+
jsonrpc: '2.0',
|
|
431
|
+
id: requestId,
|
|
432
|
+
error: { code: -32603, message: e.message || "Internal Server Error" }
|
|
433
|
+
}));
|
|
365
434
|
}
|
|
366
435
|
});
|
|
367
436
|
return;
|
|
368
437
|
}
|
|
438
|
+
// Health check endpoint for monitoring
|
|
369
439
|
if (url.pathname === '/health') {
|
|
370
440
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
371
441
|
return res.end(JSON.stringify({ status: "ok", server: env.NAME }));
|
|
372
442
|
}
|
|
443
|
+
// Default 404 for unknown paths
|
|
373
444
|
res.writeHead(404);
|
|
374
445
|
res.end("Not Found");
|
|
375
446
|
}
|
package/package.json
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"format": "prettier --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
39
39
|
"graphql": "^16.11.0",
|
|
40
40
|
"yargs": "17.7.2",
|
|
41
41
|
"zod": "3.25.30",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"tsx": "^4.21.0",
|
|
51
51
|
"typescript": "5.8.3"
|
|
52
52
|
},
|
|
53
|
-
"version": "3.
|
|
53
|
+
"version": "3.6.0"
|
|
54
54
|
}
|