@supermodeltools/mcp-server 0.4.5 → 0.4.8

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
@@ -150,23 +150,11 @@ Analyzes code structure, dependencies, and relationships across a repository. Us
150
150
  |----------|------|----------|-------------|
151
151
  | `directory` | string | Yes* | Path to repository directory (automatic zipping) |
152
152
  | `file` | string | Yes* | Path to pre-zipped archive (deprecated) |
153
- | `Idempotency-Key` | string | Yes | Cache key in format `{repo}:{type}:{hash}` |
154
153
  | `query` | string | No | Query type (summary, search, list_nodes, etc.) |
155
154
  | `jq_filter` | string | No | jq filter for custom data extraction |
156
155
 
157
156
  \* Either `directory` (recommended) or `file` must be provided
158
157
 
159
- **Quick start:**
160
-
161
- ```bash
162
- # 1. Get commit hash for cache key
163
- git rev-parse --short HEAD
164
- # Output: abc123
165
-
166
- # 2. Ask Claude to analyze (no manual zipping needed!)
167
- # "Analyze the codebase at /path/to/repo using key myproject:supermodel:abc123"
168
- ```
169
-
170
158
  **Example prompts:**
171
159
  - "Analyze the codebase at . to understand its architecture"
172
160
  - "Before I refactor the authentication module, analyze this repo to show me what depends on it"
@@ -8,6 +8,7 @@ exports.graphCache = exports.GraphCache = void 0;
8
8
  exports.buildIndexes = buildIndexes;
9
9
  exports.normalizePath = normalizePath;
10
10
  exports.toNodeDescriptor = toNodeDescriptor;
11
+ const constants_1 = require("../constants");
11
12
  /**
12
13
  * Build indexes from raw SupermodelIR response
13
14
  */
