Skip to content

Commit cbda266

Browse files
committed
refactor: consolidate MCP tools to match CLI changes from PR #280
Remove 3 redundant MCP tools and add 1 new tool to mirror the CLI consolidation done in PR #280: - Remove `explain` tool → fold into `audit` with `quick` param - Remove `hotspots` tool → fold into `triage` with `level` param - Remove `manifesto` tool → fold into `check` with manifesto-mode routing - Add standalone `path` tool (was only accessible via query mode=path) All backing data functions are unchanged. The `query` tool still supports mode=path for backward compatibility. Impact: 1 functions changed, 1 affected
1 parent fa7eee8 commit cbda266

File tree

2 files changed

+168
-125
lines changed

2 files changed

+168
-125
lines changed

src/mcp.js

Lines changed: 131 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,27 @@ const BASE_TOOLS = [
7070
required: ['name'],
7171
},
7272
},
73+
{
74+
name: 'path',
75+
description: 'Find shortest path between two symbols in the dependency graph',
76+
inputSchema: {
77+
type: 'object',
78+
properties: {
79+
from: { type: 'string', description: 'Source symbol name' },
80+
to: { type: 'string', description: 'Target symbol name' },
81+
depth: { type: 'number', description: 'Max traversal depth (default: 10)' },
82+
edge_kinds: {
83+
type: 'array',
84+
items: { type: 'string', enum: EVERY_EDGE_KIND },
85+
description: 'Edge kinds to follow (default: ["calls"])',
86+
},
87+
from_file: { type: 'string', description: 'Disambiguate source by file' },
88+
to_file: { type: 'string', description: 'Disambiguate target by file' },
89+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
90+
},
91+
required: ['from', 'to'],
92+
},
93+
},
7394
{
7495
name: 'file_deps',
7596
description: 'Show what a file imports and what imports it',
@@ -207,20 +228,6 @@ const BASE_TOOLS = [
207228
required: ['name'],
208229
},
209230
},
210-
{
211-
name: 'explain',
212-
description:
213-
'Structural summary of a file or function: public/internal API, data flow, dependencies. No LLM needed.',
214-
inputSchema: {
215-
type: 'object',
216-
properties: {
217-
target: { type: 'string', description: 'File path or function name' },
218-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
219-
...PAGINATION_PROPS,
220-
},
221-
required: ['target'],
222-
},
223-
},
224231
{
225232
name: 'where',
226233
description:
@@ -357,29 +364,6 @@ const BASE_TOOLS = [
357364
},
358365
},
359366
},
360-
{
361-
name: 'hotspots',
362-
description:
363-
'Find structural hotspots: files or directories with extreme fan-in, fan-out, or symbol density',
364-
inputSchema: {
365-
type: 'object',
366-
properties: {
367-
metric: {
368-
type: 'string',
369-
enum: ['fan-in', 'fan-out', 'density', 'coupling'],
370-
description: 'Metric to rank by',
371-
},
372-
level: {
373-
type: 'string',
374-
enum: ['file', 'directory'],
375-
description: 'Rank files or directories',
376-
},
377-
limit: { type: 'number', description: 'Number of results to return', default: 10 },
378-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
379-
offset: { type: 'number', description: 'Skip this many results (pagination, default: 0)' },
380-
},
381-
},
382-
},
383367
{
384368
name: 'co_changes',
385369
description:
@@ -469,23 +453,6 @@ const BASE_TOOLS = [
469453
},
470454
},
471455
},
472-
{
473-
name: 'manifesto',
474-
description:
475-
'Evaluate manifesto rules and return pass/fail verdicts for code health. Checks function complexity, file metrics, and cycle rules against configured thresholds.',
476-
inputSchema: {
477-
type: 'object',
478-
properties: {
479-
file: { type: 'string', description: 'Scope to file (partial match)' },
480-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
481-
kind: {
482-
type: 'string',
483-
description: 'Filter by symbol kind (function, method, class, etc.)',
484-
},
485-
...PAGINATION_PROPS,
486-
},
487-
},
488-
},
489456
{
490457
name: 'communities',
491458
description:
@@ -543,13 +510,19 @@ const BASE_TOOLS = [
543510
type: 'object',
544511
properties: {
545512
target: { type: 'string', description: 'File path or function name' },
513+
quick: {
514+
type: 'boolean',
515+
description: 'Structural summary only (skip impact + health)',
516+
default: false,
517+
},
546518
depth: { type: 'number', description: 'Impact analysis depth (default: 3)', default: 3 },
547519
file: { type: 'string', description: 'Scope to file (partial match)' },
548520
kind: {
549521
type: 'string',
550522
description: 'Filter by symbol kind (function, method, class, etc.)',
551523
},
552524
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
525+
...PAGINATION_PROPS,
553526
},
554527
required: ['target'],
555528
},
@@ -607,6 +580,12 @@ const BASE_TOOLS = [
607580
inputSchema: {
608581
type: 'object',
609582
properties: {
583+
level: {
584+
type: 'string',
585+
enum: ['function', 'file', 'directory'],
586+
description:
587+
'Granularity: function (default) | file | directory. File/directory shows hotspots',
588+
},
610589
sort: {
611590
type: 'string',
612591
enum: ['risk', 'complexity', 'churn', 'fan-in', 'mi'],
@@ -701,12 +680,16 @@ const BASE_TOOLS = [
701680
{
702681
name: 'check',
703682
description:
704-
'Run CI validation predicates against git changes. Checks for new cycles, blast radius violations, signature changes, and boundary violations. Returns pass/fail per predicate — ideal for CI gates.',
683+
'CI gate: run manifesto rules (no args), diff predicates (with ref/staged), or both (with rules flag). Returns pass/fail verdicts.',
705684
inputSchema: {
706685
type: 'object',
707686
properties: {
708687
ref: { type: 'string', description: 'Git ref to diff against (default: HEAD)' },
709688
staged: { type: 'boolean', description: 'Analyze staged changes instead of unstaged' },
689+
rules: {
690+
type: 'boolean',
691+
description: 'Also run manifesto rules alongside diff predicates',
692+
},
710693
cycles: { type: 'boolean', description: 'Enable cycles predicate (default: true)' },
711694
blast_radius: {
712695
type: 'number',
@@ -715,7 +698,13 @@ const BASE_TOOLS = [
715698
signatures: { type: 'boolean', description: 'Enable signatures predicate (default: true)' },
716699
boundaries: { type: 'boolean', description: 'Enable boundaries predicate (default: true)' },
717700
depth: { type: 'number', description: 'Max BFS depth for blast radius (default: 3)' },
701+
file: { type: 'string', description: 'Scope to file (partial match, manifesto mode)' },
702+
kind: {
703+
type: 'string',
704+
description: 'Filter by symbol kind (manifesto mode)',
705+
},
718706
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
707+
...PAGINATION_PROPS,
719708
},
720709
},
721710
},
@@ -894,6 +883,15 @@ export async function startMCPServer(customDbPath, options = {}) {
894883
}
895884
break;
896885
}
886+
case 'path':
887+
result = pathData(args.from, args.to, dbPath, {
888+
maxDepth: args.depth ?? 10,
889+
edgeKinds: args.edge_kinds,
890+
fromFile: args.from_file,
891+
toFile: args.to_file,
892+
noTests: args.no_tests,
893+
});
894+
break;
897895
case 'file_deps':
898896
result = fileDepsData(args.file, dbPath, {
899897
noTests: args.no_tests,
@@ -956,13 +954,6 @@ export async function startMCPServer(customDbPath, options = {}) {
956954
offset: args.offset ?? 0,
957955
});
958956
break;
959-
case 'explain':
960-
result = explainData(args.target, dbPath, {
961-
noTests: args.no_tests,
962-
limit: Math.min(args.limit ?? MCP_DEFAULTS.explain, MCP_MAX_LIMIT),
963-
offset: args.offset ?? 0,
964-
});
965-
break;
966957
case 'where':
967958
result = whereData(args.target, dbPath, {
968959
file: args.file_mode,
@@ -1132,17 +1123,6 @@ export async function startMCPServer(customDbPath, options = {}) {
11321123
});
11331124
break;
11341125
}
1135-
case 'hotspots': {
1136-
const { hotspotsData } = await import('./structure.js');
1137-
result = hotspotsData(dbPath, {
1138-
metric: args.metric,
1139-
level: args.level,
1140-
limit: Math.min(args.limit ?? MCP_DEFAULTS.hotspots, MCP_MAX_LIMIT),
1141-
offset: args.offset ?? 0,
1142-
noTests: args.no_tests,
1143-
});
1144-
break;
1145-
}
11461126
case 'co_changes': {
11471127
const { coChangeData, coChangeTopData } = await import('./cochange.js');
11481128
result = args.file
@@ -1200,17 +1180,6 @@ export async function startMCPServer(customDbPath, options = {}) {
12001180
});
12011181
break;
12021182
}
1203-
case 'manifesto': {
1204-
const { manifestoData } = await import('./manifesto.js');
1205-
result = manifestoData(dbPath, {
1206-
file: args.file,
1207-
noTests: args.no_tests,
1208-
kind: args.kind,
1209-
limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
1210-
offset: args.offset ?? 0,
1211-
});
1212-
break;
1213-
}
12141183
case 'communities': {
12151184
const { communitiesData } = await import('./communities.js');
12161185
result = communitiesData(dbPath, {
@@ -1235,13 +1204,21 @@ export async function startMCPServer(customDbPath, options = {}) {
12351204
break;
12361205
}
12371206
case 'audit': {
1238-
const { auditData } = await import('./audit.js');
1239-
result = auditData(args.target, dbPath, {
1240-
depth: args.depth,
1241-
file: args.file,
1242-
kind: args.kind,
1243-
noTests: args.no_tests,
1244-
});
1207+
if (args.quick) {
1208+
result = explainData(args.target, dbPath, {
1209+
noTests: args.no_tests,
1210+
limit: Math.min(args.limit ?? MCP_DEFAULTS.explain, MCP_MAX_LIMIT),
1211+
offset: args.offset ?? 0,
1212+
});
1213+
} else {
1214+
const { auditData } = await import('./audit.js');
1215+
result = auditData(args.target, dbPath, {
1216+
depth: args.depth,
1217+
file: args.file,
1218+
kind: args.kind,
1219+
noTests: args.no_tests,
1220+
});
1221+
}
12451222
break;
12461223
}
12471224
case 'batch_query': {
@@ -1255,18 +1232,30 @@ export async function startMCPServer(customDbPath, options = {}) {
12551232
break;
12561233
}
12571234
case 'triage': {
1258-
const { triageData } = await import('./triage.js');
1259-
result = triageData(dbPath, {
1260-
sort: args.sort,
1261-
minScore: args.min_score,
1262-
role: args.role,
1263-
file: args.file,
1264-
kind: args.kind,
1265-
noTests: args.no_tests,
1266-
weights: args.weights,
1267-
limit: Math.min(args.limit ?? MCP_DEFAULTS.triage, MCP_MAX_LIMIT),
1268-
offset: args.offset ?? 0,
1269-
});
1235+
if (args.level === 'file' || args.level === 'directory') {
1236+
const { hotspotsData } = await import('./structure.js');
1237+
const metric = args.sort === 'risk' ? 'fan-in' : args.sort;
1238+
result = hotspotsData(dbPath, {
1239+
metric,
1240+
level: args.level,
1241+
limit: Math.min(args.limit ?? MCP_DEFAULTS.hotspots, MCP_MAX_LIMIT),
1242+
offset: args.offset ?? 0,
1243+
noTests: args.no_tests,
1244+
});
1245+
} else {
1246+
const { triageData } = await import('./triage.js');
1247+
result = triageData(dbPath, {
1248+
sort: args.sort,
1249+
minScore: args.min_score,
1250+
role: args.role,
1251+
file: args.file,
1252+
kind: args.kind,
1253+
noTests: args.no_tests,
1254+
weights: args.weights,
1255+
limit: Math.min(args.limit ?? MCP_DEFAULTS.triage, MCP_MAX_LIMIT),
1256+
offset: args.offset ?? 0,
1257+
});
1258+
}
12701259
break;
12711260
}
12721261
case 'branch_compare': {
@@ -1321,17 +1310,45 @@ export async function startMCPServer(customDbPath, options = {}) {
13211310
break;
13221311
}
13231312
case 'check': {
1324-
const { checkData } = await import('./check.js');
1325-
result = checkData(dbPath, {
1326-
ref: args.ref,
1327-
staged: args.staged,
1328-
cycles: args.cycles,
1329-
blastRadius: args.blast_radius,
1330-
signatures: args.signatures,
1331-
boundaries: args.boundaries,
1332-
depth: args.depth,
1333-
noTests: args.no_tests,
1334-
});
1313+
const isDiffMode = args.ref || args.staged;
1314+
1315+
if (!isDiffMode && !args.rules) {
1316+
// No ref, no staged → run manifesto rules on whole codebase
1317+
const { manifestoData } = await import('./manifesto.js');
1318+
result = manifestoData(dbPath, {
1319+
file: args.file,
1320+
noTests: args.no_tests,
1321+
kind: args.kind,
1322+
limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
1323+
offset: args.offset ?? 0,
1324+
});
1325+
} else {
1326+
const { checkData } = await import('./check.js');
1327+
const checkResult = checkData(dbPath, {
1328+
ref: args.ref,
1329+
staged: args.staged,
1330+
cycles: args.cycles,
1331+
blastRadius: args.blast_radius,
1332+
signatures: args.signatures,
1333+
boundaries: args.boundaries,
1334+
depth: args.depth,
1335+
noTests: args.no_tests,
1336+
});
1337+
1338+
if (args.rules) {
1339+
const { manifestoData } = await import('./manifesto.js');
1340+
const manifestoResult = manifestoData(dbPath, {
1341+
file: args.file,
1342+
noTests: args.no_tests,
1343+
kind: args.kind,
1344+
limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
1345+
offset: args.offset ?? 0,
1346+
});
1347+
result = { check: checkResult, manifesto: manifestoResult };
1348+
} else {
1349+
result = checkResult;
1350+
}
1351+
}
13351352
break;
13361353
}
13371354
case 'ast_query': {

0 commit comments

Comments
 (0)