@supermodeltools/mcp-server 0.4.2 → 0.4.4

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.
@@ -2,8 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handler = exports.tool = exports.metadata = void 0;
4
4
  const promises_1 = require("fs/promises");
5
+ const child_process_1 = require("child_process");
6
+ const crypto_1 = require("crypto");
7
+ const crypto_2 = require("crypto");
8
+ const path_1 = require("path");
5
9
  const types_1 = require("../types");
6
10
  const filtering_1 = require("../filtering");
11
+ const queries_1 = require("../queries");
12
+ const zip_repository_1 = require("../utils/zip-repository");
7
13
  exports.metadata = {
8
14
  resource: 'graphs',
9
15
  operation: 'write',
@@ -13,56 +19,522 @@ exports.metadata = {
13
19
  operationId: 'generateSupermodelGraph',
14
20
  };
15
21
  exports.tool = {
16
- name: 'analyze_codebase',
17
- description: "Analyze code structure, dependencies, and relationships across a repository.\n\nUSE THIS TOOL WHEN:\n- Exploring an unfamiliar codebase to understand its architecture\n- Planning refactorings or assessing change impact across multiple files\n- Finding dependencies between modules, functions, or classes\n- Understanding call relationships and code flow patterns\n- Mapping domain models and system boundaries\n- Investigating how components interact before making changes\n\nPROVIDES:\nComprehensive code graphs including:\n- Module and package dependency relationships\n- Function-level call hierarchies\n- Domain classifications and architectural patterns\n- AST-level structural relationships\n- Summary statistics (file counts, languages, complexity)\n\nREQUIRES:\n- file: Path to a ZIP archive of the repository (use `git archive -o /tmp/repo.zip HEAD` for git repos)\n- Idempotency-Key: Cache key in format {repo}:{type}:{hash} (e.g., myproject:supermodel:abc123)\n- jq_filter (optional but recommended): Filter to extract specific data and reduce response size\n\nALWAYS use jq_filter to extract only the data you need. The full response can be very large.\n\nExample filters:\n- '.graph.nodes[] | select(.type==\"function\")' - Extract only function nodes\n- '.summary' - Get just the summary statistics\n- '.graph.relationships[] | select(.type==\"calls\")' - Extract call relationships",
22
+ name: 'explore_codebase',
23
+ description: `Analyzes code within the target directory to produce a graph that can be used to navigate the codebase when solving bugs, planning or analyzing code changes.
24
+
25
+ ## Example Output
26
+
27
+ This is actual output from running explore_codebase on its own repository (19 TypeScript files, ~60KB).
28
+
29
+ The graph structure shows 163 nodes (Functions, Classes, Types, Files, Domains) and 254 relationships between them. Below is an excerpt showing the data structure:
30
+
31
+ \`\`\`json
32
+ {
33
+ "repo": "1c740c9c4f5c9528e244ab144488214341f959231f73009a46a74a1f11350c3c",
34
+ "version": "sir-2026-01-15",
35
+ "schemaVersion": "1.2.0",
36
+ "generatedAt": "2026-01-15T18:17:10.067Z",
37
+ "summary": {
38
+ "filesProcessed": 19,
39
+ "types": 28,
40
+ "functions": 52,
41
+ "repoSizeBytes": 61058,
42
+ "classes": 2,
43
+ "domains": 6,
44
+ "primaryLanguage": "json"
45
+ },
46
+ "graph": {
47
+ "nodeCount": 163,
48
+ "relationshipCount": 254,
49
+ "sampleNodes": [
50
+ {
51
+ "id": "0965aff4:42ff:df74:ae01:0d17d0886720",
52
+ "labels": ["ExternalModule"],
53
+ "properties": {
54
+ "name": "mcp.js"
55
+ }
56
+ },
57
+ {
58
+ "id": "ab32efae:3825:dada:a4b5:95e95dbb71cc",
59
+ "labels": ["Function"],
60
+ "properties": {
61
+ "name": "getNode",
62
+ "filePath": "src/queries/discovery.ts",
63
+ "language": "typescript",
64
+ "startLine": 25,
65
+ "endLine": 57,
66
+ "kind": "function"
67
+ }
68
+ },
69
+ {
70
+ "id": "8648e520:0be3:b754:77ce:c19dcebf6d6f",
71
+ "labels": ["File"],
72
+ "properties": {
73
+ "name": "test-full-graph.js",
74
+ "filePath": "test-full-graph.js",
75
+ "path": "test-full-graph.js",
76
+ "language": "javascript"
77
+ }
78
+ }
79
+ ],
80
+ "sampleRelationships": [
81
+ {
82
+ "id": "b161d717:5ee5:b827:cc26:f071f7a9648d->ff2a17f0:c2b6:f518:dcb8:91584b241c0f:CHILD_DIRECTORY",
83
+ "type": "CHILD_DIRECTORY",
84
+ "startNode": "b161d717:5ee5:b827:cc26:f071f7a9648d",
85
+ "endNode": "ff2a17f0:c2b6:f518:dcb8:91584b241c0f",
86
+ "properties": {}
87
+ },
88
+ {
89
+ "id": "ff2a17f0:c2b6:f518:dcb8:91584b241c0f->7f19b034:9fee:67c7:7b42:c7ba738d9ceb:CONTAINS_FILE",
90
+ "type": "CONTAINS_FILE",
91
+ "startNode": "ff2a17f0:c2b6:f518:dcb8:91584b241c0f",
92
+ "endNode": "7f19b034:9fee:67c7:7b42:c7ba738d9ceb",
93
+ "properties": {}
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ \`\`\`
99
+
100
+ The graph contains nodes with properties like filePath, startLine, endLine for functions, and relationships like CHILD_DIRECTORY, CONTAINS_FILE, calls, IMPORTS that connect code entities.
101
+
102
+ Query types available: graph_status, summary, get_node, search, list_nodes, function_calls_in, function_calls_out, definitions_in_file, file_imports, domain_map, domain_membership, neighborhood, jq
103
+ `,
18
104
  inputSchema: {
19
105
  type: 'object',
20
106
  properties: {
21
- file: {
107
+ directory: {
22
108
  type: 'string',
23
- description: 'Path to the zipped repository archive containing the code to analyze.',
109
+ description: 'Path to the repository directory to analyze. Can be a subdirectory for faster analysis and smaller graph size (e.g., "/repo/src/core" instead of "/repo").',
24
110
  },
25
111
  'Idempotency-Key': {
26
112
  type: 'string',
27
- description: 'Cache key in format {repo}:{type}:{hash}. Generate hash with: git rev-parse --short HEAD',
113
+ description: 'Optional cache key in format {repo}:{type}:{hash}. If not provided, will be auto-generated using git commit hash or random UUID. Provide a previously used idempotency key to fetch a cached response, for example with a different filter.',
114
+ },
115
+ query: {
116
+ type: 'string',
117
+ enum: [
118
+ 'graph_status', 'summary', 'get_node', 'search', 'list_nodes',
119
+ 'function_calls_in', 'function_calls_out', 'definitions_in_file',
120
+ 'file_imports', 'domain_map', 'domain_membership', 'neighborhood', 'jq'
121
+ ],
122
+ description: 'Query type to execute. Use graph_status first to check cache, then summary to load.',
123
+ },
124
+ targetId: {
125
+ type: 'string',
126
+ description: 'Node ID for queries that operate on a specific node (get_node, function_calls_*, etc.)',
127
+ },
128
+ searchText: {
129
+ type: 'string',
130
+ description: 'Search text for name substring matching (search, domain_membership)',
131
+ },
132
+ namePattern: {
133
+ type: 'string',
134
+ description: 'Regex pattern for name matching (list_nodes)',
135
+ },
136
+ filePathPrefix: {
137
+ type: 'string',
138
+ description: 'Filter by file path prefix (list_nodes, definitions_in_file, search)',
139
+ },
140
+ labels: {
141
+ type: 'array',
142
+ items: { type: 'string' },
143
+ description: 'Filter by node labels: Function, Class, Type, File, Domain, etc. (list_nodes, search)',
144
+ },
145
+ depth: {
146
+ type: 'number',
147
+ description: 'Traversal depth for neighborhood query (default 1, max 3)',
148
+ },
149
+ relationshipTypes: {
150
+ type: 'array',
151
+ items: { type: 'string' },
152
+ description: 'Relationship types to traverse (neighborhood). Options: calls, IMPORTS',
153
+ },
154
+ limit: {
155
+ type: 'number',
156
+ description: 'Max results to return (default 200)',
157
+ },
158
+ includeRaw: {
159
+ type: 'boolean',
160
+ description: 'Include full raw node data in get_node response (default false)',
28
161
  },
29
162
  jq_filter: {
30
163
  type: 'string',
31
164
  title: 'jq Filter',
32
- description: 'A jq filter to extract specific fields from the response. STRONGLY RECOMMENDED to reduce response size.',
165
+ description: 'Raw jq filter for escape hatch queries or legacy mode (when query param not specified)',
33
166
  },
34
167
  },
35
- required: ['file', 'Idempotency-Key'],
168
+ required: ['directory'],
36
169
  },
37
170
  };
171
+ /**
172
+ * Generate an idempotency key in format {repo}:supermodel:{hash}
173
+ * Tries to use git commit hash, falls back to UUID-based hash
174
+ */
175
+ function generateIdempotencyKey(directory) {
176
+ const repoName = (0, path_1.basename)(directory);
177
+ let hash;
178
+ let statusHash = '';
179
+ try {
180
+ // Try to get git commit hash
181
+ hash = (0, child_process_1.execSync)('git rev-parse --short HEAD', {
182
+ cwd: directory,
183
+ encoding: 'utf-8',
184
+ stdio: ['pipe', 'pipe', 'pipe']
185
+ }).trim();
186
+ try {
187
+ // Get git status to detect uncommitted changes
188
+ const statusOutput = (0, child_process_1.execSync)('git status --porcelain', {
189
+ cwd: directory,
190
+ encoding: 'utf-8',
191
+ stdio: ['pipe', 'pipe', 'pipe']
192
+ }).trim();
193
+ statusHash = (0, crypto_2.createHash)('sha1').update(statusOutput || 'clean').digest('hex').substring(0, 7);
194
+ console.error('[DEBUG] Generated idempotency key using git hash:', hash, 'and status hash:', statusHash);
195
+ }
196
+ catch (statusError) {
197
+ // If git status fails, just use commit hash
198
+ console.error('[DEBUG] Generated idempotency key using git hash:', hash, '(git status unavailable)');
199
+ }
200
+ }
201
+ catch (error) {
202
+ // Git not available or not a git repo, use UUID-based hash
203
+ const uuid = (0, crypto_1.randomUUID)();
204
+ // Hash like git does (SHA-1) and take first 7 characters
205
+ hash = (0, crypto_2.createHash)('sha1').update(uuid).digest('hex').substring(0, 7);
206
+ console.error('[DEBUG] Generated idempotency key using random UUID hash:', hash);
207
+ }
208
+ return statusHash ? `${repoName}:supermodel:${hash}-${statusHash}` : `${repoName}:supermodel:${hash}`;
209
+ }
38
210
  const handler = async (client, args) => {
39
211
  if (!args) {
40
212
  return (0, types_1.asErrorResult)('No arguments provided');
41
213
  }
42
- const { jq_filter, file, 'Idempotency-Key': idempotencyKey } = args;
43
- if (!file || typeof file !== 'string') {
44
- return (0, types_1.asErrorResult)('File argument is required and must be a string path');
214
+ const { jq_filter, directory, 'Idempotency-Key': providedIdempotencyKey, query, targetId, searchText, namePattern, filePathPrefix, labels, depth, relationshipTypes, limit, includeRaw, } = args;
215
+ // Validate directory
216
+ if (!directory || typeof directory !== 'string') {
217
+ return (0, types_1.asErrorResult)('Directory argument is required and must be a string path');
218
+ }
219
+ // Generate or validate idempotency key
220
+ let idempotencyKey;
221
+ let keyGenerated = false;
222
+ if (!providedIdempotencyKey || typeof providedIdempotencyKey !== 'string') {
223
+ idempotencyKey = generateIdempotencyKey(directory);
224
+ keyGenerated = true;
225
+ console.error('[DEBUG] Auto-generated idempotency key:', idempotencyKey);
45
226
  }
46
- if (!idempotencyKey || typeof idempotencyKey !== 'string') {
47
- return (0, types_1.asErrorResult)('Idempotency-Key argument is required');
227
+ else {
228
+ idempotencyKey = providedIdempotencyKey;
229
+ console.error('[DEBUG] Using provided idempotency key:', idempotencyKey);
48
230
  }
231
+ // Check if we can skip zipping (graph already cached)
232
+ // Use get() atomically to avoid TOCTOU race condition
233
+ const cachedGraph = queries_1.graphCache.get(idempotencyKey);
234
+ if (cachedGraph && query) {
235
+ console.error('[DEBUG] Graph cached, skipping ZIP creation');
236
+ // Execute query directly from cache using the cached graph
237
+ // We pass the cached graph to executeQuery so it doesn't need to look it up again
238
+ const result = await handleQueryModeWithCache(client, {
239
+ query: query,
240
+ idempotencyKey,
241
+ cachedGraph,
242
+ targetId,
243
+ searchText,
244
+ namePattern,
245
+ filePathPrefix,
246
+ labels,
247
+ depth,
248
+ relationshipTypes,
249
+ limit,
250
+ includeRaw,
251
+ jq_filter,
252
+ });
253
+ // Add metadata about cache hit
254
+ if (keyGenerated && result.content?.[0]?.type === 'text') {
255
+ const originalText = result.content[0].text;
256
+ let responseData;
257
+ try {
258
+ responseData = JSON.parse(originalText);
259
+ // Add metadata about auto-generated key
260
+ responseData._metadata = {
261
+ ...responseData._metadata,
262
+ idempotencyKey,
263
+ idempotencyKeyGenerated: true
264
+ };
265
+ result.content[0].text = JSON.stringify(responseData, null, 2);
266
+ }
267
+ catch {
268
+ // Not JSON, prepend key info as text
269
+ result.content[0].text = `[Auto-generated Idempotency-Key: ${idempotencyKey}]\n\n${originalText}`;
270
+ }
271
+ }
272
+ return result;
273
+ }
274
+ console.error('[DEBUG] Auto-zipping directory:', directory);
275
+ // Handle auto-zipping
276
+ let zipPath;
277
+ let cleanup = null;
49
278
  try {
50
- // Read the file into a Buffer and convert to Blob
51
- // The SDK expects a Blob type, not a stream
52
- console.error('[DEBUG] Reading file:', file);
53
- const fileBuffer = await (0, promises_1.readFile)(file);
54
- // Create a Blob from the buffer
55
- // In Node.js 18+, Blob is available globally
56
- const fileBlob = new Blob([fileBuffer], { type: 'application/zip' });
57
- console.error('[DEBUG] File size:', fileBuffer.length, 'bytes');
58
- console.error('[DEBUG] Making API request with idempotency key:', idempotencyKey);
59
- // Construct the request object
60
- const requestParams = {
61
- file: fileBlob,
62
- idempotencyKey: idempotencyKey
279
+ const zipResult = await (0, zip_repository_1.zipRepository)(directory);
280
+ zipPath = zipResult.path;
281
+ cleanup = zipResult.cleanup;
282
+ console.error('[DEBUG] Auto-zip complete:', zipResult.fileCount, 'files,', formatBytes(zipResult.sizeBytes));
283
+ }
284
+ catch (error) {
285
+ console.error('[ERROR] Auto-zip failed:', error.message);
286
+ // Provide helpful error messages
287
+ if (error.message.includes('does not exist')) {
288
+ return (0, types_1.asErrorResult)(`Directory does not exist: ${directory}`);
289
+ }
290
+ if (error.message.includes('Permission denied')) {
291
+ return (0, types_1.asErrorResult)(`Permission denied accessing directory: ${directory}`);
292
+ }
293
+ if (error.message.includes('exceeds limit')) {
294
+ return (0, types_1.asErrorResult)(error.message);
295
+ }
296
+ if (error.message.includes('ENOSPC')) {
297
+ return (0, types_1.asErrorResult)('Insufficient disk space to create ZIP archive');
298
+ }
299
+ return (0, types_1.asErrorResult)(`Failed to create ZIP archive: ${error.message}`);
300
+ }
301
+ // Execute query with cleanup handling
302
+ try {
303
+ let result;
304
+ // If query param is specified, use the new query engine
305
+ if (query) {
306
+ result = await handleQueryMode(client, {
307
+ query: query,
308
+ file: zipPath,
309
+ idempotencyKey,
310
+ targetId,
311
+ searchText,
312
+ namePattern,
313
+ filePathPrefix,
314
+ labels,
315
+ depth,
316
+ relationshipTypes,
317
+ limit,
318
+ includeRaw,
319
+ jq_filter,
320
+ });
321
+ }
322
+ else {
323
+ // Legacy mode: use jq_filter directly on API response
324
+ result = await handleLegacyMode(client, zipPath, idempotencyKey, jq_filter);
325
+ }
326
+ // If key was auto-generated, add it to the response
327
+ if (keyGenerated && result.content && result.content[0]?.type === 'text') {
328
+ const originalText = result.content[0].text;
329
+ let responseData;
330
+ try {
331
+ responseData = JSON.parse(originalText);
332
+ // Add metadata about auto-generated key
333
+ responseData._metadata = {
334
+ ...responseData._metadata,
335
+ idempotencyKey,
336
+ idempotencyKeyGenerated: true
337
+ };
338
+ result.content[0].text = JSON.stringify(responseData, null, 2);
339
+ }
340
+ catch {
341
+ // Not JSON, prepend key info as text
342
+ result.content[0].text = `[Auto-generated Idempotency-Key: ${idempotencyKey}]\n\n${originalText}`;
343
+ }
344
+ }
345
+ return result;
346
+ }
347
+ finally {
348
+ // Always cleanup temp ZIP files
349
+ if (cleanup) {
350
+ await cleanup();
351
+ }
352
+ }
353
+ };
354
+ exports.handler = handler;
355
+ /**
356
+ * Format bytes as human-readable string
357
+ */
358
+ function formatBytes(bytes) {
359
+ if (bytes < 1024)
360
+ return `${bytes} B`;
361
+ if (bytes < 1024 * 1024)
362
+ return `${(bytes / 1024).toFixed(2)} KB`;
363
+ if (bytes < 1024 * 1024 * 1024)
364
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
365
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
366
+ }
367
+ /**
368
+ * Handle query-based requests when graph is already cached
369
+ * Uses the cached graph directly to avoid TOCTOU issues
370
+ */
371
+ async function handleQueryModeWithCache(client, params) {
372
+ const queryParams = {
373
+ query: params.query,
374
+ file: '', // Not used when we have cached graph
375
+ idempotencyKey: params.idempotencyKey,
376
+ targetId: params.targetId,
377
+ searchText: params.searchText,
378
+ namePattern: params.namePattern,
379
+ filePathPrefix: params.filePathPrefix,
380
+ labels: params.labels,
381
+ depth: params.depth,
382
+ relationshipTypes: params.relationshipTypes,
383
+ limit: params.limit,
384
+ includeRaw: params.includeRaw,
385
+ jq_filter: params.jq_filter,
386
+ };
387
+ // Execute query with the cached graph's raw data
388
+ // This handles the edge case where cache is evicted between our check and query execution
389
+ // by passing the raw API response so executeQuery can rebuild indexes if needed
390
+ let result = await (0, queries_1.executeQuery)(queryParams, params.cachedGraph.raw);
391
+ // Handle query errors
392
+ if ((0, queries_1.isQueryError)(result)) {
393
+ const errorWithHints = {
394
+ ...result,
395
+ hints: getErrorHints(result.error.code, params.query),
396
+ };
397
+ return (0, types_1.asTextContentResult)(errorWithHints);
398
+ }
399
+ // Add breadcrumb hints to successful results
400
+ const resultWithHints = addBreadcrumbHints(result, params.query);
401
+ return (0, types_1.asTextContentResult)(resultWithHints);
402
+ }
403
+ /**
404
+ * Handle query-based requests using the query engine
405
+ */
406
+ async function handleQueryMode(client, params) {
407
+ const queryParams = {
408
+ query: params.query,
409
+ file: params.file,
410
+ idempotencyKey: params.idempotencyKey,
411
+ targetId: params.targetId,
412
+ searchText: params.searchText,
413
+ namePattern: params.namePattern,
414
+ filePathPrefix: params.filePathPrefix,
415
+ labels: params.labels,
416
+ depth: params.depth,
417
+ relationshipTypes: params.relationshipTypes,
418
+ limit: params.limit,
419
+ includeRaw: params.includeRaw,
420
+ jq_filter: params.jq_filter,
421
+ };
422
+ // First, try to execute query from cache
423
+ let result = await (0, queries_1.executeQuery)(queryParams);
424
+ // If cache miss, fetch from API and retry
425
+ if ((0, queries_1.isQueryError)(result) && result.error.code === 'CACHE_MISS') {
426
+ console.error('[DEBUG] Cache miss, fetching from API...');
427
+ try {
428
+ const apiResponse = await fetchFromApi(client, params.file, params.idempotencyKey);
429
+ result = await (0, queries_1.executeQuery)(queryParams, apiResponse);
430
+ }
431
+ catch (error) {
432
+ return (0, types_1.asErrorResult)(`API call failed: ${error.message || String(error)}`);
433
+ }
434
+ }
435
+ // Handle query errors
436
+ if ((0, queries_1.isQueryError)(result)) {
437
+ // Include hints for common errors
438
+ const errorWithHints = {
439
+ ...result,
440
+ hints: getErrorHints(result.error.code, params.query),
63
441
  };
64
- const response = await client.graphs.generateSupermodelGraph(requestParams);
65
- console.error('[DEBUG] API request successful');
442
+ return (0, types_1.asTextContentResult)(errorWithHints);
443
+ }
444
+ // Add breadcrumb hints to successful results
445
+ const resultWithHints = addBreadcrumbHints(result, params.query);
446
+ return (0, types_1.asTextContentResult)(resultWithHints);
447
+ }
448
+ /**
449
+ * Add breadcrumb hints to query results for agent navigation
450
+ */
451
+ function addBreadcrumbHints(result, queryType) {
452
+ const hints = [];
453
+ switch (queryType) {
454
+ case 'summary':
455
+ hints.push('NEXT: Use search with searchText to find specific functions/classes', 'NEXT: Use list_nodes with labels=["Function"] to browse all functions', 'NEXT: Use domain_map to see architectural domains');
456
+ break;
457
+ case 'search':
458
+ case 'list_nodes':
459
+ if (result.result?.nodes?.length > 0) {
460
+ hints.push('NEXT: Use get_node with targetId to get full details for any node', 'NEXT: Use function_calls_in with targetId to see who calls a function', 'NEXT: Use function_calls_out with targetId to see what a function calls');
461
+ }
462
+ break;
463
+ case 'get_node':
464
+ if (result.result?.node) {
465
+ const label = result.result.node.labels?.[0];
466
+ if (label === 'Function') {
467
+ hints.push('NEXT: Use function_calls_in to see callers of this function', 'NEXT: Use function_calls_out to see functions this calls', 'NEXT: Use neighborhood with depth=2 to see call graph around this function');
468
+ }
469
+ else if (label === 'File') {
470
+ hints.push('NEXT: Use definitions_in_file to see all definitions in this file', 'NEXT: Use file_imports to see import relationships');
471
+ }
472
+ else if (label === 'Domain') {
473
+ hints.push('NEXT: Use domain_membership to see all members of this domain');
474
+ }
475
+ }
476
+ break;
477
+ case 'function_calls_in':
478
+ case 'function_calls_out':
479
+ if (result.result?.nodes?.length > 0) {
480
+ hints.push('NEXT: Use get_node with any caller/callee ID for full details', 'NEXT: Chain function_calls_in/out to trace deeper call paths', 'NEXT: Use neighborhood for broader call graph exploration');
481
+ }
482
+ break;
483
+ case 'definitions_in_file':
484
+ hints.push('NEXT: Use function_calls_in/out on any function ID to trace calls', 'NEXT: Use get_node for full details on any definition');
485
+ break;
486
+ case 'domain_map':
487
+ hints.push('NEXT: Use domain_membership with domain name to see members', 'NEXT: Use search to find specific functions within a domain');
488
+ break;
489
+ }
490
+ if (hints.length > 0) {
491
+ return { ...result, hints };
492
+ }
493
+ return result;
494
+ }
495
+ /**
496
+ * Get hints for specific error conditions
497
+ */
498
+ function getErrorHints(errorCode, queryType) {
499
+ switch (errorCode) {
500
+ case 'NOT_FOUND':
501
+ return [
502
+ 'Use search with searchText to find nodes by name',
503
+ 'Use list_nodes with labels filter to browse available nodes',
504
+ 'Check the targetId format - it should be the full node ID from a previous query'
505
+ ];
506
+ case 'INVALID_PARAMS':
507
+ return [
508
+ `Query '${queryType}' may require specific parameters`,
509
+ 'Use graph_status to see available query types and their requirements'
510
+ ];
511
+ default:
512
+ return [];
513
+ }
514
+ }
515
+ /**
516
+ * Fetch graph from API
517
+ */
518
+ async function fetchFromApi(client, file, idempotencyKey) {
519
+ console.error('[DEBUG] Reading file:', file);
520
+ const fileBuffer = await (0, promises_1.readFile)(file);
521
+ const fileBlob = new Blob([fileBuffer], { type: 'application/zip' });
522
+ console.error('[DEBUG] File size:', fileBuffer.length, 'bytes');
523
+ console.error('[DEBUG] Making API request with idempotency key:', idempotencyKey);
524
+ const requestParams = {
525
+ file: fileBlob,
526
+ idempotencyKey: idempotencyKey,
527
+ };
528
+ const response = await client.graphs.generateSupermodelGraph(requestParams);
529
+ console.error('[DEBUG] API request successful');
530
+ return response;
531
+ }
532
+ /**
533
+ * Legacy mode: direct jq filtering on API response
534
+ */
535
+ async function handleLegacyMode(client, file, idempotencyKey, jq_filter) {
536
+ try {
537
+ const response = await fetchFromApi(client, file, idempotencyKey);
66
538
  return (0, types_1.asTextContentResult)(await (0, filtering_1.maybeFilter)(jq_filter, response));
67
539
  }
68
540
  catch (error) {
@@ -91,6 +563,5 @@ const handler = async (client, args) => {
91
563
  }
92
564
  return (0, types_1.asErrorResult)(`API call failed: ${error.message || String(error)}. Check server logs for details.`);
93
565
  }
94
- };
95
- exports.handler = handler;
566
+ }
96
567
  exports.default = { metadata: exports.metadata, tool: exports.tool, handler: exports.handler };