@@ -200,10 +201,12 @@ class GraphCache {
200
201
  cache = new Map();
201
202
  maxGraphs;
202
203
  maxNodes;
204
+ maxAgeMs;
203
205
  currentNodes = 0;
204
206
  constructor(options) {
205
- this.maxGraphs = options?.maxGraphs || 20;
206
- this.maxNodes = options?.maxNodes || 1000000;
207
+ this.maxGraphs = options?.maxGraphs || constants_1.DEFAULT_MAX_GRAPHS;
208
+ this.maxNodes = options?.maxNodes || constants_1.DEFAULT_MAX_NODES;
209
+ this.maxAgeMs = options?.maxAgeMs || constants_1.DEFAULT_CACHE_TTL_MS;
207
210
  }
208
211
  get(cacheKey) {
209
212
  const entry = this.cache.get(cacheKey);
@@ -216,16 +219,20 @@ class GraphCache {
216
219
  }
217
220
  set(cacheKey, graph) {
218
221
  const nodeCount = graph.summary.nodeCount;
222
+ // Evict stale entries first
223
+ this.evictStale();
219
224
  // Evict if needed
220
225
  while ((this.cache.size >= this.maxGraphs || this.currentNodes + nodeCount > this.maxNodes) &&
221
226
  this.cache.size > 0) {
222
227
  this.evictOldest();
223
228
  }
224
229
  // Store
230
+ const now = Date.now();
225
231
  this.cache.set(cacheKey, {
226
232
  graph,
227
233
  nodeCount,
228
- lastAccessed: Date.now(),
234
+ lastAccessed: now,
235
+ createdAt: now,
229
236
  });
230
237
  this.currentNodes += nodeCount;
231
238
  }
@@ -247,6 +254,28 @@ class GraphCache {
247
254
  this.cache.delete(oldestKey);
248
255
  }
249
256
  }
257
+ /**
258
+ * Evict all cache entries that have exceeded their TTL (maxAgeMs)
259
+ * This method can be called manually or is automatically invoked before adding new entries
260
+ * @returns Number of entries evicted
261
+ */
262
+ evictStale() {
263
+ const now = Date.now();
264
+ const keysToEvict = [];
265
+ // Find all stale entries
266
+ for (const [key, entry] of this.cache) {
267
+ if (now - entry.createdAt > this.maxAgeMs) {
268
+ keysToEvict.push(key);
269
+ }
270
+ }
271
+ // Evict them
272
+ for (const key of keysToEvict) {
273
+ const entry = this.cache.get(key);
274
+ this.currentNodes -= entry.nodeCount;
275
+ this.cache.delete(key);
276
+ }
277
+ return keysToEvict.length;
278
+ }
250
279
  status() {
251
280
  return {
252
281
  graphs: this.cache.size,
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * Application-wide constants
4
+ * Single source of truth for configuration values
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MAX_NEIGHBORHOOD_DEPTH = exports.DEFAULT_QUERY_LIMIT = exports.DEFAULT_CACHE_TTL_MS = exports.DEFAULT_MAX_NODES = exports.DEFAULT_MAX_GRAPHS = exports.MAX_ZIP_SIZE_BYTES = exports.ZIP_CLEANUP_AGE_MS = exports.CONNECTION_TIMEOUT_MS = exports.DEFAULT_API_TIMEOUT_MS = void 0;
8
+ // HTTP timeout configuration
9
+ exports.DEFAULT_API_TIMEOUT_MS = 900_000; // 15 minutes (complex repos can take 10+ min)
10
+ exports.CONNECTION_TIMEOUT_MS = 30_000; // 30 seconds to establish connection
11
+ // ZIP configuration
12
+ exports.ZIP_CLEANUP_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
13
+ exports.MAX_ZIP_SIZE_BYTES = 500 * 1024 * 1024; // 500MB default
14
+ // Cache configuration
15
+ exports.DEFAULT_MAX_GRAPHS = 20; // Maximum number of graphs to cache
16
+ exports.DEFAULT_MAX_NODES = 1_000_000; // Maximum total nodes across all cached graphs
17
+ exports.DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour - time-to-live for cached graphs
18
+ // Query defaults
19
+ exports.DEFAULT_QUERY_LIMIT = 200; // Default result limit for queries
20
+ exports.MAX_NEIGHBORHOOD_DEPTH = 3; // Maximum traversal depth for neighborhood queries
package/dist/index.js CHANGED
@@ -1,12 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
37
  const server_1 = require("./server");
38
+ const logger = __importStar(require("./utils/logger"));
5
39
  async function main() {
6
40
  const server = new server_1.Server();
7
41
  await server.start();
8
42
  }
9
43
  main().catch((error) => {
10
- console.error('Fatal error:', error);
44
+ logger.error('Fatal error:', error);
11
45
  process.exit(1);
12
46
  });
@@ -8,17 +8,22 @@ exports.search = search;
8
8
  exports.listNodes = listNodes;
9
9
  const graph_cache_1 = require("../cache/graph-cache");
10
10
  const types_1 = require("./types");
11
- const DEFAULT_LIMIT = 200;
11
+ const constants_1 = require("../constants");
12
+ const DEFAULT_LIMIT = constants_1.DEFAULT_QUERY_LIMIT;
12
13
  /**
13
14
  * get_node - Return full details for a specific node ID
14
15
  */
15
16
  function getNode(params, graph, source) {
16
17
  if (!params.targetId) {
17
- return (0, types_1.createError)('INVALID_PARAMS', 'targetId is required for get_node query');
18
+ console.error('[ERROR] get_node called without targetId');
19
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: targetId', {
20
+ detail: 'Use search or list_nodes to find valid node IDs, then call get_node with the targetId',
21
+ });
18
22
  }
19
23
  const node = graph.nodeById.get(params.targetId);
20
24
  if (!node) {
21
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`, {
25
+ console.error('[ERROR] Node not found:', params.targetId);
26
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
22
27
  detail: 'Use search or list_nodes to discover valid node IDs',
23
28
  });
24
29
  }
@@ -36,7 +41,10 @@ function getNode(params, graph, source) {
36
41
  */
37
42
  function search(params, graph, source) {
38
43
  if (!params.searchText) {
39
- return (0, types_1.createError)('INVALID_PARAMS', 'searchText is required for search query');
44
+ console.error('[ERROR] search called without searchText');
45
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: searchText', {
46
+ detail: 'Provide a search term to find nodes by name substring',
47
+ });
40
48
  }
41
49
  const searchLower = params.searchText.toLowerCase();
42
50
  const limit = params.limit || DEFAULT_LIMIT;
@@ -94,7 +102,14 @@ function listNodes(params, graph, source) {
94
102
  regex = new RegExp(namePattern, 'i');
95
103
  }
96
104
  catch (e) {
97
- return (0, types_1.createError)('INVALID_PARAMS', `Invalid namePattern regex: ${namePattern}`);
105
+ // Log detailed error for debugging
106
+ const errorMsg = e instanceof Error ? e.message : String(e);
107
+ console.error('[ERROR] Invalid regex pattern');
108
+ console.error('[ERROR] Pattern:', namePattern);
109
+ console.error('[ERROR] Error:', errorMsg);
110
+ return (0, types_1.createError)('INVALID_PARAMS', `Invalid regex pattern: ${errorMsg}`, {
111
+ detail: `Pattern: ${namePattern}`,
112
+ });
98
113
  }
99
114
  }
100
115
  const results = [];
@@ -54,9 +54,10 @@ async function executeQuery(params, apiResponse) {
54
54
  }
55
55
  // All other queries require a graph
56
56
  if (!graph) {
57
- return (0, types_1.createError)('CACHE_MISS', `Graph not found in cache for key '${cacheKey}'`, {
57
+ console.error('[ERROR] Cache miss for key:', cacheKey);
58
+ return (0, types_1.createError)('CACHE_MISS', `Graph not cached. The query engine will fetch it from the API.`, {
58
59
  retryable: true,
59
- detail: 'Re-call with the same file and idempotencyKey to fetch from API',
60
+ detail: 'This is a normal first-time operation. The graph will be cached for subsequent queries.',
60
61
  });
61
62
  }
62
63
  // Dispatch to appropriate handler
@@ -99,9 +100,15 @@ function dispatchQuery(params, graph, source) {
99
100
  // Not implemented yet
100
101
  case 'uses_in_file':
101
102
  case 'list_files_in_dir':
102
- return (0, types_1.createError)('INVALID_QUERY', `Query type '${params.query}' is not yet implemented`);
103
+ console.error('[ERROR] Unimplemented query type requested:', params.query);
104
+ return (0, types_1.createError)('INVALID_QUERY', `Query type '${params.query}' is not yet implemented`, {
105
+ detail: 'This query is planned for a future release',
106
+ });
103
107
  default:
104
- return (0, types_1.createError)('INVALID_QUERY', `Unknown query type: ${params.query}`);
108
+ console.error('[ERROR] Unknown query type:', params.query);
109
+ return (0, types_1.createError)('INVALID_QUERY', `Unknown query type: ${params.query}`, {
110
+ detail: 'Use graph_status to see available query types',
111
+ });
105
112
  }
106
113
  }
107
114
  /**
@@ -129,8 +136,12 @@ async function executeJqQuery(params, graph, source) {
129
136
  }
130
137
  catch (error) {
131
138
  const message = error instanceof Error ? error.message : String(error);
132
- return (0, types_1.createError)('BAD_JQ', `jq filter error: ${message}`, {
133
- detail: params.jq_filter,
139
+ // Log detailed error for debugging
140
+ console.error('[ERROR] jq filter execution failed');
141
+ console.error('[ERROR] Filter:', params.jq_filter);
142
+ console.error('[ERROR] Error:', message);
143
+ return (0, types_1.createError)('BAD_JQ', `Invalid jq filter syntax. ${message}`, {
144
+ detail: `Filter: ${params.jq_filter}`,
134
145
  });
135
146
  }
136
147
  }
@@ -144,63 +155,63 @@ function getAvailableQueries() {
144
155
  {
145
156
  query: 'graph_status',
146
157
  description: 'Check if graph is cached and get summary stats',
147
- requiredParams: ['file', 'idempotencyKey'],
158
+ requiredParams: ['idempotencyKey'],
148
159
  optionalParams: [],
149
160
  phase: 'v1',
150
161
  },
151
162
  {
152
163
  query: 'summary',
153
164
  description: 'Get high-level stats about the codebase',
154
- requiredParams: ['file', 'idempotencyKey'],
165
+ requiredParams: ['idempotencyKey'],
155
166
  optionalParams: [],
156
167
  phase: 'v1',
157
168
  },
158
169
  {
159
170
  query: 'get_node',
160
171
  description: 'Get full details for a specific node by ID',
161
- requiredParams: ['file', 'idempotencyKey', 'targetId'],
172
+ requiredParams: ['idempotencyKey', 'targetId'],
162
173
  optionalParams: [],
163
174
  phase: 'v1',
164
175
  },
165
176
  {
166
177
  query: 'search',
167
178
  description: 'Search nodes by name substring',
168
- requiredParams: ['file', 'idempotencyKey', 'searchText'],
179
+ requiredParams: ['idempotencyKey', 'searchText'],
169
180
  optionalParams: ['labels', 'filePathPrefix', 'limit'],
170
181
  phase: 'v1',
171
182
  },
172
183
  {
173
184
  query: 'list_nodes',
174
185
  description: 'List nodes with filters (labels, namePattern, filePathPrefix)',
175
- requiredParams: ['file', 'idempotencyKey'],
186
+ requiredParams: ['idempotencyKey'],
176
187
  optionalParams: ['labels', 'namePattern', 'filePathPrefix', 'searchText', 'limit'],
177
188
  phase: 'v1',
178
189
  },
179
190
  {
180
191
  query: 'function_calls_in',
181
192
  description: 'Find all callers of a function',
182
- requiredParams: ['file', 'idempotencyKey', 'targetId'],
193
+ requiredParams: ['idempotencyKey', 'targetId'],
183
194
  optionalParams: ['limit'],
184
195
  phase: 'v1',
185
196
  },
186
197
  {
187
198
  query: 'function_calls_out',
188
199
  description: 'Find all functions called by a function',
189
- requiredParams: ['file', 'idempotencyKey', 'targetId'],
200
+ requiredParams: ['idempotencyKey', 'targetId'],
190
201
  optionalParams: ['limit'],
191
202
  phase: 'v1',
192
203
  },
193
204
  {
194
205
  query: 'definitions_in_file',
195
206
  description: 'Get all classes, functions, types defined in a file',
196
- requiredParams: ['file', 'idempotencyKey'],
207
+ requiredParams: ['idempotencyKey'],
197
208
  optionalParams: ['targetId', 'filePathPrefix', 'limit'],
198
209
  phase: 'v1',
199
210
  },
200
211
  {
201
212
  query: 'jq',
202
213
  description: 'Execute raw jq filter (escape hatch)',
203
- requiredParams: ['file', 'idempotencyKey', 'jq_filter'],
214
+ requiredParams: ['idempotencyKey', 'jq_filter'],
204
215
  optionalParams: [],
205
216
  phase: 'v1',
206
217
  },
@@ -208,28 +219,28 @@ function getAvailableQueries() {
208
219
  {
209
220
  query: 'file_imports',
210
221
  description: 'Get imports for a file (outgoing and incoming)',
211
- requiredParams: ['file', 'idempotencyKey', 'targetId'],
222
+ requiredParams: ['idempotencyKey', 'targetId'],
212
223
  optionalParams: ['limit'],
213
224
  phase: 'v1.1',
214
225
  },
215
226
  {
216
227
  query: 'domain_map',
217
228
  description: 'List all domains with relationships',
218
- requiredParams: ['file', 'idempotencyKey'],
229
+ requiredParams: ['idempotencyKey'],
219
230
  optionalParams: [],
220
231
  phase: 'v1.1',
221
232
  },
222
233
  {
223
234
  query: 'domain_membership',
224
235
  description: 'Get members of a domain',
225
- requiredParams: ['file', 'idempotencyKey'],
236
+ requiredParams: ['idempotencyKey'],
226
237
  optionalParams: ['targetId', 'searchText', 'limit'],
227
238
  phase: 'v1.1',
228
239
  },
229
240
  {
230
241
  query: 'neighborhood',
231
242
  description: 'Get ego graph around a node',
232
- requiredParams: ['file', 'idempotencyKey', 'targetId'],
243
+ requiredParams: ['idempotencyKey', 'targetId'],
233
244
  optionalParams: ['depth', 'relationshipTypes', 'limit'],
234
245
  phase: 'v1.1',
235
246
  },
@@ -12,23 +12,32 @@ exports.domainMembership = domainMembership;
12
12
  exports.neighborhood = neighborhood;
13
13
  const graph_cache_1 = require("../cache/graph-cache");
14
14
  const types_1 = require("./types");
15
- const DEFAULT_LIMIT = 200;
15
+ const constants_1 = require("../constants");
16
+ const DEFAULT_LIMIT = constants_1.DEFAULT_QUERY_LIMIT;
16
17
  /**
17
18
  * function_calls_in - Find all callers of a function
18
19
  */
19
20
  function functionCallsIn(params, graph, source) {
20
21
  if (!params.targetId) {
21
- return (0, types_1.createError)('INVALID_PARAMS', 'targetId is required for function_calls_in query');
22
+ console.error('[ERROR] function_calls_in called without targetId');
23
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: targetId', {
24
+ detail: 'Use search or list_nodes with labels=["Function"] to find function IDs',
25
+ });
22
26
  }
23
27
  // Verify target exists and is a function
24
28
  const targetNode = graph.nodeById.get(params.targetId);
25
29
  if (!targetNode) {
26
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`, {
30
+ console.error('[ERROR] Node not found:', params.targetId);
31
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
27
32
  detail: 'Use search or list_nodes with labels=["Function"] to discover function IDs',
28
33
  });
29
34
  }
30
35
  if (targetNode.labels?.[0] !== 'Function') {
31
- return (0, types_1.createError)('INVALID_PARAMS', `Node '${params.targetId}' is not a Function (got ${targetNode.labels?.[0]})`);
36
+ const actualLabel = targetNode.labels?.[0] || 'unknown';
37
+ console.error('[ERROR] Node is not a Function:', params.targetId, 'is', actualLabel);
38
+ return (0, types_1.createError)('INVALID_PARAMS', `Node is not a Function (found ${actualLabel})`, {
39
+ detail: 'This query only works on Function nodes. Use search with labels=["Function"] to find functions',
40
+ });
32
41
  }
33
42
  const adj = graph.callAdj.get(params.targetId);
34
43
  if (!adj) {
@@ -60,17 +69,25 @@ function functionCallsIn(params, graph, source) {
60
69
  */
61
70
  function functionCallsOut(params, graph, source) {
62
71
  if (!params.targetId) {
63
- return (0, types_1.createError)('INVALID_PARAMS', 'targetId is required for function_calls_out query');
72
+ console.error('[ERROR] function_calls_out called without targetId');
73
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: targetId', {
74
+ detail: 'Use search or list_nodes with labels=["Function"] to find function IDs',
75
+ });
64
76
  }
65
77
  // Verify target exists and is a function
66
78
  const targetNode = graph.nodeById.get(params.targetId);
67
79
  if (!targetNode) {
68
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`, {
80
+ console.error('[ERROR] Node not found:', params.targetId);
81
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
69
82
  detail: 'Use search or list_nodes with labels=["Function"] to discover function IDs',
70
83
  });
71
84
  }
72
85
  if (targetNode.labels?.[0] !== 'Function') {
73
- return (0, types_1.createError)('INVALID_PARAMS', `Node '${params.targetId}' is not a Function (got ${targetNode.labels?.[0]})`);
86
+ const actualLabel = targetNode.labels?.[0] || 'unknown';
87
+ console.error('[ERROR] Node is not a Function:', params.targetId, 'is', actualLabel);
88
+ return (0, types_1.createError)('INVALID_PARAMS', `Node is not a Function (found ${actualLabel})`, {
89
+ detail: 'This query only works on Function nodes. Use search with labels=["Function"] to find functions',
90
+ });
74
91
  }
75
92
  const adj = graph.callAdj.get(params.targetId);
76
93
  if (!adj) {
@@ -106,10 +123,17 @@ function definitionsInFile(params, graph, source) {
106
123
  if (params.targetId) {
107
124
  const targetNode = graph.nodeById.get(params.targetId);
108
125
  if (!targetNode) {
109
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`);
126
+ console.error('[ERROR] Node not found:', params.targetId);
127
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
128
+ detail: 'Use search or list_nodes with labels=["File"] to find file nodes',
129
+ });
110
130
  }
