@supermodeltools/mcp-server 0.5.3 → 0.6.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.
package/README.md CHANGED
@@ -8,6 +8,45 @@ MCP server that provides deep codebase analysis to AI agents via the [Supermodel
8
8
 
9
9
  ## Install
10
10
 
11
+ ### Quick Setup (Recommended)
12
+
13
+ Run the setup script to configure the recommended timeout settings:
14
+
15
+ ```bash
16
+ curl -sSL https://raw.githubusercontent.com/supermodeltools/mcp/main/setup.sh | bash
17
+ ```
18
+
19
+ <details>
20
+ <summary>Prefer to inspect before running? (Click to expand)</summary>
21
+
22
+ Download, review, then execute:
23
+
24
+ ```bash
25
+ # Download the script
26
+ curl -sSL https://raw.githubusercontent.com/supermodeltools/mcp/main/setup.sh -o setup.sh
27
+
28
+ # Review the contents
29
+ cat setup.sh
30
+
31
+ # Make executable and run
32
+ chmod +x setup.sh
33
+ ./setup.sh
34
+ ```
35
+
36
+ Or clone the entire repo:
37
+
38
+ ```bash
39
+ git clone https://github.com/supermodeltools/mcp.git
40
+ cd mcp
41
+ ./setup.sh
42
+ ```
43
+
44
+ </details>
45
+
46
+ This will configure `MCP_TOOL_TIMEOUT=900000` for optimal performance with large codebases.
47
+
48
+ ### Manual Install
49
+
11
50
  ```bash
12
51
  npm install -g @supermodeltools/mcp-server
13
52
  ```
@@ -18,6 +57,45 @@ Or run directly:
18
57
  npx @supermodeltools/mcp-server
19
58
  ```
20
59
 
60
+ ## ⚠️ Important: Configure Timeout for Large Codebase Analysis
61
+
62
+ The `explore_codebase` tool can take **5-15 minutes** to analyze large repositories. Most MCP clients have a default timeout of 60-120 seconds, which will cause the operation to fail prematurely.
63
+
64
+ **Quick Setup:**
65
+
66
+ Add this to your shell profile to set a 15-minute timeout:
67
+
68
+ ```bash
69
+ export MCP_TOOL_TIMEOUT=900000
70
+ ```
71
+
72
+ **Installation by Client:**
73
+
74
+ <details>
75
+ <summary><strong>Claude Code CLI</strong></summary>
76
+
77
+ Add to your shell profile (`~/.zshrc` for macOS or `~/.bashrc` for Linux):
78
+
79
+ ```bash
80
+ export MCP_TOOL_TIMEOUT=900000
81
+ ```
82
+
83
+ Then reload your shell:
84
+
85
+ ```bash
86
+ source ~/.zshrc # or ~/.bashrc
87
+ ```
88
+
89
+ Verify it's set:
90
+
91
+ ```bash
92
+ echo $MCP_TOOL_TIMEOUT
93
+ ```
94
+
95
+ </details>
96
+
97
+ **Note:** Timeout configuration via `MCP_TOOL_TIMEOUT` is only supported in Claude Code CLI. For more details, see the [official Claude Code documentation](https://code.claude.com/docs/en/settings.md).
98
+
21
99
  ## Configuration
22
100
 
23
101
  Get your API key from the [Supermodel Dashboard](https://dashboard.supermodeltools.com).
@@ -114,25 +192,7 @@ Add to `~/.cursor/mcp.json`:
114
192
  }
115
193
  ```
116
194
 
117
- ### Claude Desktop
118
-
119
- Add to `claude_desktop_config.json`:
120
-
121
- ```json
122
- {
123
- "mcpServers": {
124
- "supermodel": {
125
- "command": "npx",
126
- "args": ["-y", "@supermodeltools/mcp-server"],
127
- "env": {
128
- "SUPERMODEL_API_KEY": "your-api-key"
129
- }
130
- }
131
- }
132
- }
133
- ```
134
-
135
- ### Claude Code
195
+ ### Claude Code CLI
136
196
 
137
197
  Add the MCP server with your API key:
138
198
 
@@ -152,6 +212,44 @@ Verify installation:
152
212
  claude mcp list
153
213
  ```
154
214
 
215
+ ## Health Checks
216
+
217
+ This MCP server implements the [MCP ping utility](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping) for connection health monitoring. The ping mechanism allows clients to verify that the server is responsive and the connection remains alive.
218
+
219
+ ### How It Works
220
+
221
+ - **Request**: Client sends a `ping` JSON-RPC request with no parameters
222
+ - **Response**: Server responds promptly with an empty result object `{}`
223
+ - **Automatic**: Handled automatically by the MCP SDK - no additional configuration needed
224
+
225
+ ### Use Cases
226
+
227
+ - **Pre-flight checks**: Verify server is accessible before starting work
228
+ - **Connection monitoring**: Detect stale connections during long-running sessions
229
+ - **Periodic health checks**: Confirm server remains responsive
230
+
231
+ ### Example
232
+
233
+ ```json
234
+ // Request
235
+ {
236
+ "jsonrpc": "2.0",
237
+ "id": "123",
238
+ "method": "ping"
239
+ }
240
+
241
+ // Response
242
+ {
243
+ "jsonrpc": "2.0",
244
+ "id": "123",
245
+ "result": {}
246
+ }
247
+ ```
248
+
249
+ If the server doesn't respond within a reasonable timeout (typically 5-10 seconds), the connection should be considered stale.
250
+
251
+ For more details, see the [MCP specification for ping/health checks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping).
252
+
155
253
  ## Tools
156
254
 
157
255
  ### `explore_codebase`
@@ -196,9 +294,176 @@ Analyzes code structure, dependencies, and relationships across a repository. Us
196
294
  - Cleans up temporary files automatically
197
295
  - Cross-platform compatible
198
296
 
297
+ ## Tool Performance & Timeout Requirements
298
+
299
+ The `explore_codebase` tool analyzes your entire repository to build a comprehensive code graph. Analysis time scales with repository size and complexity.
300
+
301
+ | Tool | Typical Duration | Recommended Timeout | Repository Size |
302
+ |------|------------------|---------------------|--------------------|
303
+ | `explore_codebase` | 2-5 min | 600000ms (10 min) | Small (<1k files) |
304
+ | `explore_codebase` | 5-10 min | 900000ms (15 min) | Medium (1k-10k files) |
305
+ | `explore_codebase` | 10-15 min | 1200000ms (20 min) | Large (10k+ files) |
306
+
307
+ **Default recommendation**: The server uses a 15-minute timeout (`900000ms`) by default, which works well for most medium-sized repositories.
308
+
309
+ **Caching behavior**: The first run analyzes your codebase and caches the results. Subsequent queries on the same repository are typically much faster (seconds instead of minutes) as they use the cached graph.
310
+
311
+ **Setting custom timeouts**: If you need to adjust the timeout for larger repositories, you can set the `SUPERMODEL_TIMEOUT_MS` environment variable:
312
+
313
+ ```json
314
+ {
315
+ "mcpServers": {
316
+ "supermodel": {
317
+ "command": "npx",
318
+ "args": ["-y", "@supermodeltools/mcp-server"],
319
+ "env": {
320
+ "SUPERMODEL_API_KEY": "your-api-key",
321
+ "SUPERMODEL_TIMEOUT_MS": "1200000"
322
+ }
323
+ }
324
+ }
325
+ }
326
+ ```
327
+
199
328
  ## Troubleshooting
200
329
 
201
- Debug logs go to stderr:
330
+ ### Timeout Errors
331
+
332
+ #### "Request timeout"
333
+
334
+ **Cause:** The analysis is taking longer than your MCP client's timeout allows (varies by client—Claude Code CLI defaults to ~2 minutes, Claude Desktop enforces 5 minutes). Large repositories or complex codebases may require more time to analyze.
335
+
336
+ **Solutions:**
337
+
338
+ 1. **Analyze a subdirectory instead** - Target specific parts of your codebase:
339
+ ```bash
340
+ # Instead of analyzing the entire repo
341
+ explore_codebase(directory="/path/to/repo")
342
+
343
+ # Analyze just the core module
344
+ explore_codebase(directory="/path/to/repo/src/core")
345
+ ```
346
+
347
+ 2. **Increase your MCP client timeout** - For Claude Code CLI, set the `MCP_TOOL_TIMEOUT` environment variable:
348
+
349
+ ```bash
350
+ # Set timeout to 15 minutes (900000ms) for large codebase analysis
351
+ export MCP_TOOL_TIMEOUT=900000
352
+ ```
353
+
354
+ Then reload your shell or start a new terminal session. This timeout applies to all MCP tool executions.
355
+
356
+ **Note:** Timeout configuration is currently only supported in Claude Code CLI.
357
+
358
+ 3. **Verify `.gitignore` excludes build artifacts** - Ensure your repository excludes:
359
+ - `node_modules/`, `vendor/`, `venv/`
360
+ - `dist/`, `build/`, `out/`
361
+ - `.next/`, `.nuxt/`, `.cache/`
362
+
363
+ The MCP server automatically excludes these patterns when zipping, but `.gitignore` prevents them from being in your working directory in the first place—both improve performance and reduce analysis size.
364
+
365
+ #### "Analysis interrupted mid-way"
366
+
367
+ **Cause:** Network interruption or the MCP server process was terminated before completion.
368
+
369
+ **Solutions:**
370
+
371
+ 1. **Check MCP server logs** - Logs location varies by client:
372
+
373
+ > **Note:** Log filenames match your MCP server name from the config. If you named it differently (e.g., `my-server`), look for `mcp-server-my-server.log` instead of `mcp-server-supermodel.log`.
374
+
375
+ **Claude Desktop (macOS):**
376
+ ```bash
377
+ tail -f ~/Library/Logs/Claude/mcp-server-supermodel.log
378
+ ```
379
+
380
+ **Claude Desktop (Windows):**
381
+ ```powershell
382
+ Get-Content "$env:APPDATA\Claude\Logs\mcp-server-supermodel.log" -Wait
383
+ ```
384
+
385
+ **Claude Desktop (Linux):**
386
+ ```bash
387
+ tail -f ~/.config/Claude/logs/mcp-server-supermodel.log
388
+ ```
389
+
390
+ **Cursor:**
391
+ ```bash
392
+ # Check the Cursor logs directory
393
+ tail -f ~/Library/Application\ Support/Cursor/logs/mcp-server-supermodel.log
394
+ ```
395
+
396
+ **Claude Code:**
397
+ ```bash
398
+ # Logs are shown in the terminal when running with verbose mode
399
+ claude --verbose
400
+ ```
401
+
402
+ 2. **Retry the analysis** - Temporary network issues often resolve on retry
403
+
404
+ 3. **Check your internet connection** - The analysis requires a stable connection to the Supermodel API
405
+
406
+ 4. **Verify the API is accessible:**
407
+ ```bash
408
+ curl -H "Authorization: Bearer YOUR_API_KEY" https://api.supermodeltools.com/health
409
+ ```
410
+
411
+ #### "ERROR Request failed. Check the MCP server logs"
412
+
413
+ **Multiple possible causes:**
414
+
415
+ ##### 1. Missing or invalid API key
416
+
417
+ Check if your API key is set:
418
+ ```bash
419
+ echo $SUPERMODEL_API_KEY
420
+ ```
421
+
422
+ Verify it's valid at [Supermodel Dashboard](https://dashboard.supermodeltools.com).
423
+
424
+ **Solution:**
425
+ ```bash
426
+ # Set in your shell profile (~/.zshrc or ~/.bashrc)
427
+ export SUPERMODEL_API_KEY="your-api-key"
428
+ source ~/.zshrc
429
+
430
+ # Or update your MCP client config with the correct key
431
+ ```
432
+
433
+ ##### 2. API service outage or rate limiting
434
+
435
+ Check the error details in logs (see log locations above).
436
+
437
+ **Solution:**
438
+ - Visit [Supermodel Status](https://status.supermodeltools.com) for service status
439
+ - If rate limited, wait a few minutes before retrying
440
+ - Consider upgrading your API plan if hitting rate limits frequently
441
+
442
+ ##### 3. Repository too large
443
+
444
+ The API has size limits for analysis. Check the [Supermodel documentation](https://docs.supermodeltools.com) for current limits.
445
+
446
+ **Solution:**
447
+ ```bash
448
+ # Check your repo size
449
+ du -sh /path/to/repo
450
+
451
+ # If too large, analyze subdirectories instead
452
+ explore_codebase(directory="/path/to/repo/src")
453
+ ```
454
+
455
+ ##### 4. Network or firewall issues
456
+
457
+ Corporate firewalls may block outbound requests to the Supermodel API.
458
+
459
+ **Solution:**
460
+ - Test connectivity: `curl https://api.supermodeltools.com/health`
461
+ - Check firewall rules allow HTTPS to `api.supermodeltools.com`
462
+ - Contact your IT department if behind a corporate proxy
463
+
464
+ ### Debug Logging
465
+
466
+ Debug logs go to stderr and include:
202
467
 
203
468
  - `[DEBUG] Server configuration:` - Startup config
204
469
  - `[DEBUG] Auto-zipping directory:` - Starting zip creation
@@ -206,12 +471,32 @@ Debug logs go to stderr:
206
471
  - `[DEBUG] Making API request` - Request details
207
472
  - `[ERROR] API call failed:` - Error details with HTTP status
208
473
 
209
- **Common issues:**
210
- - 401: Check `SUPERMODEL_API_KEY` is set
211
- - ZIP too large: Directory contains too many files/dependencies. Ensure `.gitignore` is configured properly
212
- - Permission denied: Check read permissions on the directory
213
- - Insufficient disk space: Free up space in your system's temp directory
214
- - Directory does not exist: Verify the path is correct and absolute
474
+ To enable verbose logging, set the `DEBUG` environment variable:
475
+
476
+ ```bash
477
+ # In your MCP config
478
+ {
479
+ "mcpServers": {
480
+ "supermodel": {
481
+ "command": "npx",
482
+ "args": ["-y", "@supermodeltools/mcp-server"],
483
+ "env": {
484
+ "SUPERMODEL_API_KEY": "your-api-key",
485
+ "DEBUG": "supermodel:*"
486
+ }
487
+ }
488
+ }
489
+ }
490
+ ```
491
+
492
+ ### Common Issues
493
+
494
+ - **401 Unauthorized:** Check `SUPERMODEL_API_KEY` is set correctly
495
+ - **ZIP too large:** Directory contains too many files/dependencies. Ensure `.gitignore` is configured properly
496
+ - **Permission denied:** Check read permissions on the directory
497
+ - **Insufficient disk space:** Free up space in your system's temp directory
498
+ - **Directory does not exist:** Verify the path is correct and absolute
499
+ - **ENOTFOUND or connection errors:** Check your internet connection and firewall settings
215
500
 
216
501
  ## Benchmarking
217
502
 
package/dist/server.js CHANGED
@@ -110,7 +110,20 @@ For localized bugs:
110
110
 
111
111
  Example:
112
112
  - Full repo: directory="/repo" → 180MB, 50k nodes
113
- - Subsystem: directory="/repo/django/db" → 15MB, 3k nodes`,
113
+ - Subsystem: directory="/repo/django/db" → 15MB, 3k nodes
114
+
115
+ ## Error Handling
116
+
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
120
+
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
125
+
126
+ This helps the maintainers fix bugs faster and avoids wasting your iteration budget on issues you cannot work around locally.`,
114
127
  });
