@letoribo/mcp-graphql-enhanced 3.5.1 → 3.7.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/helpers/prompt-registry.d.ts +5 -0
- package/dist/helpers/prompt-registry.d.ts.map +1 -0
- package/dist/helpers/prompt-registry.js +22 -0
- package/dist/helpers/tool-registry.d.ts +6 -0
- package/dist/helpers/tool-registry.d.ts.map +1 -0
- package/dist/helpers/tool-registry.js +42 -0
- package/dist/index.js +88 -36
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-registry.d.ts","sourceRoot":"","sources":["../../src/helpers/prompt-registry.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,QAenB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/helpers/prompt-registry.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.registerPrompt = registerPrompt;
|
|
5
|
+
/**
|
|
6
|
+
* Universal prompt wrapper for MCP server
|
|
7
|
+
*/
|
|
8
|
+
function registerPrompt(server, name, description, template) {
|
|
9
|
+
server.prompt(name, description, (args) => {
|
|
10
|
+
return {
|
|
11
|
+
messages: [
|
|
12
|
+
{
|
|
13
|
+
role: "user",
|
|
14
|
+
content: {
|
|
15
|
+
type: "text",
|
|
16
|
+
text: template + (args?.focus ? ` Focus on: ${args.focus}` : "")
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal tool wrapper that registers tools for both MCP and HTTP Discovery.
|
|
3
|
+
* Handles Zod schema parsing to generate metadata for external clients.
|
|
4
|
+
*/
|
|
5
|
+
export declare function registerTool(server: any, toolHandlers: Map<string, (args: any) => Promise<any>>, registeredToolsMetadata: any[], name: string, description: string, schema: any, handler: (args: any) => Promise<any>): void;
|
|
6
|
+
//# sourceMappingURL=tool-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/helpers/tool-registry.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,YAAY,CACxB,MAAM,EAAE,GAAG,EACX,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,EACtD,uBAAuB,EAAE,GAAG,EAAE,EAC9B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,GAAG,EACX,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,QAwCvC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/helpers/tool-registry.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.registerTool = registerTool;
|
|
5
|
+
/**
|
|
6
|
+
* Universal tool wrapper that registers tools for both MCP and HTTP Discovery.
|
|
7
|
+
* Handles Zod schema parsing to generate metadata for external clients.
|
|
8
|
+
*/
|
|
9
|
+
function registerTool(server, toolHandlers, registeredToolsMetadata, name, description, schema, handler) {
|
|
10
|
+
// 1. Official MCP Server registration
|
|
11
|
+
server.tool(name, description, schema, handler);
|
|
12
|
+
// 2. Map handler for internal HTTP routing
|
|
13
|
+
toolHandlers.set(name, handler);
|
|
14
|
+
// 3. Smart metadata generation for Discovery (HTTP list-tools)
|
|
15
|
+
registeredToolsMetadata.push({
|
|
16
|
+
name,
|
|
17
|
+
description,
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: Object.fromEntries(Object.entries(schema).map(([key, value]) => {
|
|
21
|
+
// Recursive helper to extract type info from Zod objects
|
|
22
|
+
const getZodType = (v) => {
|
|
23
|
+
const type = v?._def?.typeName?.replace('Zod', '').toLowerCase() || "string";
|
|
24
|
+
if (type === 'array') {
|
|
25
|
+
return {
|
|
26
|
+
type: "array",
|
|
27
|
+
items: { type: getZodType(v._def.type) }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { type };
|
|
31
|
+
};
|
|
32
|
+
const typeInfo = getZodType(value);
|
|
33
|
+
return [key, typeInfo];
|
|
34
|
+
})),
|
|
35
|
+
// Identify required fields by checking for ZodOptional or '?' in key
|
|
36
|
+
required: Object.keys(schema).filter(key => {
|
|
37
|
+
const val = schema[key];
|
|
38
|
+
return val?._def?.typeName !== 'ZodOptional' && !key.includes('?');
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,13 +14,15 @@ const graphql_1 = require("graphql");
|
|
|
14
14
|
// Helper imports
|
|
15
15
|
const deprecation_js_1 = require("./helpers/deprecation.js");
|
|
16
16
|
const introspection_js_1 = require("./helpers/introspection.js");
|
|
17
|
+
const tool_registry_js_1 = require("./helpers/tool-registry.js");
|
|
18
|
+
const prompt_registry_js_1 = require("./helpers/prompt-registry.js");
|
|
17
19
|
const getVersion = () => {
|
|
18
20
|
try {
|
|
19
21
|
const pkg = require("../package.json");
|
|
20
22
|
return pkg.version;
|
|
21
23
|
}
|
|
22
24
|
catch {
|
|
23
|
-
return "3.
|
|
25
|
+
return "3.6.0";
|
|
24
26
|
}
|
|
25
27
|
};
|
|
26
28
|
(0, deprecation_js_1.checkDeprecatedArguments)();
|
|
@@ -59,6 +61,11 @@ const server = new mcp_js_1.McpServer({
|
|
|
59
61
|
name: env.NAME,
|
|
60
62
|
version: getVersion(),
|
|
61
63
|
description: "Start of the #mcp-graphql-enhanced channel on GraphQL server. Join here: https://discord.com/channels/622115132221685760/1348633379555184640"
|
|
64
|
+
}, {
|
|
65
|
+
capabilities: {
|
|
66
|
+
prompts: {},
|
|
67
|
+
tools: {}
|
|
68
|
+
}
|
|
62
69
|
});
|
|
63
70
|
// --- CACHE STATE ---
|
|
64
71
|
let cachedSDL = null;
|
|
@@ -198,8 +205,10 @@ async function performUpdate(force) {
|
|
|
198
205
|
updatePromise = null;
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
|
-
// --- TOOL
|
|
208
|
+
// --- TOOL REGISTRY ---
|
|
202
209
|
const toolHandlers = new Map();
|
|
210
|
+
// This will store schemas for our dynamic HTTP 'list-tools' response
|
|
211
|
+
const registeredToolsMetadata = [];
|
|
203
212
|
/** * executionLogs stores the last 5 GraphQL operations.
|
|
204
213
|
* This allows the AI to "inspect" its own generated queries and the raw data
|
|
205
214
|
* for debugging or bridging to 3D visualization tools.
|
|
@@ -255,7 +264,7 @@ const queryGraphqlHandler = async ({ query, variables, headers }) => {
|
|
|
255
264
|
}
|
|
256
265
|
};
|
|
257
266
|
toolHandlers.set("query-graphql", queryGraphqlHandler);
|
|
258
|
-
|
|
267
|
+
(0, tool_registry_js_1.registerTool)(server, toolHandlers, registeredToolsMetadata, "query-graphql", "Execute a GraphQL query against the endpoint", {
|
|
259
268
|
query: zod_1.default.string(),
|
|
260
269
|
variables: zod_1.default.string().optional(),
|
|
261
270
|
headers: zod_1.default.string().optional(),
|
|
@@ -322,91 +331,134 @@ const introspectHandler = async ({ typeNames }) => {
|
|
|
322
331
|
};
|
|
323
332
|
};
|
|
324
333
|
toolHandlers.set("introspect-schema", introspectHandler);
|
|
325
|
-
|
|
334
|
+
(0, tool_registry_js_1.registerTool)(server, toolHandlers, registeredToolsMetadata, "introspect-schema", "Introspect the GraphQL schema with optional type filtering", {
|
|
326
335
|
typeNames: zod_1.default.array(zod_1.default.string()).optional(),
|
|
327
336
|
}, introspectHandler);
|
|
337
|
+
// --- PROMPTS (The "Add from ..." buttons in Claude UI) ---
|
|
338
|
+
// 1. Connection check
|
|
339
|
+
(0, prompt_registry_js_1.registerPrompt)(server, "health-check", "Check if the GraphQL endpoint is alive", "Run 'query-graphql' with query '{ __typename }' to verify connection.");
|
|
340
|
+
// 2. High-level overview
|
|
341
|
+
(0, prompt_registry_js_1.registerPrompt)(server, "schema-overview", "List all available types", "Run 'introspect-schema' to see all types and entry points.");
|
|
342
|
+
// 3. Data types analysis
|
|
343
|
+
(0, prompt_registry_js_1.registerPrompt)(server, "list-scalars", "List all scalar types", "Run 'introspect-schema' and identify all scalars in the schema.");
|
|
328
344
|
// --- HTTP SERVER LOGIC ---
|
|
329
345
|
async function handleHttpRequest(req, res) {
|
|
346
|
+
// Standard CORS headers for cross-origin compatibility
|
|
330
347
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
331
348
|
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
|
|
332
349
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
350
|
+
// Handle preflight requests
|
|
333
351
|
if (req.method === 'OPTIONS') {
|
|
334
352
|
res.writeHead(204);
|
|
335
353
|
res.end();
|
|
336
354
|
return;
|
|
337
355
|
}
|
|
338
356
|
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
339
|
-
//
|
|
357
|
+
// Serve Web GUI (GraphiQL) - ONLY if explicitly enabled
|
|
340
358
|
if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/graphiql')) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
359
|
+
// Check our explicit flag
|
|
360
|
+
if (process.env.ENABLE_HTTP === 'true') {
|
|
361
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
362
|
+
return res.end((0, graphiql_js_1.renderGraphiQL)(env.ENDPOINT, env.HEADERS));
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// Forbidden if not explicitly enabled
|
|
366
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
367
|
+
return res.end("Forbidden: GraphiQL UI is disabled. Start with ENABLE_HTTP=true to use it.");
|
|
368
|
+
}
|
|
344
369
|
}
|
|
370
|
+
// Process MCP JSON-RPC Endpoint
|
|
345
371
|
if (url.pathname === '/mcp' && req.method === 'POST') {
|
|
346
372
|
let body = '';
|
|
347
373
|
req.on('data', chunk => { body += chunk; });
|
|
348
374
|
req.on('end', async () => {
|
|
375
|
+
let requestId = null; // Defined early to be accessible in catch block
|
|
349
376
|
try {
|
|
350
|
-
const
|
|
377
|
+
const request = JSON.parse(body);
|
|
378
|
+
const { method, id, params } = request;
|
|
379
|
+
requestId = id;
|
|
351
380
|
console.error(`[HTTP-RPC] Method: ${method} | ID: ${id}`);
|
|
352
|
-
|
|
381
|
+
// --- DYNAMIC DISCOVERY ---
|
|
382
|
+
if (method === "list-tools" || method === "tools/list") {
|
|
383
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
384
|
+
return res.end(JSON.stringify({
|
|
385
|
+
jsonrpc: '2.0',
|
|
386
|
+
id: requestId,
|
|
387
|
+
result: { tools: registeredToolsMetadata }
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
// --- TOOL EXECUTION (CALL) ---
|
|
391
|
+
let targetMethod = method;
|
|
392
|
+
let toolArgs = params;
|
|
393
|
+
// Support both direct calls and standard MCP "call-tool" structure
|
|
394
|
+
if (method === "call-tool" || method === "tools/call") {
|
|
395
|
+
targetMethod = params.name;
|
|
396
|
+
toolArgs = params.arguments;
|
|
397
|
+
}
|
|
398
|
+
const handler = toolHandlers.get(targetMethod);
|
|
353
399
|
if (!handler) {
|
|
354
400
|
res.writeHead(404);
|
|
355
|
-
return res.end(JSON.stringify({
|
|
401
|
+
return res.end(JSON.stringify({
|
|
402
|
+
jsonrpc: '2.0',
|
|
403
|
+
id: requestId,
|
|
404
|
+
error: { code: -32601, message: `Tool ${targetMethod} not found` }
|
|
405
|
+
}));
|
|
356
406
|
}
|
|
357
|
-
|
|
407
|
+
// Execute the actual business logic for the tool
|
|
408
|
+
const result = await handler(toolArgs);
|
|
358
409
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
359
|
-
res.end(JSON.stringify({
|
|
410
|
+
res.end(JSON.stringify({
|
|
411
|
+
jsonrpc: '2.0',
|
|
412
|
+
id: requestId,
|
|
413
|
+
result
|
|
414
|
+
}));
|
|
360
415
|
}
|
|
361
416
|
catch (e) {
|
|
362
417
|
console.error(`[HTTP-ERROR] ${e.message}`);
|
|
363
418
|
res.writeHead(500);
|
|
364
|
-
res.end(JSON.stringify({
|
|
419
|
+
res.end(JSON.stringify({
|
|
420
|
+
jsonrpc: '2.0',
|
|
421
|
+
id: requestId,
|
|
422
|
+
error: { code: -32603, message: e.message || "Internal Server Error" }
|
|
423
|
+
}));
|
|
365
424
|
}
|
|
366
425
|
});
|
|
367
426
|
return;
|
|
368
427
|
}
|
|
428
|
+
// Health check endpoint for monitoring
|
|
369
429
|
if (url.pathname === '/health') {
|
|
370
430
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
371
431
|
return res.end(JSON.stringify({ status: "ok", server: env.NAME }));
|
|
372
432
|
}
|
|
433
|
+
// Default 404 for unknown paths
|
|
373
434
|
res.writeHead(404);
|
|
374
435
|
res.end("Not Found");
|
|
375
436
|
}
|
|
376
437
|
// --- STARTUP ---
|
|
377
438
|
async function main() {
|
|
378
|
-
const rawEnableHttp = process.env.ENABLE_HTTP;
|
|
379
|
-
// Determine if we should open the HTTP port.
|
|
380
|
-
// 1. Check if we're not running inside the MCP Inspector.
|
|
381
|
-
// 2. Ensure the user hasn't explicitly disabled HTTP (ENABLE_HTTP="false").
|
|
382
439
|
const isInspector = !!(process.env.MCP_INSPECTOR || process.env.INSPECTOR_PORT);
|
|
383
|
-
const
|
|
384
|
-
|
|
440
|
+
const isHttpExplicitlyEnabled = process.env.ENABLE_HTTP === "true";
|
|
441
|
+
// Open HTTP port by default for MCP SSE, unless explicitly disabled
|
|
442
|
+
if (process.env.ENABLE_HTTP !== "false" && !isInspector) {
|
|
385
443
|
const serverHttp = node_http_1.default.createServer(handleHttpRequest);
|
|
386
|
-
// Error handling to prevent crashes if the port is already in use
|
|
387
444
|
serverHttp.on('error', (e) => {
|
|
388
|
-
if (e.code
|
|
389
|
-
console.error(`[HTTP-ERROR] ${
|
|
445
|
+
if (e.code === 'EADDRINUSE') {
|
|
446
|
+
console.error(`[HTTP-ERROR] Port ${env.MCP_PORT} is busy.`);
|
|
447
|
+
}
|
|
390
448
|
});
|
|
391
449
|
serverHttp.listen(env.MCP_PORT, () => {
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
450
|
+
// All-in-one status report
|
|
451
|
+
console.error(`[SYSTEM] Server "${env.NAME}" v${getVersion()} active`);
|
|
452
|
+
console.error(`🤖 MCP SSE: http://localhost:${env.MCP_PORT}/mcp`);
|
|
453
|
+
// Show UI only if explicitly requested
|
|
454
|
+
if (isHttpExplicitlyEnabled) {
|
|
455
|
+
console.error(`🎨 GraphiQL UI: http://localhost:${env.MCP_PORT}/graphiql`);
|
|
396
456
|
}
|
|
397
|
-
console.error(`🤖 MCP Endpoint: http://localhost:${env.MCP_PORT}/mcp`);
|
|
398
457
|
});
|
|
399
458
|
}
|
|
400
459
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
401
460
|
await server.connect(transport);
|
|
402
|
-
|
|
403
|
-
if (!isInspector) {
|
|
404
|
-
console.error(`[STDIO] MCP Server "${env.NAME}" v${getVersion()} started`);
|
|
405
|
-
}
|
|
406
|
-
getSchema().catch(e => {
|
|
407
|
-
if (!isInspector)
|
|
408
|
-
console.error(`[SCHEMA] Warning: ${e.message}`);
|
|
409
|
-
});
|
|
461
|
+
getSchema().catch(() => { });
|
|
410
462
|
}
|
|
411
463
|
process.on('SIGINT', () => process.exit(0));
|
|
412
464
|
process.on('SIGTERM', () => process.exit(0));
|
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.7.0"
|
|
54
54
|
}
|