@letoribo/mcp-graphql-enhanced 3.6.0 → 3.8.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/LICENSE +3 -18
- 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 +84 -90
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024 Boris Besemer
|
|
4
|
-
Copyright (c) 2025-2026
|
|
3
|
+
Copyright (c) 2024 Boris Besemer
|
|
4
|
+
Copyright (c) 2025-2026 letoribo
|
|
5
5
|
|
|
6
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,19 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
19
19
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
20
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
21
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
PROJECT EVOLUTION & SOVEREIGNTY NOTE:
|
|
26
|
-
As of mid-2025, this project has undergone a fundamental architectural shift.
|
|
27
|
-
The legacy Bun-based implementation has been decommissioned and replaced with
|
|
28
|
-
a surgical, production-ready NPM/Node.js core for maximum stability.
|
|
29
|
-
|
|
30
|
-
This "Enhanced" version was battle-tested as the architectural engine for the
|
|
31
|
-
"Neo4j Discord-as-a-Database" project (mcp-neo4j-discord.vercel.app), handling
|
|
32
|
-
large-scale graph datasets.
|
|
33
|
-
|
|
34
|
-
While active real-time data ingestion is currently throttled due to persistent
|
|
35
|
-
infrastructure challenges and blackouts in Odesa, the codebase remains the
|
|
36
|
-
sovereign standard for high-performance GraphRAG, 3D visualization interface,
|
|
37
|
-
and resilient AI intent parsing. Developed under pressure in Odesa, Ukraine.
|
|
22
|
+
SOFTWARE.
|
|
@@ -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;
|
|
@@ -67,7 +74,6 @@ let schemaLoadError = null;
|
|
|
67
74
|
let isUpdating = false;
|
|
68
75
|
let updatePromise = null;
|
|
69
76
|
let lastKnownTypeCount = 0;
|
|
70
|
-
let expectEmptySchema = false; // Intent flag for intentional purges
|
|
71
77
|
/**
|
|
72
78
|
* Smart Hybrid Schema Fetcher (Zero-Error Version)
|
|
73
79
|
* @param force If true, blocks and waits for the new schema evolution.
|
|
@@ -101,15 +107,13 @@ async function getSchema(force = false, requestedTypes) {
|
|
|
101
107
|
updatePromise = performUpdate(force);
|
|
102
108
|
try {
|
|
103
109
|
if (force || !cachedSDL) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
else {
|
|
107
|
-
updatePromise.catch(err => console.error("[SCHEMA] Background update failed:", err));
|
|
108
|
-
return cachedSDL;
|
|
110
|
+
await updatePromise; // Wait for update to complete
|
|
111
|
+
return cachedSchemaObject;
|
|
109
112
|
}
|
|
113
|
+
return cachedSchemaObject;
|
|
110
114
|
}
|
|
111
115
|
finally {
|
|
112
|
-
|
|
116
|
+
updatePromise = null;
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
/**
|
|
@@ -168,11 +172,12 @@ async function performUpdate(force) {
|
|
|
168
172
|
// Maintain state for "Gap Analysis"
|
|
169
173
|
lastKnownTypeCount = businessTypes.length;
|
|
170
174
|
const currentSDL = printSchema(tempSchema);
|
|
171
|
-
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
172
175
|
// --- CACHE & NOTIFICATION ---
|
|
176
|
+
// Always update the live object if we successfully built it
|
|
177
|
+
cachedSchemaObject = tempSchema;
|
|
173
178
|
if (currentSDL !== cachedSDL) {
|
|
174
179
|
cachedSDL = currentSDL;
|
|
175
|
-
|
|
180
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
176
181
|
return [
|
|
177
182
|
`✨ SCHEMA EVOLVED (${duration}s)`,
|
|
178
183
|
`📊 Source: ${env.SCHEMA ? 'Local/Remote SDL' : 'Live Endpoint'}`,
|
|
@@ -182,6 +187,7 @@ async function performUpdate(force) {
|
|
|
182
187
|
].join('\n');
|
|
183
188
|
}
|
|
184
189
|
else {
|
|
190
|
+
// Even if SDL string is the same, we've ensured cachedSchemaObject is set above
|
|
185
191
|
return `✅ Status: Schema stable (${lastKnownTypeCount} labels).`;
|
|
186
192
|
}
|
|
187
193
|
}
|
|
@@ -202,37 +208,6 @@ async function performUpdate(force) {
|
|
|
202
208
|
const toolHandlers = new Map();
|
|
203
209
|
// This will store schemas for our dynamic HTTP 'list-tools' response
|
|
204
210
|
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
|
-
}
|
|
236
211
|
/** * executionLogs stores the last 5 GraphQL operations.
|
|
237
212
|
* This allows the AI to "inspect" its own generated queries and the raw data
|
|
238
213
|
* for debugging or bridging to 3D visualization tools.
|
|
@@ -288,41 +263,59 @@ const queryGraphqlHandler = async ({ query, variables, headers }) => {
|
|
|
288
263
|
}
|
|
289
264
|
};
|
|
290
265
|
toolHandlers.set("query-graphql", queryGraphqlHandler);
|
|
291
|
-
registerTool("query-graphql", "Execute a GraphQL query against the endpoint", {
|
|
266
|
+
(0, tool_registry_js_1.registerTool)(server, toolHandlers, registeredToolsMetadata, "query-graphql", "Execute a GraphQL query against the endpoint", {
|
|
292
267
|
query: zod_1.default.string(),
|
|
293
268
|
variables: zod_1.default.string().optional(),
|
|
294
269
|
headers: zod_1.default.string().optional(),
|
|
295
270
|
}, queryGraphqlHandler);
|
|
296
271
|
// Tool: introspect-schema
|
|
272
|
+
// --- TOOL: introspect-schema ---
|
|
297
273
|
const introspectHandler = async ({ typeNames }) => {
|
|
298
|
-
// 1.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
274
|
+
// 1. Fetch the schema directly
|
|
275
|
+
const result = await getSchema(true);
|
|
276
|
+
// Explicitly check if the result is an error string or null
|
|
277
|
+
if (!result || typeof result === 'string') {
|
|
278
|
+
return {
|
|
279
|
+
content: [{
|
|
280
|
+
type: "text",
|
|
281
|
+
text: `❌ SCHEMA_ERROR: ${typeof result === 'string' ? result : 'GraphQL schema is not initialized yet.'}\n` +
|
|
282
|
+
`ACTION: Please wait 5-10 seconds for the Neo4j endpoint to respond.`
|
|
283
|
+
}]
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// TYPE FIX: Cast to GraphQLSchema so TS knows methods like getTypeMap exist
|
|
287
|
+
const schema = result;
|
|
302
288
|
const typeMap = schema.getTypeMap();
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
// --- GAP ANALYSIS: Check if requested types are actually in the map ---
|
|
289
|
+
const mutationType = schema.getMutationType();
|
|
290
|
+
const mutationFields = mutationType ? mutationType.getFields() : {};
|
|
291
|
+
// --- GAP ANALYSIS ---
|
|
307
292
|
if (typeNames && typeNames.length > 0) {
|
|
308
|
-
const missing = typeNames.filter(name =>
|
|
293
|
+
const missing = typeNames.filter(name => {
|
|
294
|
+
const existsAsType = !!typeMap[name];
|
|
295
|
+
const existsAsMutation = !!mutationFields[name];
|
|
296
|
+
return !existsAsType && !existsAsMutation;
|
|
297
|
+
});
|
|
309
298
|
if (missing.length > 0) {
|
|
299
|
+
const typeKeys = Object.keys(typeMap).filter(t => !t.startsWith('__'));
|
|
300
|
+
const schemaVersion = `v${typeKeys.length}.${Math.floor(Date.now() / 10000) % 1000}`;
|
|
310
301
|
return {
|
|
311
302
|
content: [{
|
|
312
303
|
type: "text",
|
|
313
304
|
text: `❌ PARTIAL RESULTS [ID: ${schemaVersion}]\n\n` +
|
|
314
|
-
`MISSING
|
|
315
|
-
`REASON:
|
|
316
|
-
`ACTION:
|
|
317
|
-
`
|
|
305
|
+
`MISSING ENTITIES: ${missing.join(", ")}\n` +
|
|
306
|
+
`REASON: These specific types or mutations were not found in the current schema map.\n` +
|
|
307
|
+
`ACTION: Ensure the names match your Neo4j labels exactly.\n\n` +
|
|
308
|
+
`AVAILABLE_ENTITIES: ${typeKeys.filter(t => !['Query', 'Mutation', 'Report'].includes(t)).join(", ")}`
|
|
318
309
|
}]
|
|
319
310
|
};
|
|
320
311
|
}
|
|
321
312
|
}
|
|
313
|
+
// 2. Generate General Manifest
|
|
322
314
|
if (!typeNames || typeNames.length === 0) {
|
|
323
315
|
const queryType = schema.getQueryType();
|
|
324
316
|
const discoveredEntities = new Set();
|
|
325
317
|
if (queryType) {
|
|
318
|
+
// TYPE FIX: Cast fields to Record<string, GraphQLField<any, any>>
|
|
326
319
|
const queryFields = queryType.getFields();
|
|
327
320
|
Object.values(queryFields).forEach((field) => {
|
|
328
321
|
const namedType = (0, graphql_1.getNamedType)(field.type);
|
|
@@ -332,32 +325,35 @@ const introspectHandler = async ({ typeNames }) => {
|
|
|
332
325
|
});
|
|
333
326
|
}
|
|
334
327
|
const coreEntities = Array.from(discoveredEntities).sort();
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
`🔗 Join the discussion: https://discord.com/channels/625400653321076807/1480510460339159184`,
|
|
338
|
-
`---`
|
|
339
|
-
].join('\n');
|
|
328
|
+
const typeKeys = Object.keys(typeMap).filter(t => !t.startsWith('__'));
|
|
329
|
+
const schemaVersion = `v${typeKeys.length}.${Math.floor(Date.now() / 10000) % 1000}`;
|
|
340
330
|
return {
|
|
341
331
|
content: [{
|
|
342
332
|
type: "text",
|
|
343
|
-
text:
|
|
344
|
-
`GraphQL Schema Manifest [ID: ${schemaVersion}]\n\n` +
|
|
333
|
+
text: `GraphQL Schema Manifest [ID: ${schemaVersion}]\n\n` +
|
|
345
334
|
`ENTRY_POINT_ENTITIES: ${coreEntities.join(", ") || "None"}\n` +
|
|
346
335
|
`TOTAL_SCHEMA_TYPES: ${typeKeys.length}\n\n` +
|
|
347
336
|
`ALL_AVAILABLE_TYPES: ${typeKeys.join(", ")}`
|
|
348
337
|
}]
|
|
349
338
|
};
|
|
350
339
|
}
|
|
351
|
-
// 3. Detailed introspection
|
|
340
|
+
// 3. Detailed introspection
|
|
352
341
|
const filtered = (0, introspection_js_1.introspectSpecificTypes)(schema, typeNames);
|
|
353
342
|
return {
|
|
354
343
|
content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }]
|
|
355
344
|
};
|
|
356
345
|
};
|
|
357
346
|
toolHandlers.set("introspect-schema", introspectHandler);
|
|
358
|
-
registerTool("introspect-schema", "Introspect the GraphQL schema with optional type filtering", {
|
|
347
|
+
(0, tool_registry_js_1.registerTool)(server, toolHandlers, registeredToolsMetadata, "introspect-schema", "Introspect the GraphQL schema with optional type filtering", {
|
|
359
348
|
typeNames: zod_1.default.array(zod_1.default.string()).optional(),
|
|
360
349
|
}, introspectHandler);
|
|
350
|
+
// --- PROMPTS (The "Add from ..." buttons in Claude UI) ---
|
|
351
|
+
// 1. Connection check
|
|
352
|
+
(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.");
|
|
353
|
+
// 2. High-level overview
|
|
354
|
+
(0, prompt_registry_js_1.registerPrompt)(server, "schema-overview", "List all available types", "Run 'introspect-schema' to see all types and entry points.");
|
|
355
|
+
// 3. Data types analysis
|
|
356
|
+
(0, prompt_registry_js_1.registerPrompt)(server, "list-scalars", "List all scalar types", "Run 'introspect-schema' and identify all scalars in the schema.");
|
|
361
357
|
// --- HTTP SERVER LOGIC ---
|
|
362
358
|
async function handleHttpRequest(req, res) {
|
|
363
359
|
// Standard CORS headers for cross-origin compatibility
|
|
@@ -371,10 +367,18 @@ async function handleHttpRequest(req, res) {
|
|
|
371
367
|
return;
|
|
372
368
|
}
|
|
373
369
|
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
374
|
-
// Serve Web GUI (GraphiQL)
|
|
370
|
+
// Serve Web GUI (GraphiQL) - ONLY if explicitly enabled
|
|
375
371
|
if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/graphiql')) {
|
|
376
|
-
|
|
377
|
-
|
|
372
|
+
// Check our explicit flag
|
|
373
|
+
if (process.env.ENABLE_HTTP === 'true') {
|
|
374
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
375
|
+
return res.end((0, graphiql_js_1.renderGraphiQL)(env.ENDPOINT, env.HEADERS));
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Forbidden if not explicitly enabled
|
|
379
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
380
|
+
return res.end("Forbidden: GraphiQL UI is disabled. Start with ENABLE_HTTP=true to use it.");
|
|
381
|
+
}
|
|
378
382
|
}
|
|
379
383
|
// Process MCP JSON-RPC Endpoint
|
|
380
384
|
if (url.pathname === '/mcp' && req.method === 'POST') {
|
|
@@ -389,7 +393,6 @@ async function handleHttpRequest(req, res) {
|
|
|
389
393
|
console.error(`[HTTP-RPC] Method: ${method} | ID: ${id}`);
|
|
390
394
|
// --- DYNAMIC DISCOVERY ---
|
|
391
395
|
if (method === "list-tools" || method === "tools/list") {
|
|
392
|
-
// Просто отдаем то, что накопили в нашем реестре при регистрации
|
|
393
396
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
394
397
|
return res.end(JSON.stringify({
|
|
395
398
|
jsonrpc: '2.0',
|
|
@@ -446,38 +449,29 @@ async function handleHttpRequest(req, res) {
|
|
|
446
449
|
}
|
|
447
450
|
// --- STARTUP ---
|
|
448
451
|
async function main() {
|
|
449
|
-
const rawEnableHttp = process.env.ENABLE_HTTP;
|
|
450
|
-
// Determine if we should open the HTTP port.
|
|
451
|
-
// 1. Check if we're not running inside the MCP Inspector.
|
|
452
|
-
// 2. Ensure the user hasn't explicitly disabled HTTP (ENABLE_HTTP="false").
|
|
453
452
|
const isInspector = !!(process.env.MCP_INSPECTOR || process.env.INSPECTOR_PORT);
|
|
454
|
-
const
|
|
455
|
-
|
|
453
|
+
const isHttpExplicitlyEnabled = process.env.ENABLE_HTTP === "true";
|
|
454
|
+
// Open HTTP port by default for MCP SSE, unless explicitly disabled
|
|
455
|
+
if (process.env.ENABLE_HTTP !== "false" && !isInspector) {
|
|
456
456
|
const serverHttp = node_http_1.default.createServer(handleHttpRequest);
|
|
457
|
-
// Error handling to prevent crashes if the port is already in use
|
|
458
457
|
serverHttp.on('error', (e) => {
|
|
459
|
-
if (e.code
|
|
460
|
-
console.error(`[HTTP-ERROR] ${
|
|
458
|
+
if (e.code === 'EADDRINUSE') {
|
|
459
|
+
console.error(`[HTTP-ERROR] Port ${env.MCP_PORT} is busy.`);
|
|
460
|
+
}
|
|
461
461
|
});
|
|
462
462
|
serverHttp.listen(env.MCP_PORT, () => {
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
463
|
+
// All-in-one status report
|
|
464
|
+
console.error(`[SYSTEM] Server "${env.NAME}" v${getVersion()} active`);
|
|
465
|
+
console.error(`🤖 MCP SSE: http://localhost:${env.MCP_PORT}/mcp`);
|
|
466
|
+
// Show UI only if explicitly requested
|
|
467
|
+
if (isHttpExplicitlyEnabled) {
|
|
468
|
+
console.error(`🎨 GraphiQL UI: http://localhost:${env.MCP_PORT}/graphiql`);
|
|
467
469
|
}
|
|
468
|
-
console.error(`🤖 MCP Endpoint: http://localhost:${env.MCP_PORT}/mcp`);
|
|
469
470
|
});
|
|
470
471
|
}
|
|
471
472
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
472
473
|
await server.connect(transport);
|
|
473
|
-
|
|
474
|
-
if (!isInspector) {
|
|
475
|
-
console.error(`[STDIO] MCP Server "${env.NAME}" v${getVersion()} started`);
|
|
476
|
-
}
|
|
477
|
-
getSchema().catch(e => {
|
|
478
|
-
if (!isInspector)
|
|
479
|
-
console.error(`[SCHEMA] Warning: ${e.message}`);
|
|
480
|
-
});
|
|
474
|
+
getSchema().catch(() => { });
|
|
481
475
|
}
|
|
482
476
|
process.on('SIGINT', () => process.exit(0));
|
|
483
477
|
process.on('SIGTERM', () => process.exit(0));
|
package/package.json
CHANGED