@supermodeltools/mcp-server 0.6.4 → 0.7.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.
package/README.md CHANGED
@@ -294,6 +294,92 @@ Analyzes code structure, dependencies, and relationships across a repository. Us
294
294
  - Cleans up temporary files automatically
295
295
  - Cross-platform compatible
296
296
 
297
+ ### Individual Graph Tools
298
+
299
+ For targeted analysis, use these specialized tools instead of the comprehensive `explore_codebase`:
300
+
301
+ #### `get_call_graph`
302
+
303
+ Generate a function-level call graph showing caller/callee relationships.
304
+
305
+ **Use this to:**
306
+ - Find all functions that call a specific function
307
+ - Find all functions called by a specific function
308
+ - Trace call chains through the codebase
309
+ - Understand function dependencies
310
+
311
+ **Parameters:**
312
+
313
+ | Argument | Type | Required | Description |
314
+ |----------|------|----------|-------------|
315
+ | `directory` | string | Yes | Path to repository directory |
316
+ | `jq_filter` | string | No | jq filter for custom data extraction |
317
+
318
+ #### `get_dependency_graph`
319
+
320
+ Generate a module-level dependency graph showing import relationships.
321
+
322
+ **Use this to:**
323
+ - Understand module dependencies
324
+ - Find circular dependencies
325
+ - Identify tightly coupled modules
326
+ - Plan module extraction or refactoring
327
+
328
+ **Parameters:**
329
+
330
+ | Argument | Type | Required | Description |
331
+ |----------|------|----------|-------------|
332
+ | `directory` | string | Yes | Path to repository directory |
333
+ | `jq_filter` | string | No | jq filter for custom data extraction |
334
+
335
+ #### `get_domain_graph`
336
+
337
+ Generate a high-level domain classification graph.
338
+
339
+ **Use this to:**
340
+ - Understand the architectural domains in a codebase
341
+ - See how code is organized into logical areas
342
+ - Get a bird's-eye view of system structure
343
+ - Identify domain boundaries
344
+
345
+ **Parameters:**
346
+
347
+ | Argument | Type | Required | Description |
348
+ |----------|------|----------|-------------|
349
+ | `directory` | string | Yes | Path to repository directory |
350
+ | `jq_filter` | string | No | jq filter for custom data extraction |
351
+
352
+ #### `get_parse_graph`
353
+
354
+ Generate an AST-level parse graph with fine-grained code structure.
355
+
356
+ **Use this to:**
357
+ - Analyze detailed code structure
358
+ - Find specific syntax patterns
359
+ - Understand class/function definitions at AST level
360
+ - Support precise refactoring operations
361
+
362
+ **Parameters:**
363
+
364
+ | Argument | Type | Required | Description |
365
+ |----------|------|----------|-------------|
366
+ | `directory` | string | Yes | Path to repository directory |
367
+ | `jq_filter` | string | No | jq filter for custom data extraction |
368
+
369
+ ### Choosing the Right Tool
370
+
371
+ | Tool | Best For | Output Size |
372
+ |------|----------|-------------|
373
+ | `explore_codebase` | Comprehensive analysis with built-in queries | Largest - all graph types |
374
+ | `get_call_graph` | Function call tracing, debugging | Medium - functions only |
375
+ | `get_dependency_graph` | Module refactoring, circular deps | Small - modules only |
376
+ | `get_domain_graph` | Architecture overview | Smallest - domains only |
377
+ | `get_parse_graph` | AST analysis, precise refactoring | Large - full AST |
378
+
379
+ **Tip:** Start with `get_domain_graph` for a quick architecture overview, then drill down with `get_call_graph` or `get_dependency_graph` for specific areas.
380
+
381
+ > **Note:** All graph tools accept a `directory` parameter and an optional `jq_filter`. If you start the server with a default working directory (`node dist/index.js /path/to/repo`), the `directory` argument can be omitted from tool calls.
382
+
297
383
  ## Tool Performance & Timeout Requirements
298
384
 
299
385
  The `explore_codebase` tool analyzes your entire repository to build a comprehensive code graph. Analysis time scales with repository size and complexity.
@@ -502,6 +588,69 @@ To enable verbose logging, set the `DEBUG` environment variable:
502
588
 
