@letoribo/mcp-graphql-enhanced 3.7.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/index.js +43 -30
- 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.
|
package/dist/index.js
CHANGED
|
@@ -74,7 +74,6 @@ let schemaLoadError = null;
|
|
|
74
74
|
let isUpdating = false;
|
|
75
75
|
let updatePromise = null;
|
|
76
76
|
let lastKnownTypeCount = 0;
|
|
77
|
-
let expectEmptySchema = false; // Intent flag for intentional purges
|
|
78
77
|
/**
|
|
79
78
|
* Smart Hybrid Schema Fetcher (Zero-Error Version)
|
|
80
79
|
* @param force If true, blocks and waits for the new schema evolution.
|
|
@@ -108,15 +107,13 @@ async function getSchema(force = false, requestedTypes) {
|
|
|
108
107
|
updatePromise = performUpdate(force);
|
|
109
108
|
try {
|
|
110
109
|
if (force || !cachedSDL) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else {
|
|
114
|
-
updatePromise.catch(err => console.error("[SCHEMA] Background update failed:", err));
|
|
115
|
-
return cachedSDL;
|
|
110
|
+
await updatePromise; // Wait for update to complete
|
|
111
|
+
return cachedSchemaObject;
|
|
116
112
|
}
|
|
113
|
+
return cachedSchemaObject;
|
|
117
114
|
}
|
|
118
115
|
finally {
|
|
119
|
-
|
|
116
|
+
updatePromise = null;
|
|
120
117
|
}
|
|
121
118
|
}
|
|
122
119
|
/**
|
|
@@ -175,11 +172,12 @@ async function performUpdate(force) {
|
|
|
175
172
|
// Maintain state for "Gap Analysis"
|
|
176
173
|
lastKnownTypeCount = businessTypes.length;
|
|
177
174
|
const currentSDL = printSchema(tempSchema);
|
|
178
|
-
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
179
175
|
// --- CACHE & NOTIFICATION ---
|
|
176
|
+
// Always update the live object if we successfully built it
|
|
177
|
+
cachedSchemaObject = tempSchema;
|
|
180
178
|
if (currentSDL !== cachedSDL) {
|
|
181
179
|
cachedSDL = currentSDL;
|
|
182
|
-
|
|
180
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
183
181
|
return [
|
|
184
182
|
`✨ SCHEMA EVOLVED (${duration}s)`,
|
|
185
183
|
`📊 Source: ${env.SCHEMA ? 'Local/Remote SDL' : 'Live Endpoint'}`,
|
|
@@ -189,6 +187,7 @@ async function performUpdate(force) {
|
|
|
189
187
|
].join('\n');
|
|
190
188
|
}
|
|
191
189
|
else {
|
|
190
|
+
// Even if SDL string is the same, we've ensured cachedSchemaObject is set above
|
|
192
191
|
return `✅ Status: Schema stable (${lastKnownTypeCount} labels).`;
|
|
193
192
|
}
|
|
194
193
|
}
|
|
@@ -270,35 +269,53 @@ toolHandlers.set("query-graphql", queryGraphqlHandler);
|
|
|
270
269
|
headers: zod_1.default.string().optional(),
|
|
271
270
|
}, queryGraphqlHandler);
|
|
272
271
|
// Tool: introspect-schema
|
|
272
|
+
// --- TOOL: introspect-schema ---
|
|
273
273
|
const introspectHandler = async ({ typeNames }) => {
|
|
274
|
-
// 1.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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;
|
|
278
288
|
const typeMap = schema.getTypeMap();
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
// --- 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 ---
|
|
283
292
|
if (typeNames && typeNames.length > 0) {
|
|
284
|
-
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
|
+
});
|
|
285
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}`;
|
|
286
301
|
return {
|
|
287
302
|
content: [{
|
|
288
303
|
type: "text",
|
|
289
304
|
text: `❌ PARTIAL RESULTS [ID: ${schemaVersion}]\n\n` +
|
|
290
|
-
`MISSING
|
|
291
|
-
`REASON:
|
|
292
|
-
`ACTION:
|
|
293
|
-
`
|
|
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(", ")}`
|
|
294
309
|
}]
|
|
295
310
|
};
|
|
296
311
|
}
|
|
297
312
|
}
|
|
313
|
+
// 2. Generate General Manifest
|
|
298
314
|
if (!typeNames || typeNames.length === 0) {
|
|
299
315
|
const queryType = schema.getQueryType();
|
|
300
316
|
const discoveredEntities = new Set();
|
|
301
317
|
if (queryType) {
|
|
318
|
+
// TYPE FIX: Cast fields to Record<string, GraphQLField<any, any>>
|
|
302
319
|
const queryFields = queryType.getFields();
|
|
303
320
|
Object.values(queryFields).forEach((field) => {
|
|
304
321
|
const namedType = (0, graphql_1.getNamedType)(field.type);
|
|
@@ -308,23 +325,19 @@ const introspectHandler = async ({ typeNames }) => {
|
|
|
308
325
|
});
|
|
309
326
|
}
|
|
310
327
|
const coreEntities = Array.from(discoveredEntities).sort();
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
`🔗 Join the discussion: https://discord.com/channels/625400653321076807/1480510460339159184`,
|
|
314
|
-
`---`
|
|
315
|
-
].join('\n');
|
|
328
|
+
const typeKeys = Object.keys(typeMap).filter(t => !t.startsWith('__'));
|
|
329
|
+
const schemaVersion = `v${typeKeys.length}.${Math.floor(Date.now() / 10000) % 1000}`;
|
|
316
330
|
return {
|
|
317
331
|
content: [{
|
|
318
332
|
type: "text",
|
|
319
|
-
text:
|
|
320
|
-
`GraphQL Schema Manifest [ID: ${schemaVersion}]\n\n` +
|
|
333
|
+
text: `GraphQL Schema Manifest [ID: ${schemaVersion}]\n\n` +
|
|
321
334
|
`ENTRY_POINT_ENTITIES: ${coreEntities.join(", ") || "None"}\n` +
|
|
322
335
|
`TOTAL_SCHEMA_TYPES: ${typeKeys.length}\n\n` +
|
|
323
336
|
`ALL_AVAILABLE_TYPES: ${typeKeys.join(", ")}`
|
|
324
337
|
}]
|
|
325
338
|
};
|
|
326
339
|
}
|
|
327
|
-
// 3. Detailed introspection
|
|
340
|
+
// 3. Detailed introspection
|
|
328
341
|
const filtered = (0, introspection_js_1.introspectSpecificTypes)(schema, typeNames);
|
|
329
342
|
return {
|
|
330
343
|
content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }]
|
package/package.json
CHANGED