@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 +2 -1
- package/dist/server.js +13 -13
- package/dist/tools/overview.js +1 -1
- package/dist/tools/symbol-context.js +135 -12
- package/package.json +2 -2
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
|
-
##
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
##
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
##
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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',
|
package/dist/tools/overview.js
CHANGED
|
@@ -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: `
|
|
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 (<
|
|
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
|
|
78
|
-
|
|
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) =>
|
|
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) =>
|
|
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)
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|