111
131
  if (targetNode.labels?.[0] !== 'File') {
112
- return (0, types_1.createError)('INVALID_PARAMS', `Node '${params.targetId}' is not a File (got ${targetNode.labels?.[0]})`);
132
+ const actualLabel = targetNode.labels?.[0] || 'unknown';
133
+ console.error('[ERROR] Node is not a File:', params.targetId, 'is', actualLabel);
134
+ return (0, types_1.createError)('INVALID_PARAMS', `Node is not a File (found ${actualLabel})`, {
135
+ detail: 'This query requires a File node. Use search with labels=["File"] to find files',
136
+ });
113
137
  }
114
138
  filePath = targetNode.properties?.filePath || targetNode.properties?.path;
115
139
  }
@@ -117,10 +141,16 @@ function definitionsInFile(params, graph, source) {
117
141
  filePath = params.filePathPrefix;
118
142
  }
119
143
  else {
120
- return (0, types_1.createError)('INVALID_PARAMS', 'Either targetId (file node ID) or filePathPrefix is required');
144
+ console.error('[ERROR] definitions_in_file called without targetId or filePathPrefix');
145
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: targetId or filePathPrefix', {
146
+ detail: 'Provide either a file node ID (targetId) or a file path (filePathPrefix)',
147
+ });
121
148
  }
