@letoribo/mcp-graphql-enhanced 3.9.1 → 3.9.2

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 (2) hide show
  1. package/dist/index.js +91 -52
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10,7 +10,6 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
10
10
  const language_1 = require("graphql/language");
11
11
  const zod_1 = __importDefault(require("zod"));
12
12
  const graphiql_js_1 = require("./helpers/graphiql.js");
13
- const graphql_1 = require("graphql");
14
13
  // Helper imports
15
14
  const deprecation_js_1 = require("./helpers/deprecation.js");
16
15
  const introspection_js_1 = require("./helpers/introspection.js");
@@ -25,7 +24,7 @@ const getVersion = () => {
25
24
  return pkg.version;
26
25
  }
27
26
  catch {
28
- return "3.9.0";
27
+ return "3.9.2";
29
28
  }
30
29
  };
31
30
  (0, deprecation_js_1.checkDeprecatedArguments)();
@@ -128,62 +127,76 @@ async function performUpdate(force) {
128
127
  isUpdating = true;
129
128
  const startTime = Date.now();
130
129
  try {
131
- const endpoints = env.ENDPOINT.split(',').map(url => url.trim());
132
- console.error(`[SYNC] Initializing broadcast to ${endpoints.length} nodes...`);
133
- const results = await Promise.allSettled(endpoints.map(async (url) => {
134
- const response = await fetch(url, {
135
- method: "POST",
136
- headers: { "Content-Type": "application/json", ...env.HEADERS },
137
- body: JSON.stringify({ query: (0, graphql_1.getIntrospectionQuery)() }),
138
- signal: AbortSignal.timeout(10000)
139
- });
140
- if (!response.ok)
141
- throw new Error(`HTTP Error ${response.status}`);
142
- const result = await response.json();
143
- if (!result.data)
144
- throw new Error("Empty introspection data");
145
- const schema = (0, graphql_1.buildClientSchema)(result.data);
146
- schema._originUrl = url;
147
- return { url, schema };
148
- }));
149
- const successful = results
150
- .filter((r) => r.status === 'fulfilled')
151
- .map(r => r.value);
152
- const failures = results
153
- .filter((r) => r.status === 'rejected')
154
- .map(r => r.reason.message);
155
- if (successful.length === 0) {
156
- throw new Error(`Federation failed. All nodes unreachable: ${failures.join(', ')}`);
130
+ const { buildClientSchema, getIntrospectionQuery, printSchema, buildASTSchema, parse: gqlParse, isObjectType } = require("graphql");
131
+ let tempSchemas = [];
132
+ // --- FETCHING LOGIC: LOCAL SDL OR REMOTE BROADCAST ---
133
+ if (env.SCHEMA) {
134
+ let sdl;
135
+ if (env.SCHEMA.startsWith("http")) {
136
+ const response = await fetch(env.SCHEMA);
137
+ if (!response.ok)
138
+ throw new Error(`Remote_SDL_Fetch_Failed: ${response.statusText}`);
139
+ sdl = await response.text();
140
+ }
141
+ else {
142
+ sdl = await (0, introspection_js_1.introspectLocalSchema)(env.SCHEMA);
143
+ }
144
+ tempSchemas = [buildASTSchema(gqlParse(sdl))];
157
145
  }
158
- // Update cache with all discovered schemas
159
- cachedSchemas = successful.map(s => s.schema);
160
- cachedSchemaObject = cachedSchemas[0]; // Baseline schema
161
- cachedSDL = (0, graphql_1.printSchema)(cachedSchemaObject);
162
- // Generate Node Manifest for AI reasoning (Full data, no slices)
163
- global.nodeManifest = successful.map(node => {
164
- const typeMap = node.schema.getTypeMap();
165
- const domainTypes = Object.keys(typeMap).filter(name => {
166
- const type = typeMap[name];
167
- return !name.startsWith('__') && !(0, graphql_1.isScalarType)(type);
168
- });
169
- return {
170
- endpoint: node.url,
171
- availableMutations: Object.keys(node.schema.getMutationType()?.getFields() || {}),
172
- domainEntities: domainTypes
173
- };
146
+ else {
147
+ const endpoints = env.ENDPOINT.split(',').map(url => url.trim());
148
+ const results = await Promise.all(endpoints.map(async (url) => {
149
+ try {
150
+ const response = await fetch(url, {
151
+ method: "POST",
152
+ headers: { "Content-Type": "application/json", ...env.HEADERS },
153
+ body: JSON.stringify({ query: getIntrospectionQuery() }),
154
+ });
155
+ if (!response.ok)
156
+ return null;
157
+ const result = await response.json();
158
+ return result.data ? buildClientSchema(result.data) : null;
159
+ }
160
+ catch (e) {
161
+ console.error(`[SYNC-WARN] Failed to reach ${url}`);
162
+ return null;
163
+ }
164
+ }));
165
+ tempSchemas = results.filter((s) => s !== null);
166
+ }
167
+ if (tempSchemas.length === 0) {
168
+ throw new Error("No valid schemas could be retrieved.");
169
+ }
170
+ // Use the primary schema for the UI/Metadata context
171
+ cachedSchemaObject = tempSchemas[0];
172
+ const currentSDL = printSchema(cachedSchemaObject);
173
+ const typeMap = cachedSchemaObject.getTypeMap();
174
+ const businessTypes = Object.keys(typeMap).filter(typeName => {
175
+ const type = typeMap[typeName];
176
+ return !typeName.startsWith('__') &&
177
+ !['Query', 'Mutation', 'Subscription'].includes(typeName) &&
178
+ isObjectType(type);
174
179
  });
175
- const uniqueTypeCount = new Set(cachedSchemas.flatMap(s => Object.keys(s.getTypeMap()))).size;
176
- const duration = ((Date.now() - startTime) / 1000).toFixed(2);
177
- return `✅ FEDERATION SYNCED: ${successful.length} active nodes, ${uniqueTypeCount} unique types discovered in ${duration}s.`;
180
+ if (currentSDL !== cachedSDL) {
181
+ cachedSDL = currentSDL;
182
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
183
+ const sourceInfo = env.SCHEMA ? 'SDL File' : `${tempSchemas.length} Active Nodes`;
184
+ return [
185
+ `✨ SCHEMA EVOLVED (${duration}s)`,
186
+ `📊 Source: ${sourceInfo}`,
187
+ `🧬 Types: ${businessTypes.length}`,
188
+ `---`,
189
+ `The bridge has updated the graph model.`
190
+ ].join('\n');
191
+ }
192
+ return `✅ Status: Schema stable (${businessTypes.length} types).`;
178
193
  }
179
194
  catch (error) {
180
195
  console.error(`[CRITICAL] Sync failure: ${error.message}`);
181
- schemaLoadError = error;
182
- return `❌ SYNC ERROR: ${error.message}`;
196
+ throw error;
183
197
  }
184
198
  finally {
185
199
  isUpdating = false;
186
- updatePromise = null;
187
200
  }
188
201
  }
189
202
  // --- TOOLS IMPLEMENTATION ---
@@ -337,23 +350,45 @@ async function handleHttpRequest(req, res) {
337
350
  return;
338
351
  }
339
352
  const url = new URL(req.url || '', `http://${req.headers.host}`);
353
+ // Render GraphiQL UI
340
354
  if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/graphiql')) {
341
355
  res.writeHead(200, { 'Content-Type': 'text/html' });
342
356
  return res.end((0, graphiql_js_1.renderGraphiQL)(`http://localhost:${env.MCP_PORT}/mcp`, env.HEADERS));
343
357
  }
358
+ // Process MCP/GraphQL requests
344
359
  if (url.pathname === '/mcp' && req.method === 'POST') {
345
360
  let body = '';
346
361
  req.on('data', chunk => { body += chunk; });
347
362
  req.on('end', async () => {
363
+ let requestId = null;
348
364
  try {
349
365
  const payload = JSON.parse(body);
366
+ // Handle raw GraphQL queries sent directly to /mcp without JSON-RPC structure
367
+ if (!payload.method && payload.query) {
368
+ const handler = toolHandlers.get("query-graphql");
369
+ if (handler) {
370
+ const mcpResult = await handler({
371
+ query: payload.query,
372
+ variables: payload.variables
373
+ });
374
+ const parsed = JSON.parse(mcpResult.content[0].text);
375
+ res.writeHead(200, { 'Content-Type': 'application/json' });
376
+ const graphQLResponse = parsed.data ? parsed : { data: parsed };
377
+ return res.end(JSON.stringify(graphQLResponse));
378
+ }
379
+ }
350
380
  const { method, id, params } = payload;
381
+ requestId = id;
351
382
  const target = (method === "call-tool" || method === "tools/call") ? params.name : method;
352
383
  const args = (method === "call-tool" || method === "tools/call") ? params.arguments : params;
353
384
  const handler = toolHandlers.get(target);
354
385
  if (!handler) {
355
386
  res.writeHead(404);
356
- return res.end(JSON.stringify({ error: { code: -32601, message: "Method not found" } }));
387
+ return res.end(JSON.stringify({
388
+ jsonrpc: '2.0',
389
+ id: requestId,
390
+ error: { code: -32601, message: `Method ${target} not found` }
391
+ }));
357
392
  }
358
393
  const result = await handler(args);
359
394
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -361,7 +396,11 @@ async function handleHttpRequest(req, res) {
361
396
  }
362
397
  catch (e) {
363
398
  res.writeHead(500);
364
- res.end(JSON.stringify({ error: { message: e.message } }));
399
+ res.end(JSON.stringify({
400
+ jsonrpc: '2.0',
401
+ id: requestId,
402
+ error: { message: e.message }
403
+ }));
365
404
  }
366
405
  });
367
406
  return;
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.9.1"
53
+ "version": "3.9.2"
54
54
  }