@supermodeltools/mcp-server 0.9.2 → 0.9.3

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/dist/constants.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Single source of truth for configuration values
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.MAX_SYMBOL_RELATED = exports.MAX_SYMBOL_CALLEES = exports.MAX_SYMBOL_CALLERS = exports.MAX_OVERVIEW_HUB_FUNCTIONS = exports.MAX_OVERVIEW_DOMAINS = 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;
7
+ exports.MAX_SOURCE_LINES = exports.MAX_SYMBOL_RELATED = exports.MAX_SYMBOL_CALLEES = exports.MAX_SYMBOL_CALLERS = exports.MAX_OVERVIEW_HUB_FUNCTIONS = exports.MAX_OVERVIEW_DOMAINS = 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
8
  // HTTP timeout configuration
9
9
  exports.DEFAULT_API_TIMEOUT_MS = 900_000; // 15 minutes (complex repos can take 10+ min)
10
10
  exports.CONNECTION_TIMEOUT_MS = 30_000; // 30 seconds to establish connection
@@ -22,3 +22,4 @@ exports.MAX_OVERVIEW_HUB_FUNCTIONS = 10; // Top N hub functions to show
22
22
  exports.MAX_SYMBOL_CALLERS = 10; // Top N callers to show
23
23
  exports.MAX_SYMBOL_CALLEES = 10; // Top N callees to show
24
24
  exports.MAX_SYMBOL_RELATED = 8; // Related symbols in same file
25
+ exports.MAX_SOURCE_LINES = 50; // Max lines of source code to include inline
package/dist/server.js CHANGED
@@ -91,21 +91,21 @@ class Server {
91
91
 
92
92
  Two tools for instant codebase understanding. Pre-computed graphs enable sub-second responses.
93
93
 
94
- ## \`overview\` — Start here
95
- Get the architecture map: domains, key files, hub functions, file/class/function counts.
96
- Call this first on any new task to understand WHERE in the codebase to look.
94
+ ## Recommended workflow
95
+ 1. \`overview\` first to learn architecture and key symbols (1 call)
96
+ 2. \`symbol_context\` on 1-2 symbols from the issue to see source, callers, callees (1-2 calls)
97
+ 3. Stop calling MCP tools. Use the results to make your fix.
97
98
 
98
- ## \`symbol_context\` — Deep dive on a symbol
99
- Given a function, class, or method name, get its definition location, callers, callees, domain membership, and related symbols in the same file.
100
- Use this when you know WHAT to investigate (e.g. from an issue description, stack trace, or grep result).
101
- Supports partial matching and "ClassName.method" syntax.
99
+ ## Anti-patterns
100
+ - >3 MCP calls total = diminishing returns. Aim for 1 overview + 1-2 symbol lookups.
101
+ - Chasing callers-of-callers burns iterations without helping.
102
102
 
103
- ## Recommended workflow
104
- 1. Call \`overview\` to understand the codebase architecture
105
- 2. Read the issue/bug description and identify relevant domains and symbols
106
- 3. Call \`symbol_context\` on key symbols to understand their structural context
107
- 4. Use Read/Grep to examine the actual source code at the identified locations
108
- 5. Make your fix and verify with tests`,
103
+ ## After fixing
104
+ Run the project's existing test suite (e.g. pytest). Do NOT write standalone test scripts.
105
+
106
+ ## Tool reference
107
+ - \`overview\`: Architecture map, domains, hub functions, file counts. Zero-arg, sub-second.
108
+ - \`symbol_context\`: Source, callers, callees, domain for any function/class/method. Supports "Class.method" and partial matching.`,
109
109
  });
