Skip to content

Commit 9ebe532

Browse files
committed
feat(mcp): harden MCP helper semantics and make the budget-first workflow explicit
- stabilize compact MCP clone and finding ids with collision-safe hashed short-id helpers - factor JSON/resource serialization and derived-section projection into shared MCP helpers, reducing service coupling without changing canonical report contracts - make the MCP workflow explicitly budget-aware and triage-first in server instructions, tool descriptions, README, and MCP docs - lock the new guidance and helper behavior with updated MCP server/service tests - refresh uv.lock to the current dependency lock state
1 parent b4345a1 commit 9ebe532

File tree

8 files changed

+470
-232
lines changed

8 files changed

+470
-232
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Live sample report:
4444
- **Baseline governance** — known debt stays accepted; CI blocks only new clones and metric regressions
4545
- **Reports** — interactive HTML, deterministic JSON/TXT plus Markdown and SARIF projections from one canonical report
4646
- **MCP server** — optional MCP surface for AI agents, IDEs, and MCP-capable clients; read-only with respect to repo and
47-
persisted artifacts
47+
persisted artifacts, budget-aware, and designed as a guided control surface for agentic development
4848
- **CI-first** — deterministic output, stable ordering, exit code contract, pre-commit support
4949
- **Fast** — incremental caching, parallel processing, warm-run optimization, and reproducible benchmark coverage
5050

@@ -170,6 +170,10 @@ codeclone-mcp --transport streamable-http --port 8000
170170
20 tools + 10 resources — deterministic, baseline-aware, and read-only. Never mutates source files, baselines, or repo
171171
state.
172172
Payloads are optimised for LLM context: compact summaries by default, full detail on demand.
173+
The cheapest useful path is also the most obvious path: first-pass triage stays compact, and deeper detail is explicit.
174+
Recommended budget-first flow for agents: `analyze_repository` or
175+
`analyze_changed_paths``get_run_summary` or `get_production_triage`
176+
`list_hotspots` or `check_*``get_finding``get_remediation`.
173177