122
149
  if (!filePath) {
123
- return (0, types_1.createError)('INVALID_PARAMS', 'Could not determine file path');
150
+ console.error('[ERROR] Could not determine file path from node');
151
+ return (0, types_1.createError)('INVALID_PARAMS', 'Could not determine file path from the provided node', {
152
+ detail: 'The node is missing filePath or path properties',
153
+ });
124
154
  }
125
155
  const normalizedPath = (0, graph_cache_1.normalizePath)(filePath);
126
156
  const pathEntry = graph.pathIndex.get(normalizedPath);
@@ -134,8 +164,9 @@ function definitionsInFile(params, graph, source) {
134
164
  }
135
165
  }
136
166
  if (resolvedPath === normalizedPath) {
137
- return (0, types_1.createError)('NOT_FOUND', `No definitions found for file '${filePath}'`, {
138
- detail: 'File may not exist in the analyzed codebase or has no class/function/type definitions',
167
+ console.error('[ERROR] File not found in codebase:', filePath);
168
+ return (0, types_1.createError)('NOT_FOUND', `File not found in analyzed codebase: ${filePath}`, {
169
+ detail: 'The file may not exist, may be excluded by .gitignore, or has no definitions',
139
170
  });
140
171
  }
141
172
  }
@@ -173,15 +204,24 @@ function definitionsInFile(params, graph, source) {
173
204
  */
174
205
  function fileImports(params, graph, source) {
175
206
  if (!params.targetId) {
176
- return (0, types_1.createError)('INVALID_PARAMS', 'targetId is required for file_imports query');
207
+ console.error('[ERROR] file_imports called without targetId');
208
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: targetId', {
209
+ detail: 'Use search or list_nodes with labels=["File"] to find file nodes',
210
+ });
177
211
  }
178
212
  const targetNode = graph.nodeById.get(params.targetId);
179
213
  if (!targetNode) {
180
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`);
214
+ console.error('[ERROR] Node not found:', params.targetId);
215
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
216
+ detail: 'Use search or list_nodes to discover valid node IDs',
217
+ });
181
218
  }
182
219
  const label = targetNode.labels?.[0];
183
220
  if (label !== 'File' && label !== 'LocalModule' && label !== 'ExternalModule') {
184
- return (0, types_1.createError)('INVALID_PARAMS', `Node '${params.targetId}' is not a File/Module (got ${label})`);
221
+ console.error('[ERROR] Node is not a File/Module:', params.targetId, 'is', label);
222
+ return (0, types_1.createError)('INVALID_PARAMS', `Node is not a File/Module (found ${label})`, {
223
+ detail: 'This query requires a File, LocalModule, or ExternalModule node',
224
+ });
185
225
  }
186
226
  const limit = params.limit || DEFAULT_LIMIT;
187
227
  const outIds = new Set();
@@ -275,13 +315,19 @@ function domainMap(params, graph, source) {
275
315
  */
276
316
  function domainMembership(params, graph, source) {
277
317
  if (!params.searchText && !params.targetId) {
278
- return (0, types_1.createError)('INVALID_PARAMS', 'searchText (domain name) or targetId (domain node ID) is required');
318
+ console.error('[ERROR] domain_membership called without searchText or targetId');
319
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: searchText or targetId', {
320
+ detail: 'Provide either a domain name (searchText) or domain node ID (targetId)',
321
+ });
279
322
  }
280
323
  let domainName;
281
324
  if (params.targetId) {
282
325
  const node = graph.nodeById.get(params.targetId);
283
326
  if (!node) {
284
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`);
327
+ console.error('[ERROR] Node not found:', params.targetId);
328
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
329
+ detail: 'Use domain_map to discover domain nodes',
330
+ });
285
331
  }