110
110
  const config = new sdk_1.Configuration({
111
111
  basePath: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com',
@@ -17,7 +17,7 @@ const api_helpers_1 = require("../utils/api-helpers");
17
17
  const constants_1 = require("../constants");
18
18
  exports.tool = {
19
19
  name: 'overview',
20
- description: `CALL THIS FIRST before grep or find. Returns a pre-computed architectural map of the entire codebase in sub-second time at zero cost. Gives you what grep/find cannot: which domains own which files, the most-called hub functions (call graph centrality), and how the codebase is structured across domains. Output is a concise summary with top architectural domains and their key files, highest-traffic functions, and file/function/class counts. Use this to know exactly where to look instead of guessing with grep.`,
20
+ description: `Returns a pre-computed architectural map of the entire codebase: which domains own which files, the most-called hub functions (call graph centrality), file/function/class counts. Sub-second, zero cost. Useful when you need to understand the overall structure before diving in. Skip this if you already know what file or symbol to investigate use symbol_context or file reading instead.`,
21
21
  inputSchema: {
22
22
  type: 'object',
23
23
  properties: {
@@ -2,8 +2,9 @@
2
2
  /**
3
3
  * `symbol_context` tool -- deep dive on a specific symbol.
4
4
  *
5
- * Given a function, class, or method name, returns (<5KB markdown):
5
+ * Given a function, class, or method name, returns (<10KB markdown):
6
6
  * - Definition location (file, line)
7
+ * - Source code (up to MAX_SOURCE_LINES)
7
8
  * - Callers (who calls this)
8
9
  * - Callees (what this calls)
9
10
  * - Domain membership
@@ -11,15 +12,53 @@
11
12
  *
12
13
  * Backed by pre-computed graphs (sub-second) with on-demand API fallback.
13
14
  */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
14
48
  Object.defineProperty(exports, "__esModule", { value: true });
15
49
  exports.handler = exports.tool = void 0;
50
+ exports.findSymbol = findSymbol;
51
+ exports.renderSymbolContext = renderSymbolContext;
52
+ exports.languageFromExtension = languageFromExtension;
53
+ const fs_1 = require("fs");
54
+ const path = __importStar(require("path"));
16
55
  const types_1 = require("../types");
17
56
  const graph_cache_1 = require("../cache/graph-cache");
18
57
  const api_helpers_1 = require("../utils/api-helpers");
19
58
  const constants_1 = require("../constants");
20
59
  exports.tool = {
21
60
  name: 'symbol_context',
22
- description: `Strictly better than grep for understanding a function, class, or method. Given a symbol name, instantly returns its definition location, all callers, all callees, architectural domain, and related symbols in the same file -- structural context that grep cannot reconstruct. Sub-second, zero cost. Supports partial matching ("filter" finds "QuerySet.filter", "filter_queryset", etc.) and "ClassName.method" syntax. Use this whenever you have a symbol name from a stack trace, issue, or search result and need to understand how it connects to the rest of the codebase.`,
61
+ description: `Strictly better than grep for understanding a function, class, or method. Given a symbol name, instantly returns its source code, definition location, all callers, all callees, architectural domain, and related symbols in the same file -- structural context that grep cannot reconstruct. Sub-second, zero cost. Supports partial matching ("filter" finds "QuerySet.filter", "filter_queryset", etc.) and "ClassName.method" syntax. Use this whenever you have a symbol name from a stack trace, issue, or search result and need to understand how it connects to the rest of the codebase.`,
23
62
  inputSchema: {
24
63
  type: 'object',
25
64
  properties: {
@@ -74,10 +113,8 @@ const handler = async (client, args, defaultWorkdir) => {
74
113
  `- Use the \`overview\` tool to see available domains and key functions`);
75
114
  }
76
115
  // Render results for top matches (usually 1, sometimes a few)
77
- const rendered = matches
78
- .slice(0, 3)
79
- .map(node => renderSymbolContext(graph, node))
80
- .join('\n---\n\n');
116
+ const renderedParts = await Promise.all(matches.slice(0, 3).map(node => renderSymbolContext(graph, node, directory)));
117
+ const rendered = renderedParts.join('\n---\n\n');
81
118
  if (matches.length > 3) {
82
119
  return (0, types_1.asTextContentResult)(rendered + `\n\n*... and ${matches.length - 3} more matches. Use a more specific name to narrow results.*`);
83
120
  }
@@ -101,7 +138,12 @@ function findSymbol(graph, query) {
101
138
  return exactIds
102
139
  .map(id => graph.nodeById.get(id))
103
140
  .filter(n => n && isCodeSymbol(n))
104
- .sort((a, b) => symbolPriority(a) - symbolPriority(b));
141
+ .sort((a, b) => {
142
+ const pDiff = symbolPriority(a) - symbolPriority(b);
143
+ if (pDiff !== 0)
144
+ return pDiff;
145
+ return callerCount(graph, b) - callerCount(graph, a);
146
+ });
105
147
  }
106
148
  // Strategy 2: ClassName.method match
107
149
  if (className && methodName) {
@@ -117,13 +159,21 @@ function findSymbol(graph, query) {
117
159
  return fp && classFilePaths.has(fp);
118
160
  });
119
161
  if (matched.length > 0) {
120
- return matched.sort((a, b) => symbolPriority(a) - symbolPriority(b));
162
+ return matched.sort((a, b) => {
163
+ const pDiff = symbolPriority(a) - symbolPriority(b);
164
+ if (pDiff !== 0)
165
+ return pDiff;
166
+ return callerCount(graph, b) - callerCount(graph, a);
167
+ });
121
168
  }
122
169
  }
123
170
  // Strategy 3: Substring match (for partial names)
171
+ if (lowerQuery.length < 2) {
172
+ return [];
173
+ }
124
174
  const substringMatches = [];
125
175
  for (const [name, ids] of graph.nameIndex) {
126
- if (name.includes(lowerQuery) || lowerQuery.includes(name)) {
176
+ if (name.includes(lowerQuery)) {
127
177
  for (const id of ids) {
128
178
  const node = graph.nodeById.get(id);
129
179
  if (node && isCodeSymbol(node)) {
@@ -132,7 +182,7 @@ function findSymbol(graph, query) {
132
182
  }
133
183
  }
134
184
  }
135
- // Sort by relevance: exact prefix > contains > contained-in
185
+ // Sort by relevance: exact prefix > contains, then symbol priority, then caller count
136
186
  substringMatches.sort((a, b) => {
137
187
  const aName = (a.properties?.name || '').toLowerCase();
138
188
  const bName = (b.properties?.name || '').toLowerCase();
@@ -140,7 +190,10 @@ function findSymbol(graph, query) {
140
190
  const bPrefix = bName.startsWith(lowerQuery) ? 0 : 1;
141
191
  if (aPrefix !== bPrefix)
142
192
  return aPrefix - bPrefix;
143
- return symbolPriority(a) - symbolPriority(b);
193
+ const pDiff = symbolPriority(a) - symbolPriority(b);
194
+ if (pDiff !== 0)
195
+ return pDiff;
196
+ return callerCount(graph, b) - callerCount(graph, a);
144
197
  });
145
198
  return substringMatches.slice(0, 10);
146
199
  }
@@ -158,8 +211,11 @@ function symbolPriority(node) {
158
211
  return 2;
159
212
  return 3;
160
213
  }
214
+ function callerCount(graph, node) {
215
+ return graph.callAdj.get(node.id)?.in.length || 0;
216
+ }
161
217
  // ── Rendering ──
162
- function renderSymbolContext(graph, node) {
218
+ async function renderSymbolContext(graph, node, directory) {
163
219
  const name = node.properties?.name || '(unknown)';
164
220
  const rawFilePath = node.properties?.filePath || '';
165
221
  const filePath = (0, graph_cache_1.normalizePath)(rawFilePath);
@@ -179,6 +235,31 @@ function renderSymbolContext(graph, node) {
179
235
  lines.push(`**Domain:** ${domain}`);
180
236
  }
181
237
  lines.push('');
238
+ // Source code
239
+ if (filePath && startLine > 0) {
240
+ try {
241
+ const absPath = path.resolve(directory, filePath);
242
+ const content = await fs_1.promises.readFile(absPath, 'utf-8');
243
+ const fileLines = content.split('\n');
244
+ const end = endLine > 0 ? Math.min(endLine, startLine + constants_1.MAX_SOURCE_LINES - 1) : startLine + constants_1.MAX_SOURCE_LINES - 1;
245
+ const sourceSlice = fileLines.slice(startLine - 1, end);
246
+ if (sourceSlice.length > 0) {
247
+ const lang = languageFromExtension(filePath);
248
+ lines.push(`### Source`);
249
+ lines.push('');
250
+ lines.push(`\`\`\`${lang}`);
251
+ lines.push(sourceSlice.join('\n'));
252
+ lines.push('```');
253
+ if (endLine > 0 && endLine > startLine + constants_1.MAX_SOURCE_LINES - 1) {
254
+ lines.push(`*... truncated (showing ${constants_1.MAX_SOURCE_LINES} of ${endLine - startLine + 1} lines)*`);
255
+ }
256
+ lines.push('');
257
+ }
258
+ }
259
+ catch {
260
+ // File unreadable — skip source section silently
261
+ }
262
+ }
182
263
  // Callers
183
264
  const adj = graph.callAdj.get(node.id);
184
265
  if (adj && adj.in.length > 0) {
@@ -275,6 +356,48 @@ function renderSymbolContext(graph, node) {
275
356
  }
276
357
  return lines.join('\n');
277
358
  }
359
+ function languageFromExtension(filePath) {
360
+ const ext = path.extname(filePath).toLowerCase();
361
+ const map = {
362
+ '.ts': 'typescript',
363
+ '.tsx': 'typescript',
364
+ '.js': 'javascript',
365
+ '.jsx': 'javascript',
366
+ '.py': 'python',
367
+ '.rb': 'ruby',
368
+ '.go': 'go',
369
+ '.rs': 'rust',
370
+ '.java': 'java',
371
+ '.kt': 'kotlin',
372
+ '.cs': 'csharp',
373
+ '.cpp': 'cpp',
374
+ '.c': 'c',
375
+ '.h': 'c',
376
+ '.hpp': 'cpp',
377
+ '.swift': 'swift',
378
+ '.php': 'php',
379
+ '.scala': 'scala',
380
+ '.sh': 'bash',
381
+ '.bash': 'bash',
382
+ '.yaml': 'yaml',
383
+ '.yml': 'yaml',
384
+ '.json': 'json',
385
+ '.xml': 'xml',
386
+ '.html': 'html',
387
+ '.css': 'css',
388
+ '.sql': 'sql',
389
+ '.r': 'r',
390
+ '.lua': 'lua',
391
+ '.dart': 'dart',
392
+ '.ex': 'elixir',
393
+ '.exs': 'elixir',
394
+ '.erl': 'erlang',
395
+ '.hs': 'haskell',
396
+ '.ml': 'ocaml',
397
+ '.clj': 'clojure',
398
+ };
399
+ return map[ext] || '';
400
+ }
278
401
  function findDomain(graph, nodeId) {
279
402
  for (const [domainName, data] of graph.domainIndex) {
280
403
  if (data.memberIds.includes(nodeId)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermodeltools/mcp-server",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
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.9.2",
41
+ "@supermodeltools/sdk": "0.9.3",
42
42
  "archiver": "^7.0.1",
43
43
  "ignore": "^7.0.5",
44
44
  "jq-web": "^0.6.2",