@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.
Files changed (3) hide show
  1. package/LICENSE +3 -18
  2. package/dist/index.js +43 -30
  3. package/package.json +1 -1
package/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Boris Besemer (Original Work)
4
- Copyright (c) 2025-2026 [letoribo] (Enhanced Architecture, NPM Refactoring & Discord Graph Infrastructure)
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
- return await updatePromise;
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
- // Promise reference is cleared inside performUpdate's 'finally' block
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
- cachedSchemaObject = tempSchema; // Store the live Schema object for tool execution
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. Always pull the latest schema state
275
- // The report from performUpdate is captured to show evolution details
276
- const evolutionSummary = await getSchema(true);
277
- const schema = cachedSchemaObject;
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
- // 2. Generate a structural fingerprint
280
- const typeKeys = Object.keys(typeMap).filter(t => !t.startsWith('__'));
281
- const schemaVersion = `v${typeKeys.length}.${Math.floor(Date.now() / 10000) % 1000}`;
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 => !typeMap[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 TYPES: ${missing.join(", ")}\n` +
291
- `REASON: The database has been updated, but the GraphQL Schema is still regenerating these specific types.\n` +
292
- `ACTION: Please wait 3 seconds and retry 'introspect-schema' to see the full graph.\n\n` +
293
- `CURRENTLY AVAILABLE: ${typeKeys.filter(t => !['Query', 'Mutation', 'Report'].includes(t)).join(", ")}`
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 communityHeader = [
312
- `🌐 Community Channel: #mcp-graphql-enhanced`,
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: `${communityHeader}\n${evolutionSummary}\n\n` + // Include the report from performUpdate
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 for specific types
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
@@ -50,5 +50,5 @@
50
50
  "tsx": "^4.21.0",
51
51
  "typescript": "5.8.3"
52
52
  },
53
- "version": "3.7.0"
53
+ "version": "3.8.0"
54
54
  }