286
332
  domainName = node.properties?.name;
287
333
  }
@@ -290,7 +336,8 @@ function domainMembership(params, graph, source) {
290
336
  }
291
337
  const domainEntry = graph.domainIndex.get(domainName);
292
338
  if (!domainEntry) {
293
- return (0, types_1.createError)('NOT_FOUND', `Domain '${domainName}' not found`, {
339
+ console.error('[ERROR] Domain not found:', domainName);
340
+ return (0, types_1.createError)('NOT_FOUND', `Domain not found: ${domainName}`, {
294
341
  detail: 'Use domain_map to list available domains',
295
342
  });
296
343
  }
@@ -312,13 +359,19 @@ function domainMembership(params, graph, source) {
312
359
  */
313
360
  function neighborhood(params, graph, source) {
314
361
  if (!params.targetId) {
315
- return (0, types_1.createError)('INVALID_PARAMS', 'targetId is required for neighborhood query');
362
+ console.error('[ERROR] neighborhood called without targetId');
363
+ return (0, types_1.createError)('INVALID_PARAMS', 'Missing required parameter: targetId', {
364
+ detail: 'Use search or list_nodes to find a node, then explore its neighborhood',
365
+ });
316
366
  }
317
367
  const targetNode = graph.nodeById.get(params.targetId);
318
368
  if (!targetNode) {
319
- return (0, types_1.createError)('NOT_FOUND', `Node with id '${params.targetId}' not found`);
369
+ console.error('[ERROR] Node not found:', params.targetId);
370
+ return (0, types_1.createError)('NOT_FOUND', `Node not found: ${params.targetId}`, {
371
+ detail: 'Use search or list_nodes to discover valid node IDs',
372
+ });
320
373
  }
321
- const depth = Math.min(params.depth || 1, 3); // Cap at 3 to prevent explosion
374
+ const depth = Math.min(params.depth || 1, constants_1.MAX_NEIGHBORHOOD_DEPTH);
322
375
  const limit = params.limit || 100;
323
376
  // Only include relationship types we actually implement
324
377
  const relationshipTypes = params.relationshipTypes || ['calls', 'IMPORTS'];