503
589
  Benchmark this MCP server using [mcpbr](https://github.com/caspianmoon/mcpbr-benchmark-caching) with the provided [`mcpbr-config.yaml`](./mcpbr-config.yaml) configuration.
504
590
 
591
+ ## Local Development & Testing
592
+
593
+ ### Building from Source
594
+
595
+ ```bash
596
+ git clone https://github.com/supermodeltools/mcp.git
597
+ cd mcp
598
+ npm install
599
+ npm run build
600
+ ```
601
+
602
+ ### Running Locally
603
+
604
+ ```bash
605
+ # Start the MCP server
606
+ node dist/index.js
607
+
608
+ # Or with a default working directory
609
+ node dist/index.js /path/to/repo
610
+ ```
611
+
612
+ ### Testing Tools Locally
613
+
614
+ Run the integration tests to verify the server and tools:
615
+
616
+ ```bash
617
+ # Run all tests including integration tests
618
+ npm test
619
+
620
+ # Run only integration tests
621
+ npm test -- src/server.integration.test.ts
622
+ ```
623
+
624
+ ### Using MCP Inspector
625
+
626
+ For interactive testing, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
627
+
628
+ ```bash
629
+ # Install the inspector
630
+ npm install -g @modelcontextprotocol/inspector
631
+
632
+ # Run with your server
633
+ npx @modelcontextprotocol/inspector node dist/index.js
634
+ ```
635
+
636
+ This opens a web UI where you can:
637
+ - See all available tools
638
+ - Call tools with custom arguments
639
+ - View responses in real-time
640
+
641
+ ### Running Tests
642
+
643
+ ```bash
644
+ # Run all tests
645
+ npm test
646
+
647
+ # Run with coverage
648
+ npm run test:coverage
649
+
650
+ # Type checking
651
+ npm run typecheck
652
+ ```
653
+
505
654
  ## Links
506
655
 
507
656
  - [API Documentation](https://docs.supermodeltools.com)
package/dist/filtering.js CHANGED
@@ -5,8 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.maybeFilter = maybeFilter;
7
7
  exports.isJqError = isJqError;
8
+ /**
9
+ * jq filtering utilities for JSON response transformation.
10
+ * Provides optional jq filter application to API responses.
11
+ * @module filtering
12
+ */
8
13
  // @ts-nocheck
9
14
  const jq_web_1 = __importDefault(require("jq-web"));
15
+ /**
16
+ * Optionally applies a jq filter to a response object.
17
+ * @param jqFilter - The jq filter string, or undefined to skip filtering
18
+ * @param response - The JSON response to filter
19
+ * @returns The filtered response, or the original response if no filter provided
20
+ */
10
21
  async function maybeFilter(jqFilter, response) {
11
22
  if (jqFilter && typeof jqFilter === 'string') {
12
23
  return await jq(response, jqFilter);
@@ -15,9 +26,20 @@ async function maybeFilter(jqFilter, response) {
15
26
  return response;
16
27
  }
17
28
  }
29
+ /**
30
+ * Applies a jq filter to JSON data.
31
+ * @param json - The JSON data to filter
32
+ * @param jqFilter - The jq filter expression
33
+ * @returns The filtered result
34
+ */
18
35
  async function jq(json, jqFilter) {
19
36
  return (await jq_web_1.default).json(json, jqFilter);
20
37
  }
38
+ /**
39
+ * Type guard to check if an error is a jq parsing error.
40
+ * @param error - The error to check
41
+ * @returns True if the error is a jq-related error with stderr output
42
+ */
21
43
  function isJqError(error) {
22
44
  return error instanceof Error && 'stderr' in error;
23
45
  }
package/dist/index.js CHANGED
@@ -34,8 +34,17 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ /**
38
+ * Entry point for the Supermodel MCP Server.
39
+ * Starts the MCP server with optional default working directory.
40
+ * @module index
41
+ */
37
42
  const server_1 = require("./server");
38
43
  const logger = __importStar(require("./utils/logger"));
44
+ /**
45
+ * Main entry point that initializes and starts the MCP server.
46
+ * Accepts an optional workdir argument from the command line.
47
+ */
39
48
  async function main() {
40
49
  // Parse command-line arguments to get optional default workdir
41
50
  // Usage: node dist/index.js [workdir]
package/dist/server.js CHANGED
@@ -37,10 +37,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.Server = void 0;
40
+ /**
41
+ * MCP Server implementation for the Supermodel codebase analysis tools.
42
+ * Provides JSON-RPC handlers for code graph generation and exploration.
43
+ * @module server
44
+ */
40
45
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
41
46
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
42
47
  const sdk_1 = require("@supermodeltools/sdk");
43
48
  const create_supermodel_graph_1 = __importDefault(require("./tools/create-supermodel-graph"));
49
+ const graph_tools_1 = require("./tools/graph-tools");
44
50
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
45
51
  const zip_repository_1 = require("./utils/zip-repository");
46
52
  const undici_1 = require("undici");
@@ -77,53 +83,32 @@ class Server {
77
83
  version: '0.0.1',
78
84
  }, {
79
85
  capabilities: { tools: {}, logging: {} },
80
- instructions: `# Server Instructions: Supermodel Codebase Explorer
86
+ instructions: `# Supermodel: Code Graph Tools
81
87
 
82
- ## Graph Rules
83
- - This API produces graphs of the code contained within a target directory.
84
- - STRATEGY: Before debugging, planning, or analyzing a change to a code repository, generate a code graph. Use it to localize changes and find what files to search more efficiently than grep.
88
+ Generate code graphs to understand a codebase before making changes.
85
89
 
86
- ## Debugging Strategy
87
- 1. Generate a code graph of the given repository or a subset.
88
- 2. Analyze the nodes and relationships which appear to be related to your issue.
89
- 3. Analyze the broader context of these nodes in relationships within their domain and subdomain.
90
- 4. Use the graph like a diagram to navigate the codebase more efficiently than raw grep and to analyze the potential blast radius of any change.
91
-
92
- ## Planning Strategy
93
- 1. Generate a code graph of the given repository or a subset.
94
- 2. Analyze relationships like dependencies, calls, and inheritance to identify the potential blast radius of a proposed change.
95
- 3. Examine other elements of the same Domain and Subdomain to look for patterns including best practices or anti-patterns.
96
- 4. Look at the nodes you plan to change and find their physical locations, allowing you to analyze more efficiently than blind grepping.
90
+ ## Recommended Workflow
97
91
 
98
- ## Analysis Strategy
99
- 1. Generate a code graph of the given repository or a subset.
100
- 2. Analyze the system domains to understand the high-level system architecture.
101
- 3. Examine leaf nodes to see the structure of the broader tree.
102
- 4. Use the graph like a map to navigate the codebase more efficiently than blind grepping.
92
+ 1. Start with \`get_domain_graph\` to understand architecture
93
+ 2. Use \`get_call_graph\` or \`get_dependency_graph\` to drill into specific areas
94
+ 3. Use \`get_parse_graph\` only when you need full structural detail
103
95
 
104
- ## Performance Optimization
96
+ Node IDs are consistent across all graph types. A function ID from \`get_domain_graph\` can be looked up in \`get_call_graph\` results to find its callers.
105
97
 
106
- For localized bugs:
107
- 1. Identify the affected subsystem from the issue description
108
- 2. Analyze only that subdirectory (e.g., \`django/db\` instead of full repo)
109
- 3. This is faster, uses less memory, and avoids ZIP size limits
98
+ ## Performance
110
99
 
111
- Example:
112
- - Full repo: directory="/repo" 180MB, 50k nodes
113
- - Subsystem: directory="/repo/django/db" 15MB, 3k nodes
100
+ - First call takes 30+ seconds (API processing time)
101
+ - Analyze subdirectories for faster results: \`src/auth\` instead of full repo
102
+ - Use \`jq_filter\` to extract only the data you need
114
103
 
115
- ## Error Handling
104
+ ## explore_codebase
116
105
 
117
- When a tool call returns an error, check the structured error fields:
118
- - \`error.recoverable: true\` → wait briefly and retry the request
119
- - \`error.reportable: true\` → this is a server-side bug, not your fault
106
+ Returns the complete graph with all data combined. Useful for comprehensive exports or integration with external tools. For iterative analysis, prefer the individual graph tools above.
120
107
 
121
- If you receive a reportable error and have GitHub access:
122
- 1. Search the \`error.repo\` issues for the \`error.code\` to avoid duplicates
123
- 2. If no existing issue, open one with the error code, message, and any reproduction context
124
- 3. If you can identify and fix the root cause, fork the repo and open a PR
108
+ ## Errors
125
109
 
126
- This helps the maintainers fix bugs faster and avoids wasting your iteration budget on issues you cannot work around locally.`,
110
+ - \`error.recoverable: true\` retry after brief wait
111
+ - \`error.reportable: true\` → server bug, can be reported`,
127
112
  });
128
113
  const config = new sdk_1.Configuration({
129
114
  basePath: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com',
@@ -143,15 +128,29 @@ This helps the maintainers fix bugs faster and avoids wasting your iteration bud
143
128
  this.setupHandlers();
144
129
  }
145
130
  setupHandlers() {
131
+ // Collect all tools: the main explore_codebase tool plus individual graph tools
132
+ const allTools = [
133
+ create_supermodel_graph_1.default,
134
+ ...graph_tools_1.graphTools,
135
+ ];
136
+ // Create a map for quick handler lookup, checking for duplicates
137
+ const toolMap = new Map();
138
+ for (const t of allTools) {
139
+ if (toolMap.has(t.tool.name)) {
140
+ throw new Error(`Duplicate tool name: ${t.tool.name}`);
141
+ }
142
+ toolMap.set(t.tool.name, t);
143
+ }
146
144
  this.server.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
147
145
  return {
148
- tools: [create_supermodel_graph_1.default.tool],
146
+ tools: allTools.map(t => t.tool),
149
147
  };
150
148
  });
151
149
  this.server.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
152
150
  const { name, arguments: args } = request.params;
153
- if (name === create_supermodel_graph_1.default.tool.name) {
154
- return create_supermodel_graph_1.default.handler(this.client, args, this.defaultWorkdir);
151
+ const tool = toolMap.get(name);
152
+ if (tool) {
153
+ return tool.handler(this.client, args, this.defaultWorkdir);
155
154
  }
156
155
  throw new Error(`Unknown tool: ${name}`);
157
156
  });
@@ -34,18 +34,15 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handler = exports.tool = exports.metadata = void 0;
37
- exports.classifyApiError = classifyApiError;
38
37
  const promises_1 = require("fs/promises");
39
- const child_process_1 = require("child_process");
40
- const crypto_1 = require("crypto");
38
+ const buffer_1 = require("buffer");
41
39
  const path_1 = require("path");
42
40
  const types_1 = require("../types");
43
41
  const filtering_1 = require("../filtering");
44
42
  const queries_1 = require("../queries");
45
43
  const zip_repository_1 = require("../utils/zip-repository");
46
44
  const logger = __importStar(require("../utils/logger"));
47
- const REPORT_REPO = 'https://github.com/supermodeltools/mcp.git';
48
- const REPORT_SUGGESTION = 'This may be a bug in the MCP server. You can help by opening an issue at https://github.com/supermodeltools/mcp/issues with the error details, or fork the repo and open a PR with a fix.';
45
+ const api_helpers_1 = require("../utils/api-helpers");
49
46
  exports.metadata = {
50
47
  resource: 'graphs',
51
48
  operation: 'write',
@@ -202,49 +199,13 @@ Query types available: graph_status, summary, get_node, search, list_nodes, func
202
199
  required: [],
203
200
  },
204
201
  };
205
- /**
206
- * Generate an idempotency key in format {repo}-{pathHash}:supermodel:{hash}
207
- * Includes path hash to prevent collisions between same-named repos
208
- */
209
- function generateIdempotencyKey(directory) {
210
- const repoName = (0, path_1.basename)(directory);
211
- const absolutePath = (0, path_1.resolve)(directory);
212
- // Always include path hash to prevent collisions
213
- const pathHash = (0, crypto_1.createHash)('sha1').update(absolutePath).digest('hex').substring(0, 7);
214
- let hash;
215
- let statusHash = '';
216
- try {
217
- // Get git commit hash
218
- hash = (0, child_process_1.execSync)('git rev-parse --short HEAD', {
219
- cwd: directory,
220
- encoding: 'utf-8',
221
- }).trim();
222
- // Include working tree status in hash to detect uncommitted changes
223
- const statusOutput = (0, child_process_1.execSync)('git status --porcelain', {
224
- cwd: directory,
225
- encoding: 'utf-8',
226
- }).toString();
227
- if (statusOutput) {
228
- // Create hash of status output
229
- statusHash = '-' + (0, crypto_1.createHash)('sha1')
230
- .update(statusOutput)
231
- .digest('hex')
232
- .substring(0, 7);
233
- }
234
- }
235
- catch {
236
- // Fallback for non-git directories: use path hash as main identifier
237
- hash = pathHash;
238
- }
239
- return `${repoName}-${pathHash}:supermodel:${hash}${statusHash}`;
240
- }
241
202
  const handler = async (client, args, defaultWorkdir) => {
242
203
  if (!args) {
243
204
  args = {};
244
205
  }
245
206
  const { jq_filter, directory: providedDirectory, query, targetId, searchText, namePattern, filePathPrefix, labels, depth, relationshipTypes, limit, includeRaw, } = args;
246
207
  // Use provided directory or fall back to default workdir
247
- const directory = providedDirectory || defaultWorkdir;
208
+ const directory = providedDirectory ?? defaultWorkdir;
248
209
  // Validate directory - check if explicitly invalid first
249
210
  if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
250
211
  logger.error('Invalid directory parameter:', providedDirectory);
@@ -274,7 +235,7 @@ const handler = async (client, args, defaultWorkdir) => {
274
235
  logger.debug('Using default workdir:', directory);
275
236
  }
276
237
  // Generate idempotency key for API request
277
- const idempotencyKey = generateIdempotencyKey(directory);
238
+ const idempotencyKey = (0, api_helpers_1.generateIdempotencyKey)(directory);
278
239
  logger.debug('Auto-generated idempotency key:', idempotencyKey);
279
240
  // Check if we can skip zipping (graph already cached)
280
241
  // Use get() atomically to avoid TOCTOU race condition
@@ -308,7 +269,7 @@ const handler = async (client, args, defaultWorkdir) => {
308
269
  const zipResult = await (0, zip_repository_1.zipRepository)(directory);
309
270
  zipPath = zipResult.path;
310
271
  cleanup = zipResult.cleanup;
311
- logger.debug('Auto-zip complete:', zipResult.fileCount, 'files,', formatBytes(zipResult.sizeBytes));
272
+ logger.debug('Auto-zip complete:', zipResult.fileCount, 'files,', (0, api_helpers_1.formatBytes)(zipResult.sizeBytes));
312
273
  }
313
274
  catch (error) {
314
275
  // Log full error details for debugging
@@ -366,8 +327,8 @@ const handler = async (client, args, defaultWorkdir) => {
366
327
  code: 'ZIP_CREATION_FAILED',
367
328
  recoverable: false,
368
329
  reportable: true,
369
- repo: REPORT_REPO,
370
- suggestion: REPORT_SUGGESTION,
330
+ repo: api_helpers_1.REPORT_REPO,
331
+ suggestion: api_helpers_1.REPORT_SUGGESTION,
371
332
  details: { directory: (0, path_1.basename)(directory), errorType: error.name || 'Error' },
372
333
  });
373
334
  }
@@ -406,18 +367,6 @@ const handler = async (client, args, defaultWorkdir) => {
406
367
  }
407
368
  };
