@letoribo/mcp-graphql-enhanced 3.8.0 → 3.8.1

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.
@@ -8,8 +8,9 @@ export declare function introspectEndpoint(endpoint: string, headers?: Record<st
8
8
  */
9
9
  export declare function introspectLocalSchema(path: string): Promise<string>;
10
10
  /**
11
- * Extract and filter specific types from a schema object.
12
- * Prevents "No result received" errors by only sending requested parts of the graph.
11
+ * Extract and filter specific types or root fields from a GraphQL schema.
12
+ * This prevents "No result received" errors by only sending the requested
13
+ * parts of the graph to the agent, maintaining a stable context window.
13
14
  */
14
15
  export declare function introspectSpecificTypes(schema: GraphQLSchema, typeNames: string[]): Record<string, any>;
15
16
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../../src/helpers/introspection.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,aAAa,EAQd,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,mBAiBjC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,mBAEvD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,uBAmEjF;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAK,EACpC,SAAS,EAAE,MAAM,EAAE,mBAYpB"}
1
+ {"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../../src/helpers/introspection.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,aAAa,EAQd,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,mBAiBjC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,mBAEvD;AAED;;;;GAIG;AAEH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,uBAgGjF;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,YAAK,EACpC,SAAS,EAAE,MAAM,EAAE,mBAYpB"}
@@ -31,12 +31,39 @@ async function introspectLocalSchema(path) {
31
31
  return await (0, promises_1.readFile)(path, "utf8");
32
32
  }
33
33
  /**
34
- * Extract and filter specific types from a schema object.
35
- * Prevents "No result received" errors by only sending requested parts of the graph.
34
+ * Extract and filter specific types or root fields from a GraphQL schema.
35
+ * This prevents "No result received" errors by only sending the requested
36
+ * parts of the graph to the agent, maintaining a stable context window.
36
37
  */
37
38
  function introspectSpecificTypes(schema, typeNames) {
38
39
  const result = {};
40
+ // Cache root field maps to avoid repeated lookups during the loop
41
+ const queryType = schema.getQueryType();
42
+ const queryFields = queryType ? queryType.getFields() : {};
43
+ const mutationType = schema.getMutationType();
44
+ const mutationFields = mutationType ? mutationType.getFields() : {};
39
45
  for (const name of typeNames) {
46
+ // --- ROOT FIELD RESOLUTION ---
47
+ // Check if the name refers to a root field (Query or Mutation)
48
+ // rather than a named Type.
49
+ const rootField = queryFields[name] || mutationFields[name];
50
+ if (rootField) {
51
+ result[name] = {
52
+ kind: queryFields[name] ? "QUERY_FIELD" : "MUTATION_FIELD",
53
+ description: rootField.description,
54
+ type: rootField.type.toString(),
55
+ args: rootField.args
56
+ .filter(arg => !arg.deprecationReason)
57
+ .map(arg => ({
58
+ name: arg.name,
59
+ type: arg.type.toString(),
60
+ description: arg.description,
61
+ }))
62
+ };
63
+ continue; // Field found, move to next requested name
64
+ }
65
+ // --- NAMED TYPE RESOLUTION ---
66
+ // Fallback to standard type introspection if no root field matches
40
67
  const type = schema.getType(name);
41
68
  if (!type)
42
69
  continue;
package/dist/index.js CHANGED
@@ -268,76 +268,89 @@ toolHandlers.set("query-graphql", queryGraphqlHandler);
268
268
  variables: zod_1.default.string().optional(),
269
269
  headers: zod_1.default.string().optional(),
270
270
  }, queryGraphqlHandler);
271
- // Tool: introspect-schema
272
- // --- TOOL: introspect-schema ---
271
+ /**
272
+ * Tool: introspect-schema
273
+ * Main handler for the introspection tool.
274
+ * Implements "Agent Recovery" logic to guide the LLM when entities are missing.
275
+ */
273
276
  const introspectHandler = async ({ typeNames }) => {
274
- // 1. Fetch the schema directly
277
+ // 1. Fetch the schema directly from the source
275
278
  const result = await getSchema(true);
276
- // Explicitly check if the result is an error string or null
279
+ // Explicitly check if the result is a valid GraphQLSchema object
277
280
  if (!result || typeof result === 'string') {
278
281
  return {
279
282
  content: [{
280
283
  type: "text",
281
284
  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.`
285
+ `ACTION: Please wait 5-10 seconds for the backend endpoint to respond.`
283
286
  }]
284
287
  };
285
288
  }
286
- // TYPE FIX: Cast to GraphQLSchema so TS knows methods like getTypeMap exist
289
+ // --- 1. INITIALIZE MAPPINGS ---
287
290
  const schema = result;
288
291
  const typeMap = schema.getTypeMap();
292
+ // Cache Root Type fields for rapid gap analysis
293
+ const queryType = schema.getQueryType();
294
+ const queryFields = queryType ? queryType.getFields() : {};
289
295
  const mutationType = schema.getMutationType();
290
296
  const mutationFields = mutationType ? mutationType.getFields() : {};
291
- // --- GAP ANALYSIS ---
297
+ // --- 2. GAP ANALYSIS & SELF-HEALING LOOP ---
298
+ // If specific types were requested, verify their existence in the current schema
292
299
  if (typeNames && typeNames.length > 0) {
293
300
  const missing = typeNames.filter(name => {
294
301
  const existsAsType = !!typeMap[name];
295
302
  const existsAsMutation = !!mutationFields[name];
296
- return !existsAsType && !existsAsMutation;
303
+ const existsAsQueryField = !!queryFields[name];
304
+ return !existsAsType && !existsAsMutation && !existsAsQueryField;
297
305
  });
306
+ // If some requested entities are missing, provide the agent with a recovery map
298
307
  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}`;
308
+ // Filter out internal GraphQL types to reduce noise for the agent
309
+ const internalTypes = ['Query', 'Mutation', 'Subscription'];
310
+ const availableEntities = Object.keys(typeMap).filter(t => !t.startsWith('__') && !internalTypes.includes(t));
311
+ // Generate a pseudo-version ID based on schema state and time
312
+ const schemaVersion = `v${availableEntities.length}.${Math.floor(Date.now() / 10000) % 1000}`;
301
313
  return {
302
314
  content: [{
303
315
  type: "text",
304
- text: `❌ PARTIAL RESULTS [ID: ${schemaVersion}]\n\n` +
316
+ text: `❌ PARTIAL RESULTS [Schema ID: ${schemaVersion}]\n\n` +
305
317
  `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
+ `REASON: These specific types or fields were not found in the current schema.\n` +
319
+ `ACTION: Re-examine the available entities below and correct your query intent.\n\n` +
320
+ `AVAILABLE_ENTITIES: ${availableEntities.join(", ")}`
309
321
  }]
310
322
  };
311
323
  }
312
324
  }
313
- // 2. Generate General Manifest
325
+ // --- 3. GENERAL MANIFEST GENERATION ---
326
+ // If no typeNames provided, return a high-level overview of the entry points
314
327
  if (!typeNames || typeNames.length === 0) {
315
- const queryType = schema.getQueryType();
316
328
  const discoveredEntities = new Set();
317
329
  if (queryType) {
318
- // TYPE FIX: Cast fields to Record<string, GraphQLField<any, any>>
319
- const queryFields = queryType.getFields();
320
- Object.values(queryFields).forEach((field) => {
330
+ // Map Query fields to their underlying Object Types
331
+ const fields = queryType.getFields();
332
+ Object.values(fields).forEach((field) => {
321
333
  const namedType = (0, graphql_1.getNamedType)(field.type);
322
334
  if ((0, graphql_1.isObjectType)(namedType) && !namedType.name.startsWith('__')) {
323
335
  discoveredEntities.add(namedType.name);
324
336
  }
325
337
  });
326
338
  }
327
- const coreEntities = Array.from(discoveredEntities).sort();
328
- const typeKeys = Object.keys(typeMap).filter(t => !t.startsWith('__'));
329
- const schemaVersion = `v${typeKeys.length}.${Math.floor(Date.now() / 10000) % 1000}`;
339
+ const entryPoints = Array.from(discoveredEntities).sort();
340
+ const allTypes = Object.keys(typeMap).filter(t => !t.startsWith('__'));
341
+ const schemaVersion = `v${allTypes.length}.${Math.floor(Date.now() / 10000) % 1000}`;
330
342
  return {
331
343
  content: [{
332
344
  type: "text",
333
345
  text: `GraphQL Schema Manifest [ID: ${schemaVersion}]\n\n` +
334
- `ENTRY_POINT_ENTITIES: ${coreEntities.join(", ") || "None"}\n` +
335
- `TOTAL_SCHEMA_TYPES: ${typeKeys.length}\n\n` +
336
- `ALL_AVAILABLE_TYPES: ${typeKeys.join(", ")}`
346
+ `ENTRY_POINT_ENTITIES: ${entryPoints.join(", ") || "None"}\n` +
347
+ `TOTAL_SCHEMA_TYPES: ${allTypes.length}\n\n` +
348
+ `ALL_AVAILABLE_TYPES: ${allTypes.join(", ")}`
337
349
  }]
338
350
  };
339
351
  }
340
- // 3. Detailed introspection
352
+ // --- 4. DETAILED INTROSPECTION ---
353
+ // Return filtered schema metadata for the requested types or root fields
341
354
  const filtered = (0, introspection_js_1.introspectSpecificTypes)(schema, typeNames);
342
355
  return {
343
356
  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.8.0"
53
+ "version": "3.8.1"
54
54
  }