@supermodeltools/mcp-server 0.9.4 → 0.9.5
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 +6 -2
- package/dist/server.js +49 -13
- package/dist/tools/overview.js +4 -0
- package/dist/tools/symbol-context.js +100 -22
- 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_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;
|
|
7
|
+
exports.MAX_BRIEF_CALLEES = exports.MAX_BRIEF_CALLERS = exports.MAX_BATCH_SYMBOLS = 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,4 +22,8 @@ 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 =
|
|
25
|
+
exports.MAX_SOURCE_LINES = 80; // Max lines of source code to include inline
|
|
26
|
+
// Batch symbol_context limits
|
|
27
|
+
exports.MAX_BATCH_SYMBOLS = 10; // Max symbols per batch call
|
|
28
|
+
exports.MAX_BRIEF_CALLERS = 5; // Callers shown in brief mode
|
|
29
|
+
exports.MAX_BRIEF_CALLEES = 5; // Callees shown in brief mode
|
package/dist/server.js
CHANGED
|
@@ -40,7 +40,7 @@ exports.Server = void 0;
|
|
|
40
40
|
/**
|
|
41
41
|
* MCP Server implementation for the Supermodel codebase analysis tools.
|
|
42
42
|
* Redesigned for maximum SWE-bench performance:
|
|
43
|
-
* -
|
|
43
|
+
* - 1 tool (symbol_context) with batch support; overview injected into instructions
|
|
44
44
|
* - Pre-computed graph support for sub-second response times
|
|
45
45
|
* - On-demand API fallback when no cache exists
|
|
46
46
|
* @module server
|
|
@@ -48,7 +48,7 @@ exports.Server = void 0;
|
|
|
48
48
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
49
49
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
50
50
|
const sdk_1 = require("@supermodeltools/sdk");
|
|
51
|
-
const overview_1 =
|
|
51
|
+
const overview_1 = require("./tools/overview");
|
|
52
52
|
const symbol_context_1 = __importDefault(require("./tools/symbol-context"));
|
|
53
53
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
54
54
|
const zip_repository_1 = require("./utils/zip-repository");
|
|
@@ -89,23 +89,28 @@ class Server {
|
|
|
89
89
|
capabilities: { tools: {}, logging: {} },
|
|
90
90
|
instructions: `# Supermodel: Codebase Intelligence
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
One read-only tool for instant codebase understanding. Pre-computed graphs enable sub-second responses.
|
|
93
|
+
|
|
94
|
+
The codebase overview is included below in these instructions — you already have the architecture map.
|
|
93
95
|
|
|
94
96
|
## Recommended workflow
|
|
95
|
-
1.
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
1. Identify symbols from the issue/overview and call \`symbol_context\` to explore them.
|
|
98
|
+
Batch via \`symbols\` array or issue multiple calls in parallel (read-only, safe).
|
|
99
|
+
2. Stop calling MCP tools. Start editing by turn 3. Max 3 MCP calls total.
|
|
98
100
|
|
|
99
|
-
##
|
|
100
|
-
-
|
|
101
|
-
-
|
|
101
|
+
## Rules
|
|
102
|
+
- Do NOT use TodoWrite. Act directly.
|
|
103
|
+
- Use the Task tool to delegate subtasks (e.g. running tests, exploring tangential code).
|
|
104
|
+
- >2 MCP turns = diminishing returns. Explore everything you need in one turn.
|
|
102
105
|
|
|
103
106
|
## After fixing
|
|
104
|
-
Run the
|
|
107
|
+
Run the full related test suite to catch regressions. Do NOT write standalone test scripts.
|
|
105
108
|
|
|
106
109
|
## Tool reference
|
|
107
|
-
- \`
|
|
108
|
-
|
|
110
|
+
- \`symbol_context\`: Source, callers, callees, domain for any function/class/method.
|
|
111
|
+
Supports "Class.method", partial matching, and batch lookups via \`symbols\` array.
|
|
112
|
+
Use \`brief: true\` for compact output when looking up 3+ symbols.
|
|
113
|
+
Read-only — safe to call in parallel.`,
|
|
109
114
|
});
|
|
110
115
|
const config = new sdk_1.Configuration({
|
|
111
116
|
basePath: process.env.SUPERMODEL_BASE_URL || 'https://api.supermodeltools.com',
|
|
@@ -126,7 +131,6 @@ Run the project's existing test suite (e.g. pytest). Do NOT write standalone tes
|
|
|
126
131
|
}
|
|
127
132
|
setupHandlers() {
|
|
128
133
|
const allTools = [
|
|
129
|
-
overview_1.default,
|
|
130
134
|
symbol_context_1.default,
|
|
131
135
|
];
|
|
132
136
|
// Create a map for quick handler lookup
|
|
@@ -151,6 +155,37 @@ Run the project's existing test suite (e.g. pytest). Do NOT write standalone tes
|
|
|
151
155
|
throw new Error(`Unknown tool: ${name}`);
|
|
152
156
|
});
|
|
153
157
|
}
|
|
158
|
+
getTestHint(primaryLanguage) {
|
|
159
|
+
switch (primaryLanguage.toLowerCase()) {
|
|
160
|
+
case 'python': return '\n\n**Test with:** `python -m pytest <test_file> -x`';
|
|
161
|
+
case 'javascript':
|
|
162
|
+
case 'typescript': return '\n\n**Test with:** `npm test`';
|
|
163
|
+
case 'go': return '\n\n**Test with:** `go test ./...`';
|
|
164
|
+
case 'rust': return '\n\n**Test with:** `cargo test`';
|
|
165
|
+
case 'java': return '\n\n**Test with:** `mvn test` or `gradle test`';
|
|
166
|
+
case 'ruby': return '\n\n**Test with:** `bundle exec rake test`';
|
|
167
|
+
default: return '';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
injectOverviewInstructions(repoMap) {
|
|
171
|
+
if (repoMap.size === 0)
|
|
172
|
+
return;
|
|
173
|
+
// Only inject if there's exactly 1 unique graph (SWE-bench always has exactly 1 repo)
|
|
174
|
+
const uniqueGraphs = new Set([...repoMap.values()]);
|
|
175
|
+
if (uniqueGraphs.size !== 1)
|
|
176
|
+
return;
|
|
177
|
+
const graph = [...uniqueGraphs][0];
|
|
178
|
+
try {
|
|
179
|
+
const overview = (0, overview_1.renderOverview)(graph);
|
|
180
|
+
const testHint = this.getTestHint(graph.summary.primaryLanguage);
|
|
181
|
+
const current = this.server.server._instructions;
|
|
182
|
+
this.server.server._instructions = (current || '') + '\n\n' + overview + testHint;
|
|
183
|
+
logger.debug('Injected overview into server instructions');
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
logger.warn('Failed to render overview for instructions:', err.message || err);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
154
189
|
async start() {
|
|
155
190
|
// Clean up any stale ZIP files from previous sessions
|
|
156
191
|
await (0, zip_repository_1.cleanupOldZips)(constants_1.ZIP_CLEANUP_AGE_MS);
|
|
@@ -162,6 +197,7 @@ Run the project's existing test suite (e.g. pytest). Do NOT write standalone tes
|
|
|
162
197
|
const repoMap = await (0, graph_cache_1.loadCacheFromDisk)(cacheDir, graph_cache_1.graphCache);
|
|
163
198
|
(0, graph_cache_1.setRepoMap)(repoMap);
|
|
164
199
|
logger.debug(`Loaded ${repoMap.size} repo mappings`);
|
|
200
|
+
this.injectOverviewInstructions(repoMap);
|
|
165
201
|
}
|
|
166
202
|
catch (err) {
|
|
167
203
|
logger.warn('Failed to load cache directory:', err.message || err);
|
package/dist/tools/overview.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.handler = exports.tool = void 0;
|
|
14
|
+
exports.renderOverview = renderOverview;
|
|
14
15
|
const types_1 = require("../types");
|
|
15
16
|
const graph_cache_1 = require("../cache/graph-cache");
|
|
16
17
|
const api_helpers_1 = require("../utils/api-helpers");
|
|
@@ -28,6 +29,9 @@ exports.tool = {
|
|
|
28
29
|
},
|
|
29
30
|
required: [],
|
|
30
31
|
},
|
|
32
|
+
annotations: {
|
|
33
|
+
readOnlyHint: true,
|
|
34
|
+
},
|
|
31
35
|
};
|
|
32
36
|
const handler = async (client, args, defaultWorkdir) => {
|
|
33
37
|
const rawDir = args?.directory;
|
|
@@ -48,6 +48,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
48
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
49
|
exports.handler = exports.tool = void 0;
|
|
50
50
|
exports.findSymbol = findSymbol;
|
|
51
|
+
exports.renderBriefSymbolContext = renderBriefSymbolContext;
|
|
51
52
|
exports.renderSymbolContext = renderSymbolContext;
|
|
52
53
|
exports.languageFromExtension = languageFromExtension;
|
|
53
54
|
const fs_1 = require("fs");
|
|
@@ -58,7 +59,7 @@ const api_helpers_1 = require("../utils/api-helpers");
|
|
|
58
59
|
const constants_1 = require("../constants");
|
|
59
60
|
exports.tool = {
|
|
60
61
|
name: 'symbol_context',
|
|
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.
|
|
62
|
+
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, read-only. Supports partial matching ("filter" finds "QuerySet.filter", "filter_queryset", etc.) and "ClassName.method" syntax. You can issue multiple calls in parallel (read-only, no side effects) or batch symbols into one call via the "symbols" array.`,
|
|
62
63
|
inputSchema: {
|
|
63
64
|
type: 'object',
|
|
64
65
|
properties: {
|
|
@@ -66,27 +67,54 @@ exports.tool = {
|
|
|
66
67
|
type: 'string',
|
|
67
68
|
description: 'Name of the function, class, or method to look up. Supports "ClassName.method" syntax.',
|
|
68
69
|
},
|
|
70
|
+
symbols: {
|
|
71
|
+
type: 'array',
|
|
72
|
+
items: { type: 'string' },
|
|
73
|
+
description: 'Array of symbol names to look up in parallel. More efficient than multiple calls.',
|
|
74
|
+
maxItems: 10,
|
|
75
|
+
},
|
|
69
76
|
directory: {
|
|
70
77
|
type: 'string',
|
|
71
78
|
description: 'Path to the repository directory. Omit if the MCP server was started with a default workdir.',
|
|
72
79
|
},
|
|
80
|
+
brief: {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
description: 'Return compact output (definition + callers only, no source code). Recommended when looking up 3+ symbols.',
|
|
83
|
+
},
|
|
73
84
|
},
|
|
74
|
-
required: [
|
|
85
|
+
required: [],
|
|
86
|
+
},
|
|
87
|
+
annotations: {
|
|
88
|
+
readOnlyHint: true,
|
|
75
89
|
},
|
|
76
90
|
};
|
|
77
91
|
const handler = async (client, args, defaultWorkdir) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
92
|
+
// Extract symbol list: prefer `symbols` array, fall back to single `symbol`
|
|
93
|
+
let symbolList;
|
|
94
|
+
const symbolsArg = args?.symbols;
|
|
95
|
+
const symbolArg = typeof args?.symbol === 'string' ? args.symbol.trim() : '';
|
|
96
|
+
if (Array.isArray(symbolsArg) && symbolsArg.length > 0) {
|
|
97
|
+
symbolList = symbolsArg
|
|
98
|
+
.filter((s) => typeof s === 'string')
|
|
99
|
+
.map(s => s.trim())
|
|
100
|
+
.filter(s => s.length > 0)
|
|
101
|
+
.slice(0, constants_1.MAX_BATCH_SYMBOLS);
|
|
102
|
+
}
|
|
103
|
+
else if (symbolArg) {
|
|
104
|
+
symbolList = [symbolArg];
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
82
107
|
return (0, types_1.asErrorResult)({
|
|
83
108
|
type: 'validation_error',
|
|
84
|
-
message: 'Missing required "symbol" parameter.',
|
|
109
|
+
message: 'Missing required "symbol" or "symbols" parameter.',
|
|
85
110
|
code: 'MISSING_SYMBOL',
|
|
86
111
|
recoverable: false,
|
|
87
112
|
suggestion: 'Provide the name of a function, class, or method to look up.',
|
|
88
113
|
});
|
|
89
114
|
}
|
|
115
|
+
const brief = !!args?.brief;
|
|
116
|
+
const rawDir = args?.directory;
|
|
117
|
+
const directory = (rawDir && rawDir.trim()) || defaultWorkdir || process.cwd();
|
|
90
118
|
if (!directory || typeof directory !== 'string') {
|
|
91
119
|
return (0, types_1.asErrorResult)({
|
|
92
120
|
type: 'validation_error',
|
|
@@ -103,22 +131,33 @@ const handler = async (client, args, defaultWorkdir) => {
|
|
|
103
131
|
catch (error) {
|
|
104
132
|
return (0, types_1.asErrorResult)((0, api_helpers_1.classifyApiError)(error));
|
|
105
133
|
}
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
const isBatch = symbolList.length > 1;
|
|
135
|
+
const useBrief = brief || isBatch;
|
|
136
|
+
const allRendered = [];
|
|
137
|
+
for (const sym of symbolList) {
|
|
138
|
+
const matches = findSymbol(graph, sym);
|
|
139
|
+
if (matches.length === 0) {
|
|
140
|
+
allRendered.push(`## ${sym}\n\nNo symbol matching "${sym}" found in the code graph.`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (useBrief) {
|
|
144
|
+
// Brief mode: compact output for each top match
|
|
145
|
+
const parts = matches.slice(0, 3).map(node => renderBriefSymbolContext(graph, node));
|
|
146
|
+
allRendered.push(parts.join('\n---\n\n'));
|
|
147
|
+
if (matches.length > 3) {
|
|
148
|
+
allRendered.push(`*... and ${matches.length - 3} more matches for "${sym}".*`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Full mode: detailed output (single symbol, not brief)
|
|
153
|
+
const parts = await Promise.all(matches.slice(0, 3).map(node => renderSymbolContext(graph, node, directory)));
|
|
154
|
+
allRendered.push(parts.join('\n---\n\n'));
|
|
155
|
+
if (matches.length > 3) {
|
|
156
|
+
allRendered.push(`*... and ${matches.length - 3} more matches. Use a more specific name to narrow results.*`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
120
159
|
}
|
|
121
|
-
return (0, types_1.asTextContentResult)(
|
|
160
|
+
return (0, types_1.asTextContentResult)(allRendered.join('\n\n---\n\n'));
|
|
122
161
|
};
|
|
123
162
|
exports.handler = handler;
|
|
124
163
|
// ── Symbol lookup ──
|
|
@@ -215,6 +254,45 @@ function callerCount(graph, node) {
|
|
|
215
254
|
return graph.callAdj.get(node.id)?.in.length || 0;
|
|
216
255
|
}
|
|
217
256
|
// ── Rendering ──
|
|
257
|
+
function renderBriefSymbolContext(graph, node) {
|
|
258
|
+
const name = node.properties?.name || '(unknown)';
|
|
259
|
+
const rawFilePath = node.properties?.filePath || '';
|
|
260
|
+
const filePath = (0, graph_cache_1.normalizePath)(rawFilePath);
|
|
261
|
+
const startLine = node.properties?.startLine || 0;
|
|
262
|
+
const endLine = node.properties?.endLine || 0;
|
|
263
|
+
const kind = node.properties?.kind || node.labels?.[0]?.toLowerCase() || 'symbol';
|
|
264
|
+
const language = node.properties?.language || '';
|
|
265
|
+
const lines = [];
|
|
266
|
+
lines.push(`## ${name}`);
|
|
267
|
+
lines.push(`**Defined in:** ${filePath}${startLine ? ':' + startLine : ''}${endLine ? '-' + endLine : ''}`);
|
|
268
|
+
lines.push(`**Type:** ${kind}${language ? ' (' + language + ')' : ''}`);
|
|
269
|
+
const domain = findDomain(graph, node.id);
|
|
270
|
+
if (domain) {
|
|
271
|
+
lines.push(`**Domain:** ${domain}`);
|
|
272
|
+
}
|
|
273
|
+
// Callers (inline, max MAX_BRIEF_CALLERS)
|
|
274
|
+
const adj = graph.callAdj.get(node.id);
|
|
275
|
+
if (adj && adj.in.length > 0) {
|
|
276
|
+
const callerNames = adj.in
|
|
277
|
+
.map(id => graph.nodeById.get(id))
|
|
278
|
+
.filter((n) => !!n)
|
|
279
|
+
.map(n => `\`${n.properties?.name || '(unknown)'}\``)
|
|
280
|
+
.slice(0, constants_1.MAX_BRIEF_CALLERS);
|
|
281
|
+
const suffix = adj.in.length > constants_1.MAX_BRIEF_CALLERS ? `, ... (${adj.in.length} total)` : '';
|
|
282
|
+
lines.push(`**Called by:** ${callerNames.join(', ')}${suffix}`);
|
|
283
|
+
}
|
|
284
|
+
// Callees (inline, max MAX_BRIEF_CALLEES)
|
|
285
|
+
if (adj && adj.out.length > 0) {
|
|
286
|
+
const calleeNames = adj.out
|
|
287
|
+
.map(id => graph.nodeById.get(id))
|
|
288
|
+
.filter((n) => !!n)
|
|
289
|
+
.map(n => `\`${n.properties?.name || '(unknown)'}\``)
|
|
290
|
+
.slice(0, constants_1.MAX_BRIEF_CALLEES);
|
|
291
|
+
const suffix = adj.out.length > constants_1.MAX_BRIEF_CALLEES ? `, ... (${adj.out.length} total)` : '';
|
|
292
|
+
lines.push(`**Calls:** ${calleeNames.join(', ')}${suffix}`);
|
|
293
|
+
}
|
|
294
|
+
return lines.join('\n');
|
|
295
|
+
}
|
|
218
296
|
async function renderSymbolContext(graph, node, directory) {
|
|
219
297
|
const name = node.properties?.name || '(unknown)';
|
|
220
298
|
const rawFilePath = node.properties?.filePath || '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supermodeltools/mcp-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
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.5",
|
|
42
42
|
"archiver": "^7.0.1",
|
|
43
43
|
"ignore": "^7.0.5",
|
|
44
44
|
"jq-web": "^0.6.2",
|