408
369
  exports.handler = handler;
409
- /**
410
- * Format bytes as human-readable string
411
- */
412
- function formatBytes(bytes) {
413
- if (bytes < 1024)
414
- return `${bytes} B`;
415
- if (bytes < 1024 * 1024)
416
- return `${(bytes / 1024).toFixed(2)} KB`;
417
- if (bytes < 1024 * 1024 * 1024)
418
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
419
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
420
- }
421
370
  /**
422
371
  * Handle query-based requests when graph is already cached
423
372
  * Uses the cached graph directly to avoid TOCTOU issues
@@ -484,7 +433,7 @@ async function handleQueryMode(client, params) {
484
433
  }
485
434
  catch (error) {
486
435
  // Error details are already logged by fetchFromApi and logErrorResponse
487
- return (0, types_1.asErrorResult)(classifyApiError(error));
436
+ return (0, types_1.asErrorResult)((0, api_helpers_1.classifyApiError)(error));
488
437
  }
489
438
  }
490
439
  // Handle query errors
@@ -581,7 +530,7 @@ function logRequest(url, method, bodySize, idempotencyKey) {
581
530
  logger.debug(` Method: ${method}`);
582
531
  logger.debug(` URL: ${url}`);
583
532
  logger.debug(` Idempotency-Key: ${idempotencyKey}`);
584
- logger.debug(` Body size: ${formatBytes(bodySize)}`);
533
+ logger.debug(` Body size: ${(0, api_helpers_1.formatBytes)(bodySize)}`);
585
534
  logger.debug(` Content-Type: multipart/form-data`);
586
535
  }
587
536
  /**
@@ -590,7 +539,7 @@ function logRequest(url, method, bodySize, idempotencyKey) {
590
539
  function logResponse(status, statusText, responseSize, duration) {
591
540
  logger.debug(`[${getTimestamp()}] [API RESPONSE]`);
592
541
  logger.debug(` Status: ${status} ${statusText}`);
593
- logger.debug(` Response size: ${formatBytes(responseSize)}`);
542
+ logger.debug(` Response size: ${(0, api_helpers_1.formatBytes)(responseSize)}`);
594
543
  logger.debug(` Duration: ${duration}ms`);
595
544
  }
596
545
  /**
@@ -653,9 +602,9 @@ async function fetchFromApi(client, file, idempotencyKey) {
653
602
  const startTime = Date.now();
654
603
  logger.debug('Reading file:', file);
655
604
  const fileBuffer = await (0, promises_1.readFile)(file);
656
- const fileBlob = new Blob([fileBuffer], { type: 'application/zip' });
605
+ const fileBlob = new buffer_1.Blob([fileBuffer], { type: 'application/zip' });
657
606
  const fileSize = fileBuffer.length;
658
- logger.debug('File size:', formatBytes(fileSize));
607
+ logger.debug('File size:', (0, api_helpers_1.formatBytes)(fileSize));
659
608
  // Get the base URL from environment or use default
660
609
  const baseUrl = process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com';
661
610
  const apiUrl = `${baseUrl}/v1/graphs/supermodel`;
@@ -716,128 +665,6 @@ async function fetchFromApi(client, file, idempotencyKey) {
716
665
  throw error;
717
666
  }
718
667
  }
719
- /**
720
- * Classify an API error into a structured error response.
721
- * Extracts HTTP status, network conditions, and timeout signals
722
- * to produce an agent-actionable error with recovery guidance.
723
- */
724
- function classifyApiError(error) {
725
- // Guard against non-Error throws (strings, nulls, plain objects)
726
- if (!error || typeof error !== 'object') {
727
- return {
728
- type: 'internal_error',
729
- message: typeof error === 'string' ? error : 'An unexpected error occurred.',
730
- code: 'UNKNOWN_ERROR',
731
- recoverable: false,
732
- reportable: true,
733
- repo: REPORT_REPO,
734
- suggestion: REPORT_SUGGESTION,
735
- details: { errorType: typeof error },
736
- };
737
- }
738
- if (error.response) {
739
- const status = error.response.status;
740
- switch (status) {
741
- case 401:
742
- return {
743
- type: 'authentication_error',
744
- message: 'Invalid or missing API key.',
745
- code: 'INVALID_API_KEY',
746
- recoverable: false,
747
- suggestion: 'Set the SUPERMODEL_API_KEY environment variable and restart the MCP server.',
748
- details: { apiKeySet: !!process.env.SUPERMODEL_API_KEY, httpStatus: 401 },
749
- };
750
- case 403:
751
- return {
752
- type: 'authorization_error',
753
- message: 'API key does not have permission for this operation.',
754
- code: 'FORBIDDEN',
755
- recoverable: false,
756
- suggestion: 'Verify your API key has the correct permissions. Contact support if unexpected.',
757
- details: { httpStatus: 403 },
758
- };
759
- case 404:
760
- return {
761
- type: 'not_found_error',
762
- message: 'API endpoint not found.',
763
- code: 'ENDPOINT_NOT_FOUND',
764
- recoverable: false,
765
- suggestion: 'Check SUPERMODEL_BASE_URL environment variable. Default: https://api.supermodeltools.com',
766
- details: { baseUrl: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com', httpStatus: 404 },
767
- };
768
- case 429:
769
- return {
770
- type: 'rate_limit_error',
771
- message: 'API rate limit exceeded.',
772
- code: 'RATE_LIMITED',
773
- recoverable: true,
774
- suggestion: 'Wait 30-60 seconds and retry. Consider analyzing smaller subdirectories to reduce API calls.',
775
- details: { httpStatus: 429 },
776
- };
777
- case 500:
778
- case 502:
779
- case 503:
780
- case 504:
781
- return {
782
- type: 'internal_error',
783
- message: `Supermodel API server error (HTTP ${status}).`,
784
- code: 'SERVER_ERROR',
785
- recoverable: true,
786
- reportable: true,
787
- repo: REPORT_REPO,
788
- suggestion: 'The API may be temporarily unavailable. Wait a few minutes and retry. If persistent, open an issue at https://github.com/supermodeltools/mcp/issues with the error details, or fork the repo and open a PR with a fix.',
789
- details: { httpStatus: status },
790
- };
791
- default: {
792
- const isServerError = status >= 500;
793
- return {
794
- type: isServerError ? 'internal_error' : 'validation_error',
795
- message: `API request failed with HTTP ${status}.`,
796
- code: 'API_ERROR',
797
- recoverable: isServerError,
798
- ...(isServerError && {
799
- reportable: true,
800
- repo: REPORT_REPO,
801
- suggestion: 'The API may be temporarily unavailable. Wait a few minutes and retry. If persistent, open an issue at https://github.com/supermodeltools/mcp/issues with the error details, or fork the repo and open a PR with a fix.',
802
- }),
803
- ...(!isServerError && { suggestion: 'Check the request parameters and base URL configuration.' }),
804
- details: { httpStatus: status },
805
- };
806
- }
807
- }
808
- }
809
- if (error.request) {
810
- // Distinguish timeout from general network failure
811
- if (error.code === 'UND_ERR_HEADERS_TIMEOUT' || error.code === 'UND_ERR_BODY_TIMEOUT' || error.message?.includes('timeout')) {
812
- return {
813
- type: 'timeout_error',
814
- message: 'API request timed out. The codebase may be too large for a single analysis.',
815
- code: 'REQUEST_TIMEOUT',
816
- recoverable: true,
817
- suggestion: 'Analyze a smaller subdirectory (e.g. directory="/repo/src/core") or increase SUPERMODEL_TIMEOUT_MS.',
818
- };
819
- }
820
- return {
821
- type: 'network_error',
822
- message: 'No response from Supermodel API server.',
823
- code: 'NO_RESPONSE',
824
- recoverable: true,
825
- suggestion: 'Check network connectivity. Verify the API is reachable at the configured base URL.',
826
- details: { baseUrl: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com' },
827
- };
828
- }
829
- // Catch-all for unexpected errors - include the actual message
830
- return {
831
- type: 'internal_error',
832
- message: error.message || 'An unexpected error occurred.',
833
- code: 'UNKNOWN_ERROR',
834
- recoverable: false,
835
- reportable: true,
836
- repo: REPORT_REPO,
837
- suggestion: REPORT_SUGGESTION,
838
- details: { errorType: error.name || 'Error' },
839
- };
840
- }
841
668
  /**
842
669
  * Legacy mode: direct jq filtering on API response
843
670
  */
@@ -858,7 +685,7 @@ async function handleLegacyMode(client, file, idempotencyKey, jq_filter) {
858
685
  });
859
686
  }