115
128
  const config = new sdk_1.Configuration({
116
129
  basePath: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com',
@@ -34,6 +34,7 @@ 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;
37
38
  const promises_1 = require("fs/promises");
38
39
  const child_process_1 = require("child_process");
39
40
  const crypto_1 = require("crypto");
@@ -43,6 +44,8 @@ const filtering_1 = require("../filtering");
43
44
  const queries_1 = require("../queries");
44
45
  const zip_repository_1 = require("../utils/zip-repository");
45
46
  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.';
46
49
  exports.metadata = {
47
50
  resource: 'graphs',
48
51
  operation: 'write',
@@ -53,7 +56,9 @@ exports.metadata = {
53
56
  };
54
57
  exports.tool = {
55
58
  name: 'explore_codebase',
56
- 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.
59
+ description: `Comprehensive codebase analysis using Supermodel API. ⚠️ This operation takes 5-15 minutes for large repositories. Ensure your Claude CLI timeout is configured (MCP_TOOL_TIMEOUT=900000). Analyzes code structure, dependencies, and patterns while respecting .gitignore.
60
+
61
+ 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.
57
62
 
58
63
  ## Example Output
59
64
 
@@ -243,12 +248,24 @@ const handler = async (client, args, defaultWorkdir) => {
243
248
  // Validate directory - check if explicitly invalid first
244
249
  if (providedDirectory !== undefined && typeof providedDirectory !== 'string') {
245
250
  logger.error('Invalid directory parameter:', providedDirectory);
246
- return (0, types_1.asErrorResult)('Invalid "directory" parameter. Provide a valid directory path as a string.');
251
+ return (0, types_1.asErrorResult)({
252
+ type: 'validation_error',
253
+ message: 'Invalid "directory" parameter. Provide a valid directory path as a string.',
254
+ code: 'INVALID_DIRECTORY',
255
+ recoverable: false,
256
+ suggestion: 'Pass directory as a string path, e.g. directory="/workspace/my-repo"',
257
+ });
247
258
  }
248
259
  // Check if we have any directory at all
249
260
  if (!directory || typeof directory !== 'string') {
250
261
  logger.error('Invalid directory parameter:', directory);
251
- return (0, types_1.asErrorResult)('No "directory" parameter provided and no default workdir configured. Please provide a directory path or start the server with a workdir argument.');
262
+ return (0, types_1.asErrorResult)({
263
+ type: 'validation_error',
264
+ message: 'No "directory" parameter provided and no default workdir configured.',
265
+ code: 'MISSING_DIRECTORY',
266
+ recoverable: false,
267
+ suggestion: 'Provide a directory path or start the MCP server with a workdir argument (e.g. npx @anthropic-ai/supermodel-mcp /path/to/repo).',
268
+ });
252
269
  }
253
270
  if (providedDirectory) {
254
271
  logger.debug('Using provided directory:', directory);
@@ -301,20 +318,58 @@ const handler = async (client, args, defaultWorkdir) => {
301
318
  if (error.stack) {
302
319
  logger.error('Stack trace:', error.stack);
303
320
  }
304
- // Return user-friendly, actionable error messages
305
- if (error.message.includes('does not exist')) {
306
- return (0, types_1.asErrorResult)(`Directory not found. Please verify the path exists: ${directory}`);
321
+ // Normalize: guard against non-Error throws (string, object, undefined)
322
+ const message = typeof error?.message === 'string' ? error.message : String(error);
323
+ // Return structured, actionable error messages
324
+ if (message.includes('does not exist')) {
325
+ return (0, types_1.asErrorResult)({
326
+ type: 'not_found_error',
327
+ message: `Directory not found: ${directory}`,
328
+ code: 'DIRECTORY_NOT_FOUND',
329
+ recoverable: false,
330
+ suggestion: 'Verify the path exists. Use an absolute path to the repository root or subdirectory.',
331
+ details: { directory },
332
+ });
307
333
  }
308
- if (error.message.includes('Permission denied')) {
309
- return (0, types_1.asErrorResult)(`Permission denied. Check that you have read access to: ${directory}`);
334
+ if (message.includes('Permission denied')) {
335
+ return (0, types_1.asErrorResult)({
336
+ type: 'resource_error',
337
+ message: `Permission denied accessing directory: ${directory}`,
338
+ code: 'PERMISSION_DENIED',
339
+ recoverable: false,
340
+ suggestion: 'Check that the MCP server process has read access to this directory.',
341
+ details: { directory },
342
+ });
310
343
  }
311
- if (error.message.includes('exceeds limit')) {
312
- return (0, types_1.asErrorResult)(error.message + '\n\nTry analyzing a subdirectory or excluding more files.');
344
+ if (message.includes('exceeds limit')) {
345
+ return (0, types_1.asErrorResult)({
346
+ type: 'resource_error',
347
+ message,
348
+ code: 'ZIP_TOO_LARGE',
349
+ recoverable: true,
350
+ suggestion: 'Analyze a subdirectory instead of the full repository (e.g. directory="/repo/src/core"). This reduces ZIP size and processing time.',
351
+ details: { directory },
352
+ });
313
353
  }
314
- if (error.message.includes('ENOSPC')) {
315
- return (0, types_1.asErrorResult)('Insufficient disk space. Free up space and try again.');
354
+ if (message.includes('ENOSPC')) {
355
+ return (0, types_1.asErrorResult)({
356
+ type: 'resource_error',
357
+ message: 'Insufficient disk space to create ZIP archive.',
358
+ code: 'DISK_FULL',
359
+ recoverable: false,
360
+ suggestion: 'Free up disk space or analyze a smaller subdirectory.',
361
+ });
316
362
  }
317
- return (0, types_1.asErrorResult)(`Failed to create ZIP archive. Check the MCP server logs for details.`);
363
+ return (0, types_1.asErrorResult)({
364
+ type: 'internal_error',
365
+ message: `Failed to create ZIP archive: ${message}`,
366
+ code: 'ZIP_CREATION_FAILED',
367
+ recoverable: false,
368
+ reportable: true,
369
+ repo: REPORT_REPO,
370
+ suggestion: REPORT_SUGGESTION,
371
+ details: { directory: (0, path_1.basename)(directory), errorType: error.name || 'Error' },
372
+ });
318
373
  }
319
374
  // Execute query with cleanup handling
320
375
  try {
@@ -429,40 +484,7 @@ async function handleQueryMode(client, params) {
429
484
  }
430
485
  catch (error) {
431
486
  // Error details are already logged by fetchFromApi and logErrorResponse
432
- // Return a user-friendly, actionable error message
433
- let errorMessage = '';
434
- if (error.response) {
435
- const status = error.response.status;
436
- switch (status) {
437
- case 401:
438
- errorMessage = 'Authentication failed. Set your SUPERMODEL_API_KEY environment variable and restart the MCP server.';
439
- break;
440
- case 403:
441
- errorMessage = 'Access forbidden. Your API key does not have permission for this operation. Contact support if this is unexpected.';
442
- break;
443
- case 404:
444
- errorMessage = 'API endpoint not found. The service URL may be incorrect. Check your SUPERMODEL_BASE_URL configuration.';
445
- break;
446
- case 429:
447
- errorMessage = 'Rate limit exceeded. Wait a few minutes and try again.';
448
- break;
449
- case 500:
450
- case 502:
451
- case 503:
452
- case 504:
453
- errorMessage = 'Server error. The Supermodel API is temporarily unavailable. Try again in a few minutes.';
454
- break;
455
- default:
456
- errorMessage = `API error (HTTP ${status}). Check the MCP server logs for details.`;
457
- }
458
- }
459
- else if (error.request) {
460
- errorMessage = 'No response from server. Check your network connection and verify the API is reachable.';
461
- }
462
- else {
463
- errorMessage = 'Request failed. Check the MCP server logs for details.';
464
- }
465
- return (0, types_1.asErrorResult)(errorMessage);
487
+ return (0, types_1.asErrorResult)(classifyApiError(error));
466
488
  }
467
489
  }
468
490
  // Handle query errors
@@ -625,7 +647,7 @@ async function logErrorResponse(error) {
625
647
  }
626
648
  }
627
649
  /**
628
- * Fetch graph from API with comprehensive logging
650
+ * Fetch graph from API with comprehensive logging and progress updates
629
651
  */
630
652
  async function fetchFromApi(client, file, idempotencyKey) {
631
653
  const startTime = Date.now();
@@ -643,33 +665,182 @@ async function fetchFromApi(client, file, idempotencyKey) {
643
665
  file: fileBlob,
644
666
  idempotencyKey: idempotencyKey,
645
667
  };
668
+ // Start progress logging
669
+ console.error('[Supermodel] Starting codebase analysis...');
670
+ // Set up periodic progress updates every 15 seconds
671
+ let progressInterval = null;
672
+ let elapsedSeconds = 0;
673
+ progressInterval = setInterval(() => {
674
+ elapsedSeconds += 15;
675
+ console.error(`[Supermodel] Analysis in progress... (${elapsedSeconds}s elapsed)`);
676
+ }, 15000);
646
677
  try {
647
678
  const response = await client.graphs.generateSupermodelGraph(requestParams);
648
679
  const duration = Date.now() - startTime;
680
+ // Clear progress interval
681
+ if (progressInterval) {
682
+ clearInterval(progressInterval);
683
+ progressInterval = null;
684
+ }
649
685
  // Calculate approximate response size
650
686
  const responseSize = JSON.stringify(response).length;
651
687
  logResponse(200, 'OK', responseSize, duration);
688
+ // Log completion with summary
689
+ const summary = response.summary;
690
+ if (summary) {
691
+ console.error(`[Supermodel] Analysis complete, retrieving results... (${summary.filesProcessed || 0} files processed)`);
692
+ }
693
+ else {
694
+ console.error('[Supermodel] Analysis complete, retrieving results...');
695
+ }
652
696
  logger.debug(`[${getTimestamp()}] [API SUCCESS] Request completed successfully`);
653
697
  return response;
654
698
  }
655
699
  catch (error) {
656
700
  const duration = Date.now() - startTime;
701
+ // Clear progress interval on error
702
+ if (progressInterval) {
703
+ clearInterval(progressInterval);
704
+ progressInterval = null;
705
+ }
657
706
  logger.error(`[${getTimestamp()}] [API FAILURE] Request failed after ${duration}ms`);
658
707
  // Log detailed error information
659
708
  await logErrorResponse(error);
660
- // Re-throw with enhanced error message
709
+ // Preserve error.response so classifyApiError can read the status code (#75)
661
710
  if (error.response?.status === 401) {
662
- throw new Error(`API authentication failed (401 Unauthorized). Please check your SUPERMODEL_API_KEY environment variable.`);
711
+ error.message = 'API authentication failed (401 Unauthorized). Please check your SUPERMODEL_API_KEY environment variable.';
663
712
  }
664
713
  else if (error.response?.status === 403) {
665
- throw new Error(`API access forbidden (403 Forbidden). Your API key may not have permission to access this resource.`);
714
+ error.message = 'API access forbidden (403 Forbidden). Your API key may not have permission to access this resource.';
666
715
  }
667
716
  else if (error.response?.status >= 500) {
668
- throw new Error(`Supermodel API server error (${error.response.status}). The service may be temporarily unavailable.`);
717
+ error.message = `Supermodel API server error (${error.response.status}). The service may be temporarily unavailable.`;
669
718
  }
670
719
  throw error;
671
720
  }
672
721
  }
722
+ /**
723
+ * Classify an API error into a structured error response.
724
+ * Extracts HTTP status, network conditions, and timeout signals
725
+ * to produce an agent-actionable error with recovery guidance.
726
+ */
727
+ function classifyApiError(error) {
728
+ // Guard against non-Error throws (strings, nulls, plain objects)
729
+ if (!error || typeof error !== 'object') {
730
+ return {
731
+ type: 'internal_error',
732
+ message: typeof error === 'string' ? error : 'An unexpected error occurred.',
733
+ code: 'UNKNOWN_ERROR',
734
+ recoverable: false,
735
+ reportable: true,
736
+ repo: REPORT_REPO,
737
+ suggestion: REPORT_SUGGESTION,
738
+ details: { errorType: typeof error },
739
+ };
740
+ }
741
+ if (error.response) {
742
+ const status = error.response.status;
743
+ switch (status) {
744
+ case 401:
745
+ return {
746
+ type: 'authentication_error',
747
+ message: 'Invalid or missing API key.',
748
+ code: 'INVALID_API_KEY',
749
+ recoverable: false,
750
+ suggestion: 'Set the SUPERMODEL_API_KEY environment variable and restart the MCP server.',
751
+ details: { apiKeySet: !!process.env.SUPERMODEL_API_KEY, httpStatus: 401 },
752
+ };
753
+ case 403:
754
+ return {
755
+ type: 'authorization_error',
756
+ message: 'API key does not have permission for this operation.',
757
+ code: 'FORBIDDEN',
758
+ recoverable: false,
759
+ suggestion: 'Verify your API key has the correct permissions. Contact support if unexpected.',
760
+ details: { httpStatus: 403 },
761
+ };
762
+ case 404:
763
+ return {
764
+ type: 'not_found_error',
765
+ message: 'API endpoint not found.',
766
+ code: 'ENDPOINT_NOT_FOUND',
767
+ recoverable: false,
768
+ suggestion: 'Check SUPERMODEL_BASE_URL environment variable. Default: https://api.supermodeltools.com',
769
+ details: { baseUrl: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com', httpStatus: 404 },
770
+ };
771
+ case 429:
772
+ return {
773
+ type: 'rate_limit_error',
774
+ message: 'API rate limit exceeded.',
775
+ code: 'RATE_LIMITED',
776
+ recoverable: true,
777
+ suggestion: 'Wait 30-60 seconds and retry. Consider analyzing smaller subdirectories to reduce API calls.',
778
+ details: { httpStatus: 429 },
779
+ };
780
+ case 500:
781
+ case 502:
782
+ case 503:
783
+ case 504:
784
+ return {
785
+ type: 'internal_error',
786
+ message: `Supermodel API server error (HTTP ${status}).`,
787
+ code: 'SERVER_ERROR',
788
+ recoverable: true,
789
+ reportable: true,
790
+ repo: REPORT_REPO,
791
+ 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.',
792
+ details: { httpStatus: status },
793
+ };
794
+ default: {
795
+ const isServerError = status >= 500;
796
+ return {
797
+ type: isServerError ? 'internal_error' : 'validation_error',
798
+ message: `API request failed with HTTP ${status}.`,
799
+ code: 'API_ERROR',
800
+ recoverable: isServerError,
801
+ ...(isServerError && {
802
+ reportable: true,
803
+ repo: REPORT_REPO,
804
+ 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.',
805
+ }),
806
+ ...(!isServerError && { suggestion: 'Check the request parameters and base URL configuration.' }),
807
+ details: { httpStatus: status },
808
+ };
809
+ }
810
+ }
811
+ }
812
+ if (error.request) {
813
+ // Distinguish timeout from general network failure
814
+ if (error.code === 'UND_ERR_HEADERS_TIMEOUT' || error.code === 'UND_ERR_BODY_TIMEOUT' || error.message?.includes('timeout')) {
815
+ return {
816
+ type: 'timeout_error',
817
+ message: 'API request timed out. The codebase may be too large for a single analysis.',
818
+ code: 'REQUEST_TIMEOUT',
819
+ recoverable: true,
820
+ suggestion: 'Analyze a smaller subdirectory (e.g. directory="/repo/src/core") or increase SUPERMODEL_TIMEOUT_MS.',
821
+ };
822
+ }
823
+ return {
824
+ type: 'network_error',
825
+ message: 'No response from Supermodel API server.',
826
+ code: 'NO_RESPONSE',
827
+ recoverable: true,
828
+ suggestion: 'Check network connectivity. Verify the API is reachable at the configured base URL.',
829
+ details: { baseUrl: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com' },
830
+ };
831
+ }
832
+ // Catch-all for unexpected errors - include the actual message
833
+ return {
834
+ type: 'internal_error',
835
+ message: error.message || 'An unexpected error occurred.',
836
+ code: 'UNKNOWN_ERROR',
837
+ recoverable: false,
838
+ reportable: true,
839
+ repo: REPORT_REPO,
840
+ suggestion: REPORT_SUGGESTION,
841
+ details: { errorType: error.name || 'Error' },
842
+ };
843
+ }
673
844
  /**
674
845
  * Legacy mode: direct jq filtering on API response
675
846
  */
@@ -681,43 +852,16 @@ async function handleLegacyMode(client, file, idempotencyKey, jq_filter) {
681
852
  catch (error) {
682
853
  if ((0, filtering_1.isJqError)(error)) {
683
854
  logger.error('jq filter error:', error.message);
684
- return (0, types_1.asErrorResult)(`Invalid jq filter syntax. Check your filter and try again.`);
855
+ return (0, types_1.asErrorResult)({
856
+ type: 'validation_error',
857
+ message: `Invalid jq filter syntax: ${error.message}`,
858
+ code: 'INVALID_JQ_FILTER',
859
+ recoverable: false,
860
+ suggestion: 'Check jq filter syntax. Use the query parameter instead for structured queries (e.g. query="summary").',
861
+ });
685
862
  }
686
863
  // Error details are already logged by fetchFromApi and logErrorResponse
687
- // Return a user-friendly, actionable error message
688
- let errorMessage = '';
689
- if (error.response) {
690
- const status = error.response.status;
691
- switch (status) {
692
- case 401:
693
- errorMessage = 'Authentication failed. Set your SUPERMODEL_API_KEY environment variable and restart the MCP server.';
694
- break;
695
- case 403:
696
- errorMessage = 'Access forbidden. Your API key does not have permission for this operation. Contact support if this is unexpected.';
697
- break;
698
- case 404:
699
- errorMessage = 'API endpoint not found. The service URL may be incorrect. Check your SUPERMODEL_BASE_URL configuration.';
700
- break;
701
- case 429:
702
- errorMessage = 'Rate limit exceeded. Wait a few minutes and try again.';
703
- break;
704
- case 500:
705
- case 502:
706
- case 503:
707
- case 504:
708
- errorMessage = 'Server error. The Supermodel API is temporarily unavailable. Try again in a few minutes.';
709
- break;
710
- default:
711
- errorMessage = `API error (HTTP ${status}). Check the MCP server logs for details.`;
712
- }
713
- }
714
- else if (error.request) {
715
- errorMessage = 'No response from server. Check your network connection and verify the API is reachable.';
716
- }
717
- else {
718
- errorMessage = 'Request failed. Check the MCP server logs for details.';
719
- }
720
- return (0, types_1.asErrorResult)(errorMessage);
864
+ return (0, types_1.asErrorResult)(classifyApiError(error));
721
865
  }
722
866
  }
723
867
  exports.default = { metadata: exports.metadata, tool: exports.tool, handler: exports.handler };
package/dist/types.js CHANGED
@@ -13,12 +13,15 @@ function asTextContentResult(result) {
13
13
  isError: false
14
14
  };
15
15
  }
16
- function asErrorResult(message) {
16
+ function asErrorResult(error) {
17
+ const text = typeof error === 'string'
18
+ ? error
19
+ : JSON.stringify({ error }, null, 2);
17
20
  return {
18
21
  content: [
19
22
  {
20
23
  type: 'text',
21
- text: message,
24
+ text,
22
25
  },
23
26
  ],
24
27
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermodeltools/mcp-server",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
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",