@latentforce/shift 1.0.16 → 1.0.17
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 +4 -2
- package/build/mcp-server.js +321 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -302,9 +302,11 @@ When left as-is (single entry with `language: null` and `path: ""`), the server
|
|
|
302
302
|
|
|
303
303
|
| Tool | Description |
|
|
304
304
|
|------|-------------|
|
|
305
|
-
| `
|
|
306
|
-
| `dependencies` | Get all dependencies for a file with relationship summaries |
|
|
305
|
+
| `project_overview` | Get the highest-level context for the project: architecture, tech stack, entry points, and top-level modules |
|
|
307
306
|
| `file_summary` | Get a summary of a file with optional parent directory context |
|
|
307
|
+
| `module_summary` | Get full documentation for the module that owns a given file, including DRG cluster metadata and parent/child module relationships |
|
|
308
|
+
| `dependencies` | Get all dependencies for a file with relationship summaries |
|
|
309
|
+
| `blast_radius` | Analyze what files would be affected if a file is modified or deleted |
|
|
308
310
|
|
|
309
311
|
Each tool accepts an optional `project_id` parameter. If not provided, it falls back to the `SHIFT_PROJECT_ID` environment variable.
|
|
310
312
|
|
package/build/mcp-server.js
CHANGED
|
@@ -31,53 +31,118 @@ function normalizePath(filePath) {
|
|
|
31
31
|
p = p.replace(/^\//, '');
|
|
32
32
|
return p;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
function getAuthHeaders() {
|
|
35
|
+
const apiKey = getApiKeyFromEnv();
|
|
36
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
37
|
+
if (apiKey)
|
|
38
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
39
|
+
return headers;
|
|
40
|
+
}
|
|
41
|
+
function handleApiError(response, text) {
|
|
42
|
+
if (response.status === 404) {
|
|
43
|
+
if (text.includes('Knowledge graph not found') || text.includes('knowledge graph')) {
|
|
44
|
+
throw new Error("No knowledge graph exists for this project. Run 'shift-cli update-drg' to build it.");
|
|
45
|
+
}
|
|
46
|
+
if (text.includes('File not found') || text.includes('not found in')) {
|
|
47
|
+
const pathMatch = text.match(/['"]([^'"]+)['"]/);
|
|
48
|
+
const filePath = pathMatch ? pathMatch[1] : 'the requested file';
|
|
49
|
+
throw new Error(`File '${filePath}' not found in knowledge graph. Possible causes:\n` +
|
|
50
|
+
` 1. The path may be incorrect (check casing and slashes)\n` +
|
|
51
|
+
` 2. The file was added after the last 'shift-cli update-drg'\n` +
|
|
52
|
+
` 3. The file type is not supported (only JS/TS files are indexed)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`API call failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
|
|
56
|
+
}
|
|
57
|
+
/** POST to backend API */
|
|
35
58
|
async function callBackendAPI(endpoint, data) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
|
45
|
-
method: 'POST',
|
|
46
|
-
headers,
|
|
47
|
-
body: JSON.stringify(data),
|
|
48
|
-
});
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
const text = await response.text();
|
|
51
|
-
// Provide actionable error messages
|
|
52
|
-
if (response.status === 404) {
|
|
53
|
-
if (text.includes('Knowledge graph not found') || text.includes('knowledge graph')) {
|
|
54
|
-
throw new Error("No knowledge graph exists for this project. Run 'shift-cli update-drg' to build it.");
|
|
55
|
-
}
|
|
56
|
-
if (text.includes('File not found') || text.includes('not found in')) {
|
|
57
|
-
// Extract path from error if possible
|
|
58
|
-
const pathMatch = text.match(/['"]([^'"]+)['"]/);
|
|
59
|
-
const filePath = pathMatch ? pathMatch[1] : 'the requested file';
|
|
60
|
-
throw new Error(`File '${filePath}' not found in knowledge graph. Possible causes:\n` +
|
|
61
|
-
` 1. The path may be incorrect (check casing and slashes)\n` +
|
|
62
|
-
` 2. The file was added after the last 'shift-cli update-drg'\n` +
|
|
63
|
-
` 3. The file type is not supported (only JS/TS files are indexed)`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
throw new Error(`API call failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
|
|
67
|
-
}
|
|
68
|
-
return await response.json();
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
throw error;
|
|
59
|
+
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: getAuthHeaders(),
|
|
62
|
+
body: JSON.stringify(data),
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const text = await response.text();
|
|
66
|
+
handleApiError(response, text);
|
|
72
67
|
}
|
|
68
|
+
return await response.json();
|
|
73
69
|
}
|
|
70
|
+
/** GET from backend API with query params */
|
|
71
|
+
async function callBackendAPIGet(endpoint, params) {
|
|
72
|
+
const qs = new URLSearchParams(params).toString();
|
|
73
|
+
const url = `${BASE_URL}${endpoint}${qs ? `?${qs}` : ''}`;
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
method: 'GET',
|
|
76
|
+
headers: getAuthHeaders(),
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const text = await response.text();
|
|
80
|
+
handleApiError(response, text);
|
|
81
|
+
}
|
|
82
|
+
return await response.json();
|
|
83
|
+
}
|
|
84
|
+
// ============= FORMATTERS =============
|
|
74
85
|
/** Format file_summary response as readable markdown */
|
|
75
86
|
function formatFileSummary(data) {
|
|
76
87
|
const lines = [];
|
|
77
88
|
lines.push(`## File: ${data.path}`);
|
|
89
|
+
if (data.module_name) {
|
|
90
|
+
lines.push(`**Module:** ${data.module_name}`);
|
|
91
|
+
}
|
|
78
92
|
lines.push('');
|
|
79
93
|
lines.push('### Summary');
|
|
80
94
|
lines.push(data.summary || 'No summary available.');
|
|
95
|
+
if (data.exports && Object.keys(data.exports).length > 0) {
|
|
96
|
+
lines.push('');
|
|
97
|
+
lines.push('### Exports');
|
|
98
|
+
for (const [category, items] of Object.entries(data.exports)) {
|
|
99
|
+
if (items?.length > 0) {
|
|
100
|
+
lines.push(`**${category}:** ${items.join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (data.key_components?.length > 0) {
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push('### Key Components');
|
|
107
|
+
for (const comp of data.key_components) {
|
|
108
|
+
if (typeof comp === 'object' && comp !== null) {
|
|
109
|
+
const name = comp.name || comp.component || Object.keys(comp)[0] || '';
|
|
110
|
+
const desc = comp.description || comp.purpose || Object.values(comp)[0] || '';
|
|
111
|
+
lines.push(`- **${name}**: ${desc}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
lines.push(`- ${comp}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (data.internal_state) {
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push(`**Internal State:** ${data.internal_state}`);
|
|
121
|
+
}
|
|
122
|
+
if (data.error_handling) {
|
|
123
|
+
lines.push(`**Error Handling:** ${data.error_handling}`);
|
|
124
|
+
}
|
|
125
|
+
if (data.constraints?.length > 0) {
|
|
126
|
+
lines.push('');
|
|
127
|
+
lines.push('### Constraints');
|
|
128
|
+
for (const c of data.constraints) {
|
|
129
|
+
lines.push(`- ${c}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (data.dependencies?.length > 0) {
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push(`### Dependencies (${data.dependency_count ?? data.dependencies.length} imports)`);
|
|
135
|
+
for (const dep of data.dependencies) {
|
|
136
|
+
lines.push(`- ${dep}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (data.dependents?.length > 0) {
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push(`### Dependents (${data.dependent_count ?? data.dependents.length} files import this)`);
|
|
142
|
+
for (const dep of data.dependents) {
|
|
143
|
+
lines.push(`- ${dep}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
81
146
|
if (data.parent_summaries?.length > 0) {
|
|
82
147
|
lines.push('');
|
|
83
148
|
lines.push('### Directory Context');
|
|
@@ -91,41 +156,73 @@ function formatFileSummary(data) {
|
|
|
91
156
|
function formatDependencies(data) {
|
|
92
157
|
const lines = [];
|
|
93
158
|
const deps = data.dependencies || [];
|
|
94
|
-
|
|
159
|
+
// edge_details is now a dict of {imports, usage_pattern, data_flow, dependency_summary}
|
|
160
|
+
const edgeDetails = data.edge_details || {};
|
|
95
161
|
lines.push(`## Dependencies: ${data.path}`);
|
|
162
|
+
if (data.module_name) {
|
|
163
|
+
lines.push(`**Module:** ${data.module_name}`);
|
|
164
|
+
}
|
|
96
165
|
lines.push('');
|
|
166
|
+
// --- Forward dependencies ---
|
|
97
167
|
if (deps.length === 0) {
|
|
98
168
|
lines.push('This file has no dependencies.');
|
|
99
169
|
}
|
|
100
170
|
else {
|
|
101
|
-
lines.push(
|
|
171
|
+
lines.push(`### Imports (${deps.length} file(s) this file depends on)`);
|
|
102
172
|
lines.push('');
|
|
103
173
|
for (const dep of deps) {
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
lines.push(`- **${dep}
|
|
174
|
+
const edge = edgeDetails[dep];
|
|
175
|
+
if (edge) {
|
|
176
|
+
lines.push(`- **${dep}**`);
|
|
177
|
+
if (edge.usage_pattern)
|
|
178
|
+
lines.push(` - Usage: ${edge.usage_pattern}`);
|
|
179
|
+
if (edge.data_flow)
|
|
180
|
+
lines.push(` - Data flow: ${edge.data_flow}`);
|
|
181
|
+
if (edge.imports?.length > 0)
|
|
182
|
+
lines.push(` - Imports: ${edge.imports.join(', ')}`);
|
|
183
|
+
if (edge.dependency_summary)
|
|
184
|
+
lines.push(` - Summary: ${edge.dependency_summary}`);
|
|
107
185
|
}
|
|
108
186
|
else {
|
|
109
187
|
lines.push(`- **${dep}**`);
|
|
110
188
|
}
|
|
111
189
|
}
|
|
112
190
|
}
|
|
191
|
+
// --- Reverse dependencies ---
|
|
192
|
+
const dependents = data.dependents || [];
|
|
193
|
+
const dependentEdges = data.dependent_edge_details || {};
|
|
194
|
+
if (dependents.length > 0) {
|
|
195
|
+
lines.push('');
|
|
196
|
+
lines.push(`### Dependents (${dependents.length} file(s) that import this file)`);
|
|
197
|
+
lines.push('');
|
|
198
|
+
for (const dep of dependents) {
|
|
199
|
+
const edge = dependentEdges[dep];
|
|
200
|
+
if (edge) {
|
|
201
|
+
lines.push(`- **${dep}**`);
|
|
202
|
+
if (edge.usage_pattern)
|
|
203
|
+
lines.push(` - Usage: ${edge.usage_pattern}`);
|
|
204
|
+
if (edge.data_flow)
|
|
205
|
+
lines.push(` - Data flow: ${edge.data_flow}`);
|
|
206
|
+
if (edge.imports?.length > 0)
|
|
207
|
+
lines.push(` - Imports: ${edge.imports.join(', ')}`);
|
|
208
|
+
if (edge.dependency_summary)
|
|
209
|
+
lines.push(` - Summary: ${edge.dependency_summary}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
lines.push(`- **${dep}**`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// --- Implicit dependencies ---
|
|
113
217
|
const implicitDeps = data.implicit_dependencies || [];
|
|
114
218
|
if (implicitDeps.length > 0) {
|
|
115
219
|
lines.push('');
|
|
116
|
-
lines.push(
|
|
117
|
-
lines.push('');
|
|
118
|
-
lines.push(`Implicit dependencies (**${implicitDeps.length}**):`);
|
|
220
|
+
lines.push(`### Implicit / External Dependencies (${implicitDeps.length})`);
|
|
119
221
|
lines.push('');
|
|
120
222
|
const implicitEdges = data.implicit_edge_summaries || {};
|
|
121
223
|
for (const dep of implicitDeps) {
|
|
122
224
|
const summary = implicitEdges[dep];
|
|
123
|
-
|
|
124
|
-
lines.push(`- **${dep}**: ${summary}`);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
lines.push(`- **${dep}**`);
|
|
128
|
-
}
|
|
225
|
+
lines.push(summary ? `- **${dep}**: ${summary}` : `- **${dep}**`);
|
|
129
226
|
}
|
|
130
227
|
}
|
|
131
228
|
return lines.join('\n');
|
|
@@ -135,6 +232,9 @@ function formatBlastRadius(data) {
|
|
|
135
232
|
const lines = [];
|
|
136
233
|
const affected = data.affected_files || [];
|
|
137
234
|
lines.push(`## Blast Radius: ${data.path}`);
|
|
235
|
+
if (data.module_name) {
|
|
236
|
+
lines.push(`**Module:** ${data.module_name}`);
|
|
237
|
+
}
|
|
138
238
|
lines.push('');
|
|
139
239
|
if (affected.length === 0) {
|
|
140
240
|
lines.push('No other files are affected by changes to this file.');
|
|
@@ -152,26 +252,155 @@ function formatBlastRadius(data) {
|
|
|
152
252
|
}
|
|
153
253
|
const levels = Object.keys(byLevel).map(Number).sort((a, b) => a - b);
|
|
154
254
|
for (const level of levels) {
|
|
155
|
-
|
|
156
|
-
lines.push(`### ${label}`);
|
|
255
|
+
lines.push(`### ${level === 1 ? 'Level 1 (direct)' : `Level ${level}`}`);
|
|
157
256
|
for (const file of byLevel[level]) {
|
|
158
257
|
lines.push(`- **${file.path}**: ${file.summary || 'No summary'}`);
|
|
159
258
|
}
|
|
160
259
|
lines.push('');
|
|
161
260
|
}
|
|
162
261
|
}
|
|
262
|
+
// --- Affected clusters ---
|
|
263
|
+
const clusters = data.cluster_blast_radius || [];
|
|
264
|
+
if (clusters.length > 0) {
|
|
265
|
+
lines.push('### Affected Modules/Folders');
|
|
266
|
+
for (const cluster of clusters) {
|
|
267
|
+
lines.push(`- **${cluster.path}**: ${cluster.summary}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return lines.join('\n');
|
|
271
|
+
}
|
|
272
|
+
/** Format module_summary response as readable markdown */
|
|
273
|
+
function formatModuleSummary(data) {
|
|
274
|
+
const lines = [];
|
|
275
|
+
lines.push(`## Module: ${data.module_name}`);
|
|
276
|
+
if (data.parent_module) {
|
|
277
|
+
lines.push(`**Parent module:** ${data.parent_module}`);
|
|
278
|
+
}
|
|
279
|
+
if (data.child_modules?.length > 0) {
|
|
280
|
+
lines.push(`**Sub-modules:** ${data.child_modules.join(', ')}`);
|
|
281
|
+
}
|
|
282
|
+
if (data.drg_cluster_summary) {
|
|
283
|
+
lines.push('');
|
|
284
|
+
lines.push('### Architecture Summary');
|
|
285
|
+
lines.push(data.drg_cluster_summary);
|
|
286
|
+
}
|
|
287
|
+
if (data.drg_cluster_metadata && Object.keys(data.drg_cluster_metadata).length > 0) {
|
|
288
|
+
const meta = data.drg_cluster_metadata;
|
|
289
|
+
if (meta.architecture_layers?.length > 0) {
|
|
290
|
+
lines.push('');
|
|
291
|
+
lines.push('**Architecture Layers:**');
|
|
292
|
+
for (const layer of meta.architecture_layers) {
|
|
293
|
+
lines.push(`- ${layer}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (meta.technology_stack?.length > 0) {
|
|
297
|
+
lines.push('');
|
|
298
|
+
lines.push('**Technology Stack:**');
|
|
299
|
+
for (const tech of meta.technology_stack) {
|
|
300
|
+
lines.push(`- ${tech}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (data.components?.length > 0) {
|
|
305
|
+
lines.push('');
|
|
306
|
+
lines.push(`### Files in this module (${data.components.length})`);
|
|
307
|
+
for (const comp of data.components) {
|
|
308
|
+
lines.push(`- ${comp}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (data.content) {
|
|
312
|
+
lines.push('');
|
|
313
|
+
lines.push('### Full Documentation');
|
|
314
|
+
lines.push(data.content);
|
|
315
|
+
}
|
|
163
316
|
return lines.join('\n');
|
|
164
317
|
}
|
|
318
|
+
/** Format project_overview response as readable markdown */
|
|
319
|
+
function formatProjectOverview(data) {
|
|
320
|
+
const lines = [];
|
|
321
|
+
lines.push('## Project Overview');
|
|
322
|
+
lines.push('');
|
|
323
|
+
if (data.architecture_summary) {
|
|
324
|
+
lines.push('### Architecture Summary');
|
|
325
|
+
lines.push(data.architecture_summary);
|
|
326
|
+
lines.push('');
|
|
327
|
+
}
|
|
328
|
+
if (data.architecture_layers?.length > 0) {
|
|
329
|
+
lines.push('### Architecture Layers');
|
|
330
|
+
for (const layer of data.architecture_layers) {
|
|
331
|
+
if (typeof layer === 'string') {
|
|
332
|
+
lines.push(`- ${layer}`);
|
|
333
|
+
}
|
|
334
|
+
else if (layer && typeof layer === 'object') {
|
|
335
|
+
const name = layer.name || layer.layer || layer.title || Object.values(layer)[0];
|
|
336
|
+
const desc = layer.description || layer.desc || (Object.values(layer).length > 1 ? Object.values(layer)[1] : '');
|
|
337
|
+
lines.push(desc ? `- **${name}**: ${desc}` : `- ${name}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
lines.push('');
|
|
341
|
+
}
|
|
342
|
+
if (data.technology_stack?.length > 0) {
|
|
343
|
+
lines.push('### Technology Stack');
|
|
344
|
+
for (const tech of data.technology_stack) {
|
|
345
|
+
if (typeof tech === 'string') {
|
|
346
|
+
lines.push(`- ${tech}`);
|
|
347
|
+
}
|
|
348
|
+
else if (tech && typeof tech === 'object') {
|
|
349
|
+
const name = tech.name || tech.technology || tech.title || Object.values(tech)[0];
|
|
350
|
+
const desc = tech.description || tech.desc || (Object.values(tech).length > 1 ? Object.values(tech)[1] : '');
|
|
351
|
+
lines.push(desc ? `- **${name}**: ${desc}` : `- ${name}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
lines.push('');
|
|
355
|
+
}
|
|
356
|
+
if (data.entry_points?.length > 0) {
|
|
357
|
+
lines.push('### Entry Points');
|
|
358
|
+
for (const ep of data.entry_points) {
|
|
359
|
+
if (typeof ep === 'string') {
|
|
360
|
+
lines.push(`- ${ep}`);
|
|
361
|
+
}
|
|
362
|
+
else if (ep && typeof ep === 'object') {
|
|
363
|
+
const name = ep.name || ep.file || ep.path || Object.values(ep)[0];
|
|
364
|
+
const desc = ep.description || ep.desc || (Object.values(ep).length > 1 ? Object.values(ep)[1] : '');
|
|
365
|
+
lines.push(desc ? `- **${name}**: ${desc}` : `- ${name}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
lines.push('');
|
|
369
|
+
}
|
|
370
|
+
if (data.constraints?.length > 0) {
|
|
371
|
+
lines.push('### Constraints');
|
|
372
|
+
for (const c of data.constraints) {
|
|
373
|
+
lines.push(`- ${c}`);
|
|
374
|
+
}
|
|
375
|
+
lines.push('');
|
|
376
|
+
}
|
|
377
|
+
if (data.top_level_modules?.length > 0) {
|
|
378
|
+
lines.push('### Top-level Modules');
|
|
379
|
+
lines.push('');
|
|
380
|
+
for (const mod of data.top_level_modules) {
|
|
381
|
+
lines.push(`#### ${mod.path}`);
|
|
382
|
+
lines.push(mod.summary || '');
|
|
383
|
+
if (mod.files?.length > 0) {
|
|
384
|
+
lines.push(`Files: ${mod.files.join(', ')}`);
|
|
385
|
+
}
|
|
386
|
+
lines.push('');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (data.codewiki_overview) {
|
|
390
|
+
lines.push('### Project Documentation');
|
|
391
|
+
lines.push(data.codewiki_overview);
|
|
392
|
+
}
|
|
393
|
+
return lines.join('\n');
|
|
394
|
+
}
|
|
395
|
+
// ============= MCP SERVER =============
|
|
165
396
|
export async function startMcpServer() {
|
|
166
|
-
// Create server instance
|
|
167
397
|
const server = new McpServer({
|
|
168
398
|
name: "shift",
|
|
169
399
|
version,
|
|
170
400
|
});
|
|
171
|
-
//
|
|
172
|
-
// Blast radius
|
|
401
|
+
// ---- blast_radius ----
|
|
173
402
|
server.registerTool("blast_radius", {
|
|
174
|
-
description: "Use this BEFORE editing or refactoring any source file. Returns every file in the project that imports or depends on the given file, grouped by dependency depth (level 1 = direct importers, level 2 = their importers, etc.). Use this to understand the full impact of a change before making it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
|
|
403
|
+
description: "Use this BEFORE editing or refactoring any source file. Returns every file in the project that imports or depends on the given file, grouped by dependency depth (level 1 = direct importers, level 2 = their importers, etc.). Also returns the affected modules/folders. Use this to understand the full impact of a change before making it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
|
|
175
404
|
inputSchema: z.object({
|
|
176
405
|
file_path: z.string().describe("Path to the file (relative to project root)"),
|
|
177
406
|
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
@@ -184,18 +413,11 @@ export async function startMcpServer() {
|
|
|
184
413
|
project_id: projectId,
|
|
185
414
|
...(args.level != null && { level: args.level }),
|
|
186
415
|
});
|
|
187
|
-
return {
|
|
188
|
-
content: [
|
|
189
|
-
{
|
|
190
|
-
type: "text",
|
|
191
|
-
text: formatBlastRadius(data)
|
|
192
|
-
},
|
|
193
|
-
],
|
|
194
|
-
};
|
|
416
|
+
return { content: [{ type: "text", text: formatBlastRadius(data) }] };
|
|
195
417
|
});
|
|
196
|
-
//
|
|
418
|
+
// ---- dependencies ----
|
|
197
419
|
server.registerTool("dependencies", {
|
|
198
|
-
description: "Returns
|
|
420
|
+
description: "Returns bidirectional dependencies of a source file: what it imports (with usage pattern, data flow, and summary per dependency) AND what imports it (reverse dependencies). Use this to trace data flow, understand module relationships, follow a bug through the call chain, or map out what a file relies on before modifying it. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — do not call for .json, .yaml, .md or other non-source files.",
|
|
199
421
|
inputSchema: z.object({
|
|
200
422
|
file_path: z.string().describe("Path to the file"),
|
|
201
423
|
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
@@ -206,18 +428,11 @@ export async function startMcpServer() {
|
|
|
206
428
|
path: normalizePath(args.file_path),
|
|
207
429
|
project_id: projectId,
|
|
208
430
|
});
|
|
209
|
-
return {
|
|
210
|
-
content: [
|
|
211
|
-
{
|
|
212
|
-
type: "text",
|
|
213
|
-
text: formatDependencies(data)
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
};
|
|
431
|
+
return { content: [{ type: "text", text: formatDependencies(data) }] };
|
|
217
432
|
});
|
|
218
|
-
//
|
|
433
|
+
// ---- file_summary ----
|
|
219
434
|
server.registerTool("file_summary", {
|
|
220
|
-
description: "Returns an AI-generated summary of a source file: what it does, its key exports and functions,
|
|
435
|
+
description: "Returns an AI-generated summary of a source file: what it does, its key exports and functions, internal state, error handling, constraints, what it imports, who imports it, and which module it belongs to. Always call this before reading a raw file — it gives you the essential context without having to parse the full source. Use the 'level' parameter to include summaries of parent directories for broader context. Only works on indexed source files: .js .jsx .ts .tsx .py .java .cpp .cs .go .c .h .css .scss .html — for .json, .yaml, .md, or other non-source files, read them directly instead.",
|
|
221
436
|
inputSchema: z.object({
|
|
222
437
|
file_path: z.string().describe("Path to the file to summarize"),
|
|
223
438
|
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
@@ -230,14 +445,35 @@ export async function startMcpServer() {
|
|
|
230
445
|
project_id: projectId,
|
|
231
446
|
level: args.level ?? 0,
|
|
232
447
|
});
|
|
233
|
-
return {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
448
|
+
return { content: [{ type: "text", text: formatFileSummary(data) }] };
|
|
449
|
+
});
|
|
450
|
+
// ---- module_summary ----
|
|
451
|
+
server.registerTool("module_summary", {
|
|
452
|
+
description: "Given any file path, returns the full documentation for the module that owns it, the list of files in that module, the corresponding DRG cluster summary and metadata, and parent/child module relationships. Use this after file_summary to get broader module-level context before editing — especially useful when a change may affect an entire module rather than just one file.",
|
|
453
|
+
inputSchema: z.object({
|
|
454
|
+
file_path: z.string().describe("Path to any file in the module (relative to project root)"),
|
|
455
|
+
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
456
|
+
})
|
|
457
|
+
}, async (args) => {
|
|
458
|
+
const projectId = resolveProjectId(args);
|
|
459
|
+
const data = await callBackendAPI('/api/v1/mcp/module-summary', {
|
|
460
|
+
path: normalizePath(args.file_path),
|
|
461
|
+
project_id: projectId,
|
|
462
|
+
});
|
|
463
|
+
return { content: [{ type: "text", text: formatModuleSummary(data) }] };
|
|
464
|
+
});
|
|
465
|
+
// ---- project_overview ----
|
|
466
|
+
server.registerTool("project_overview", {
|
|
467
|
+
description: "Returns the highest-level context for the project: architecture summary, layers, tech stack, entry points, constraints, top-level modules with their files, and the project documentation overview. Call this first at the start of a session — before exploring any specific file — to understand the overall structure and where things live.",
|
|
468
|
+
inputSchema: z.object({
|
|
469
|
+
project_id: z.string().optional().describe("Shift Lite project UUID; overrides SHIFT_PROJECT_ID if provided"),
|
|
470
|
+
})
|
|
471
|
+
}, async (args) => {
|
|
472
|
+
const projectId = resolveProjectId(args);
|
|
473
|
+
const data = await callBackendAPIGet('/api/v1/mcp/project-overview', {
|
|
474
|
+
project_id: projectId,
|
|
475
|
+
});
|
|
476
|
+
return { content: [{ type: "text", text: formatProjectOverview(data) }] };
|
|
241
477
|
});
|
|
242
478
|
const transport = new StdioServerTransport();
|
|
243
479
|
await server.connect(transport);
|