860
687
  // Error details are already logged by fetchFromApi and logErrorResponse
861
- return (0, types_1.asErrorResult)(classifyApiError(error));
688
+ return (0, types_1.asErrorResult)((0, api_helpers_1.classifyApiError)(error));
862
689
  }
863
690
  }
864
691
  exports.default = { metadata: exports.metadata, tool: exports.tool, handler: exports.handler };
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ /**
3
+ * Individual graph type tools for targeted codebase analysis.
4
+ * Each tool calls a specific graph API endpoint for focused results.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.graphTools = exports.parseGraphTool = exports.domainGraphTool = exports.dependencyGraphTool = exports.callGraphTool = void 0;
41
+ const promises_1 = require("fs/promises");
42
+ const buffer_1 = require("buffer");
43
+ const types_1 = require("../types");
44
+ const filtering_1 = require("../filtering");
45
+ const zip_repository_1 = require("../utils/zip-repository");
46
+ const logger = __importStar(require("../utils/logger"));
47
+ const api_helpers_1 = require("../utils/api-helpers");
48
+ const GRAPH_TYPES = [
49
+ {
50
+ name: 'call',
51
+ toolName: 'get_call_graph',
52
+ description: `Generate a call graph showing function-to-function call relationships.
53
+
54
+ Returns: Function nodes with "calls" relationships between them.
55
+
56
+ Use this to:
57
+ - Find all callers of a specific function
58
+ - Find all functions called by a specific function
59
+ - Trace execution flow through the codebase
60
+ - Debug by following call chains
61
+
62
+ Best for: Debugging, understanding "what calls what", tracing execution paths.`,
63
+ endpoint: '/v1/graphs/call',
64
+ operationId: 'generateCallGraph',
65
+ apiMethod: 'generateCallGraph',
66
+ },
67
+ {
68
+ name: 'dependency',
69
+ toolName: 'get_dependency_graph',
70
+ description: `Generate a dependency graph showing import relationships between files.
71
+
72
+ Returns: File nodes with "IMPORTS" relationships between them.
73
+
74
+ Use this to:
75
+ - Map which files import which other files
76
+ - Find circular dependencies
77
+ - Understand module coupling
78
+ - Plan safe refactoring of imports
79
+
80
+ Best for: Refactoring, understanding module dependencies, finding import cycles.`,
81
+ endpoint: '/v1/graphs/dependency',
82
+ operationId: 'generateDependencyGraph',
83
+ apiMethod: 'generateDependencyGraph',
84
+ },
85
+ {
86
+ name: 'domain',
87
+ toolName: 'get_domain_graph',
88
+ description: `Generate a domain classification graph showing high-level architecture.
89
+
90
+ Returns: Domains with descriptions, responsibilities, subdomains, and file/function/class assignments.
91
+
92
+ Use this to:
93
+ - Understand the architectural structure of a codebase
94
+ - See how code is organized into logical domains
95
+ - Identify domain boundaries and responsibilities
96
+ - Get a bird's-eye view before diving into details
97
+
98
+ Best for: New codebases, architecture overview, understanding system organization.`,
99
+ endpoint: '/v1/graphs/domain',
100
+ operationId: 'generateDomainGraph',
101
+ apiMethod: 'generateDomainGraph',
102
+ },
103
+ {
104
+ name: 'parse',
105
+ toolName: 'get_parse_graph',
106
+ description: `Generate a full parse graph with all code structure elements.
107
+
108
+ Returns: All nodes (File, Directory, Class, Function, Type) and structural relationships (CONTAINS, DEFINES, DECLARES, IMPORTS).
109
+
110
+ Use this to:
111
+ - Get complete structural information about the codebase
112
+ - Find all classes, functions, and types
113
+ - Understand containment and definition relationships
114
+ - Support detailed refactoring analysis
115
+
116
+ Best for: Comprehensive analysis when you need the full code structure.`,
117
+ endpoint: '/v1/graphs/parse',
118
+ operationId: 'generateParseGraph',
119
+ apiMethod: 'generateParseGraph',
120
+ },
121
+ ];
122
+ /**
123
+ * Create a tool definition and handler for a specific graph type
124
+ */
125
+ function createGraphTool(config) {
126
+ const metadata = {
127
+ resource: 'graphs',
128
+ operation: 'write',
129
+ tags: [config.name],
130
+ httpMethod: 'post',
131
+ httpPath: config.endpoint,
132
+ operationId: config.operationId,
133
+ };
134
+ const tool = {
135
+ name: config.toolName,
136
+ description: config.description,
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ directory: {
141
+ type: 'string',
142
+ description: 'Path to the repository directory to analyze.',
143
+ },
144
+ jq_filter: {
145
+ type: 'string',
146
+ title: 'jq Filter',
147
+ description: 'Optional jq filter to extract specific data from the response.',
148
+ },
149
+ },
150
+ required: [],
151
+ },
152
+ };
153
+ const handler = async (client, args, defaultWorkdir) => {
154
+ if (!args) {
155
+ args = {};
156
+ }
157
+ const { jq_filter, directory: providedDirectory } = args;
158
+ if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
159
+ return (0, types_1.asErrorResult)({
160
+ type: 'validation_error',
161
+ message: 'Invalid "directory" parameter. Provide a valid directory path as a string.',
162
+ code: 'INVALID_DIRECTORY',
163
+ recoverable: false,
164
+ suggestion: 'Pass directory as a string path, e.g. directory="/workspace/my-repo".',
165
+ });
166
+ }
167
+ if (jq_filter !== undefined && typeof jq_filter !== 'string') {
168
+ return (0, types_1.asErrorResult)({
169
+ type: 'validation_error',
170
+ message: 'Invalid "jq_filter" parameter. Provide a jq filter string.',
171
+ code: 'INVALID_JQ_FILTER',
172
+ recoverable: false,
173
+ suggestion: 'Pass jq_filter as a string, e.g. jq_filter=".nodes".',
174
+ });
175
+ }
176
+ const directory = providedDirectory ?? defaultWorkdir;
177
+ if (!directory || typeof directory !== 'string') {
178
+ return (0, types_1.asErrorResult)({
179
+ type: 'validation_error',
180
+ message: 'No "directory" parameter provided and no default workdir configured.',
181
+ code: 'MISSING_DIRECTORY',
182
+ recoverable: false,
183
+ suggestion: 'Provide a directory path or start the MCP server with a workdir argument.',
184
+ });
185
+ }
186
+ const idempotencyKey = (0, api_helpers_1.generateIdempotencyKey)(directory, config.name);
187
+ logger.debug(`[${config.toolName}] Idempotency key:`, idempotencyKey);
188
+ // Create ZIP of repository
189
+ let zipPath;
190
+ let cleanup = null;
191
+ try {
192
+ const zipResult = await (0, zip_repository_1.zipRepository)(directory);
193
+ zipPath = zipResult.path;
194
+ cleanup = zipResult.cleanup;
195
+ logger.debug(`[${config.toolName}] ZIP created:`, zipResult.fileCount, 'files,', (0, api_helpers_1.formatBytes)(zipResult.sizeBytes));
196
+ }
197
+ catch (error) {
198
+ const message = typeof error?.message === 'string' ? error.message : String(error);
199
+ if (message.includes('does not exist')) {
200
+ return (0, types_1.asErrorResult)({
201
+ type: 'not_found_error',
202
+ message: `Directory not found: ${directory}`,
203
+ code: 'DIRECTORY_NOT_FOUND',
204
+ recoverable: false,
205
+ suggestion: 'Verify the path exists.',
206
+ });
207
+ }
208
+ return (0, types_1.asErrorResult)({
209
+ type: 'internal_error',
210
+ message: `Failed to create ZIP archive: ${message}`,
211
+ code: 'ZIP_CREATION_FAILED',
212
+ recoverable: false,
213
+ reportable: true,
214
+ repo: api_helpers_1.REPORT_REPO,
215
+ suggestion: api_helpers_1.REPORT_SUGGESTION,
216
+ });
217
+ }
218
+ try {
219
+ const fileBuffer = await (0, promises_1.readFile)(zipPath);
220
+ const fileBlob = new buffer_1.Blob([fileBuffer], { type: 'application/zip' });
221
+ logger.debug(`[${config.toolName}] Calling API...`);
222
+ console.error(`[Supermodel] Generating ${config.name} graph...`);
223
+ // Call the appropriate API method via SupermodelClient
224
+ const apiMethod = client.graphs[config.apiMethod].bind(client.graphs);
225
+ const response = await apiMethod(fileBlob, { idempotencyKey });
226
+ console.error(`[Supermodel] ${config.name} graph complete.`);
227
+ // Apply optional jq filter
228
+ const result = await (0, filtering_1.maybeFilter)(jq_filter, response);
229
+ return (0, types_1.asTextContentResult)(result);
230
+ }
231
+ catch (error) {
232
+ if ((0, filtering_1.isJqError)(error)) {
233
+ logger.error(`[${config.toolName}] jq filter error:`, error.message);
234
+ return (0, types_1.asErrorResult)({
235
+ type: 'validation_error',
236
+ message: `Invalid jq filter syntax: ${error.message}`,
237
+ code: 'INVALID_JQ_FILTER',
238
+ recoverable: false,
239
+ suggestion: 'Check jq filter syntax. Example: jq_filter=".nodes" or jq_filter=".graph.nodeCount"',
240
+ });
241
+ }
242
+ logger.error(`[${config.toolName}] API error:`, error.message);
243
+ return (0, types_1.asErrorResult)((0, api_helpers_1.classifyApiError)(error));
244
+ }
245
+ finally {
246
+ if (cleanup) {
247
+ try {
248
+ await cleanup();
249
+ }
250
+ catch (cleanupError) {
251
+ logger.warn(`[${config.toolName}] Cleanup failed:`, cleanupError);
252
+ }
253
+ }
254
+ }
255
+ };
256
+ return { metadata, tool, handler };
257
+ }
258
+ // Helper to find graph type by name (safer than array indexing)
259
+ function getGraphType(name) {
260
+ const config = GRAPH_TYPES.find(t => t.name === name);
261
+ if (!config) {
262
+ throw new Error(`Unknown graph type: ${name}`);
263
+ }
264
+ return config;
265
+ }
266
+ // Create all graph tools
267
+ exports.callGraphTool = createGraphTool(getGraphType('call'));
268
+ exports.dependencyGraphTool = createGraphTool(getGraphType('dependency'));
269
+ exports.domainGraphTool = createGraphTool(getGraphType('domain'));
270
+ exports.parseGraphTool = createGraphTool(getGraphType('parse'));
271
+ // Export all tools as an array for easy registration
272
+ exports.graphTools = [
273
+ exports.callGraphTool,
274
+ exports.dependencyGraphTool,
275
+ exports.domainGraphTool,
276
+ exports.parseGraphTool,
277
+ ];
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ /**
3
+ * Shared utilities for API operations across graph tools.
4
+ * Extracted to eliminate code duplication between graph-tools.ts and create-supermodel-graph.ts.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.REPORT_SUGGESTION = exports.REPORT_REPO = void 0;
8
+ exports.formatBytes = formatBytes;
9
+ exports.generateIdempotencyKey = generateIdempotencyKey;
10
+ exports.classifyApiError = classifyApiError;
11
+ const child_process_1 = require("child_process");
12
+ const crypto_1 = require("crypto");
13
+ const path_1 = require("path");
14
+ exports.REPORT_REPO = 'https://github.com/supermodeltools/mcp.git';
15
+ exports.REPORT_SUGGESTION = 'This may be a bug in the MCP server. You can help by opening an issue at https://github.com/supermodeltools/mcp/issues with the error details, or fork the repo and open a PR with a fix.';
16
+ /**
17
+ * Format bytes as human-readable string
18
+ */
19
+ function formatBytes(bytes) {
20
+ if (bytes < 1024)
21
+ return `${bytes} B`;
22
+ if (bytes < 1024 * 1024)
23
+ return `${(bytes / 1024).toFixed(2)} KB`;
24
+ if (bytes < 1024 * 1024 * 1024)
25
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
26
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
27
+ }
28
+ /**
29
+ * Generate an idempotency key in format {repo}-{pathHash}:{graphType}:{hash}
30
+ * Includes path hash to prevent collisions between same-named repos
31
+ */
32
+ function generateIdempotencyKey(directory, graphType = 'supermodel') {
33
+ const repoName = (0, path_1.basename)(directory);
34
+ const absolutePath = (0, path_1.resolve)(directory);
35
+ // Always include path hash to prevent collisions
36
+ const pathHash = (0, crypto_1.createHash)('sha1').update(absolutePath).digest('hex').substring(0, 7);
37
+ let hash;
38
+ let statusHash = '';
39
+ try {
40
+ // Get git commit hash
41
+ hash = (0, child_process_1.execSync)('git rev-parse --short HEAD', {
42
+ cwd: directory,
43
+ encoding: 'utf-8',
44
+ }).trim();
45
+ // Include working tree status in hash to detect uncommitted changes
46
+ const statusOutput = (0, child_process_1.execSync)('git status --porcelain', {
47
+ cwd: directory,
48
+ encoding: 'utf-8',
49
+ }).toString();
50
+ if (statusOutput) {
51
+ // Create hash of status output
52
+ statusHash = '-' + (0, crypto_1.createHash)('sha1')
53
+ .update(statusOutput)
54
+ .digest('hex')
55
+ .substring(0, 7);
56
+ }
57
+ }
58
+ catch {
59
+ // Fallback for non-git directories: use path hash as main identifier
60
+ hash = pathHash;
61
+ }
62
+ return `${repoName}-${pathHash}:${graphType}:${hash}${statusHash}`;
63
+ }
64
+ /**
65
+ * Classify an API error into a structured error response.
66
+ * Extracts HTTP status, network conditions, and timeout signals
67
+ * to produce an agent-actionable error with recovery guidance.
68
+ */
69
+ function classifyApiError(error) {
70
+ // Guard against non-Error throws (strings, nulls, plain objects)
71
+ if (!error || typeof error !== 'object') {
72
+ return {
73
+ type: 'internal_error',
74
+ message: typeof error === 'string' ? error : 'An unexpected error occurred.',
75
+ code: 'UNKNOWN_ERROR',
76
+ recoverable: false,
77
+ reportable: true,
78
+ repo: exports.REPORT_REPO,
79
+ suggestion: exports.REPORT_SUGGESTION,
80
+ details: { errorType: typeof error },
81
+ };
82
+ }
83
+ if (error.response) {
84
+ const status = error.response.status;
85
+ switch (status) {
86
+ case 401:
87
+ return {
88
+ type: 'authentication_error',
89
+ message: 'Invalid or missing API key.',
90
+ code: 'INVALID_API_KEY',
91
+ recoverable: false,
92
+ suggestion: 'Set the SUPERMODEL_API_KEY environment variable and restart the MCP server.',
93
+ details: { apiKeySet: !!process.env.SUPERMODEL_API_KEY, httpStatus: 401 },
94
+ };
95
+ case 403:
96
+ return {
97
+ type: 'authorization_error',
98
+ message: 'API key does not have permission for this operation.',
99
+ code: 'FORBIDDEN',
100
+ recoverable: false,
101
+ suggestion: 'Verify your API key has the correct permissions. Contact support if unexpected.',
102
+ details: { httpStatus: 403 },
103
+ };
104
+ case 404:
105
+ return {
106
+ type: 'not_found_error',
107
+ message: 'API endpoint not found.',
108
+ code: 'ENDPOINT_NOT_FOUND',
109
+ recoverable: false,
110
+ suggestion: 'Check SUPERMODEL_BASE_URL environment variable. Default: https://api.supermodeltools.com',
111
+ details: { baseUrl: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com', httpStatus: 404 },
112
+ };
113
+ case 429:
114
+ return {
115
+ type: 'rate_limit_error',
116
+ message: 'API rate limit exceeded.',
117
+ code: 'RATE_LIMITED',
118
+ recoverable: true,
119
+ suggestion: 'Wait 30-60 seconds and retry. Consider analyzing smaller subdirectories to reduce API calls.',
120
+ details: { httpStatus: 429 },
121
+ };
122
+ case 500:
123
+ case 502:
124
+ case 503:
125
+ case 504:
126
+ return {
127
+ type: 'internal_error',
128
+ message: `Supermodel API server error (HTTP ${status}).`,
129
+ code: 'SERVER_ERROR',
130
+ recoverable: true,
131
+ reportable: true,
132
+ repo: exports.REPORT_REPO,
133
+ suggestion: 'The API may be temporarily unavailable. Wait a few minutes and retry. If persistent, open an issue at https://github.com/supermodeltools/mcp/issues with the error details, or fork the repo and open a PR with a fix.',
134
+ details: { httpStatus: status },
135
+ };
136
+ default: {
137
+ const isServerError = status >= 500;
138
+ return {
139
+ type: isServerError ? 'internal_error' : 'validation_error',
140
+ message: `API request failed with HTTP ${status}.`,
141
+ code: 'API_ERROR',
142
+ recoverable: isServerError,
143
+ ...(isServerError && {
144
+ reportable: true,
145
+ repo: exports.REPORT_REPO,
146
+ suggestion: 'The API may be temporarily unavailable. Wait a few minutes and retry. If persistent, open an issue at https://github.com/supermodeltools/mcp/issues with the error details, or fork the repo and open a PR with a fix.',
147
+ }),
148
+ ...(!isServerError && { suggestion: 'Check the request parameters and base URL configuration.' }),
149
+ details: { httpStatus: status },
150
+ };
151
+ }
152
+ }
153
+ }
154
+ if (error.request) {
155
+ // Distinguish timeout from general network failure
156
+ if (error.code === 'UND_ERR_HEADERS_TIMEOUT' || error.code === 'UND_ERR_BODY_TIMEOUT' || error.message?.includes('timeout')) {
157
+ return {
158
+ type: 'timeout_error',
159
+ message: 'API request timed out. The codebase may be too large for a single analysis.',
160
+ code: 'REQUEST_TIMEOUT',
161
+ recoverable: true,
162
+ suggestion: 'Analyze a smaller subdirectory (e.g. directory="/repo/src/core") or increase SUPERMODEL_TIMEOUT_MS.',
163
+ };
164
+ }
165
+ return {
166
+ type: 'network_error',
167
+ message: 'No response from Supermodel API server.',
168
+ code: 'NO_RESPONSE',
169
+ recoverable: true,
170
+ suggestion: 'Check network connectivity. Verify the API is reachable at the configured base URL.',
171
+ details: { baseUrl: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com' },
172
+ };
173
+ }
174
+ // Catch-all for unexpected errors - include the actual message
175
+ return {
176
+ type: 'internal_error',
177
+ message: error.message || 'An unexpected error occurred.',
178
+ code: 'UNKNOWN_ERROR',
179
+ recoverable: false,
180
+ reportable: true,
181
+ repo: exports.REPORT_REPO,
182
+ suggestion: exports.REPORT_SUGGESTION,
183
+ details: { errorType: error.name || 'Error' },
184
+ };
185
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermodeltools/mcp-server",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "description": "MCP server for Supermodel API - code graph generation for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.0.1",
41
- "@supermodeltools/sdk": "0.6.4",
41
+ "@supermodeltools/sdk": "0.7.1",
42
42
  "archiver": "^7.0.1",
43
43
  "ignore": "^7.0.5",
44
44
  "jq-web": "^0.6.2",