174178
Docs:
175179
[MCP usage guide](https://orenlab.github.io/codeclone/mcp/)

codeclone/mcp_server.py

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@
2929

3030
_SERVER_INSTRUCTIONS = (
3131
"CodeClone MCP is a deterministic, baseline-aware, read-only analysis server "
32-
"for Python repositories. Use analyze_repository first, then query the latest "
33-
"or a specific run with summary, finding, hotspot, gate, and report-section "
34-
"tools. Pass an absolute repository root to analysis tools. This server "
35-
"never updates baselines and never mutates source files."
32+
"for Python repositories. Use analyze_repository first for full runs or "
33+
"analyze_changed_paths for PR-style review, then prefer get_run_summary or "
34+
"get_production_triage for the first pass. Use list_hotspots or focused "
35+
"check_* tools before broader list_findings calls, then drill into one "
36+
"finding with get_finding or get_remediation. Use "
37+
"get_report_section(section='metrics_detail', family=..., limit=...) for "
38+
"bounded metrics drill-down, and prefer generate_pr_summary(format='markdown') "
39+
"unless machine JSON is required. Pass an absolute repository root to "
40+
"analysis tools. This server never updates baselines and never mutates "
41+
"source files."
3642
)
3743
_MCP_INSTALL_HINT = (
3844
"CodeClone MCP support requires the optional 'mcp' extra. "
@@ -115,8 +121,9 @@ def resource(
115121
description=(
116122
"Run a deterministic CodeClone analysis for a repository and register "
117123
"the result as the latest MCP run. Pass an absolute repository root: "
118-
"relative roots like '.' are rejected in MCP. Tip: set "
119-
"cache_policy='off' to bypass cache and get fully fresh results."
124+
"relative roots like '.' are rejected in MCP. Then prefer "
125+
"get_run_summary or get_production_triage for the first pass. Tip: "
126+
"set cache_policy='off' to bypass cache and get fully fresh results."
120127
),
121128
annotations=session_tool,
122129
structured_output=True,
@@ -175,8 +182,10 @@ def analyze_repository(
175182
description=(
176183
"Run a deterministic CodeClone analysis and return a changed-files "
177184
"projection using explicit paths or a git diff ref. Pass an absolute "
178-
"repository root: relative roots like '.' are rejected in MCP. Tip: "
179-
"set cache_policy='off' to bypass cache and get fully fresh results."
185+
"repository root: relative roots like '.' are rejected in MCP. Then "
186+
"prefer get_report_section(section='changed') or get_production_triage "
187+
"before broader finding lists. Tip: set cache_policy='off' to bypass "
188+
"cache and get fully fresh results."
180189
),
181190
annotations=session_tool,
182191
structured_output=True,
@@ -233,7 +242,8 @@ def analyze_changed_paths(
233242
@tool(
234243
title="Get Run Summary",
235244
description=(
236-
"Return the stored compact MCP summary for the latest or specified run."
245+
"Return the stored compact MCP summary for the latest or specified "
246+
"run. Start here when you want the cheapest run-level snapshot."
237247
),
238248
annotations=read_only_tool,
239249
structured_output=True,
@@ -246,7 +256,8 @@ def get_run_summary(run_id: str | None = None) -> dict[str, object]:
246256
description=(
247257
"Return a production-first triage view over a stored run: health, "
248258
"cache freshness, production hotspots, and production suggestions, "
249-
"while keeping global source-kind counters visible."
259+
"while keeping global source-kind counters visible. Use this as the "
260+
"default first-pass review on noisy repositories."
250261
),
251262
annotations=read_only_tool,
252263
structured_output=True,
@@ -302,9 +313,10 @@ def evaluate_gates(
302313
title="Get Report Section",
303314
description=(
304315
"Return a canonical CodeClone report section for the latest or "
305-
"specified MCP run. The 'metrics' section returns only the "
306-
"summary, while 'metrics_detail' returns paginated item slices or "
307-
"summary+hint when unfiltered."
316+
"specified MCP run. Prefer specific sections instead of 'all' unless "
317+
"you truly need the full canonical report. The 'metrics' section "
318+
"returns only the summary, while 'metrics_detail' returns paginated "
319+
"item slices or summary+hint when unfiltered."
308320
),
309321
annotations=read_only_tool,
310322
structured_output=True,
@@ -330,7 +342,9 @@ def get_report_section(
330342
title="List Findings",
331343
description=(
332344
"List canonical finding groups with deterministic ordering, optional "
333-
"filters, pagination, and compact summary cards by default."
345+
"filters, pagination, and compact summary cards by default. Prefer "
346+
"list_hotspots or focused check_* tools for first-pass triage; use "
347+
"this when you need a broader filtered list."
334348
),
335349
annotations=read_only_tool,
336350
structured_output=True,
@@ -372,7 +386,9 @@ def list_findings(
372386
title="Get Finding",
373387
description=(
374388
"Return a single canonical finding group by short or full id. "
375-
"Normal detail is the default."
389+
"Normal detail is the default. Use this after list_hotspots, "
390+
"list_findings, or check_* instead of requesting larger lists at "
391+
"higher detail."
376392
),
377393
annotations=read_only_tool,
378394
structured_output=True,
@@ -392,7 +408,8 @@ def get_finding(
392408
title="Get Remediation",
393409
description=(
394410
"Return actionable remediation guidance for a single finding. "
395-
"Normal detail is the default."
411+
"Normal detail is the default. Use this when you need the fix packet "
412+
"for one finding without pulling larger detail lists."
396413
),
397414
annotations=read_only_tool,
398415
structured_output=True,
@@ -412,7 +429,8 @@ def get_remediation(
412429
title="List Hotspots",
413430
description=(
414431
"Return one of the derived CodeClone hotlists for the latest or "
415-
"specified MCP run, using compact summary cards by default."
432+
"specified MCP run, using compact summary cards by default. Prefer "
433+
"this for first-pass triage before broader list_findings calls."
416434
),
417435
annotations=read_only_tool,
418436
structured_output=True,
@@ -464,7 +482,9 @@ def compare_runs(
464482
description=(
465483
"Return complexity hotspots from a compatible stored run. "
466484
"Use analyze_repository first if no full run is available. When "
467-
"filtering by root without run_id, pass an absolute root."
485+
"filtering by root without run_id, pass an absolute root. Prefer "
486+
"this narrower tool instead of list_findings when you only need "
487+
"complexity hotspots."
468488
),
469489
annotations=read_only_tool,
470490
structured_output=True,
@@ -491,7 +511,9 @@ def check_complexity(
491511
description=(
492512
"Return clone findings from a compatible stored run. "
493513
"Use analyze_repository first if no compatible run is available. "
494-
"When filtering by root without run_id, pass an absolute root."
514+
"When filtering by root without run_id, pass an absolute root. "
515+
"Prefer this narrower tool instead of list_findings when you only "
516+
"need clone findings."
495517
),
496518
annotations=read_only_tool,
497519
structured_output=True,
@@ -520,7 +542,9 @@ def check_clones(
520542
description=(
521543
"Return coupling hotspots from a compatible stored run. "
522544
"Use analyze_repository first if no full run is available. When "
523-
"filtering by root without run_id, pass an absolute root."
545+
"filtering by root without run_id, pass an absolute root. Prefer "
546+
"this narrower tool instead of list_findings when you only need "
547+
"coupling hotspots."
524548
),
525549
annotations=read_only_tool,
526550
structured_output=True,
@@ -545,7 +569,9 @@ def check_coupling(
545569
description=(
546570
"Return cohesion hotspots from a compatible stored run. "
547571
"Use analyze_repository first if no full run is available. When "
548-
"filtering by root without run_id, pass an absolute root."
572+
"filtering by root without run_id, pass an absolute root. Prefer "
573+
"this narrower tool instead of list_findings when you only need "
574+
"cohesion hotspots."
549575
),
550576
annotations=read_only_tool,
551577
structured_output=True,
@@ -570,7 +596,9 @@ def check_cohesion(
570596
description=(
571597
"Return dead-code findings from a compatible stored run. "
572598
"Use analyze_repository first if no full run is available. When "
573-
"filtering by root without run_id, pass an absolute root."
599+
"filtering by root without run_id, pass an absolute root. Prefer "
600+
"this narrower tool instead of list_findings when you only need "
601+
"dead-code findings."
574602
),
575603
annotations=read_only_tool,
576604
structured_output=True,
@@ -594,7 +622,11 @@ def check_dead_code(
594622

595623
@tool(
596624
title="Generate PR Summary",
597-
description="Generate a PR-friendly CodeClone summary for changed files.",
625+
description=(
626+
"Generate a PR-friendly CodeClone summary for changed files. Prefer "
627+
"format='markdown' for compact LLM-facing output; use 'json' only "
628+
"for machine post-processing."
629+
),
598630
annotations=read_only_tool,
599631
structured_output=True,
600632
)

0 commit comments

Comments
 (0)