Skip to content

Commit 5472ead

Browse files
author
Claude Sonnet (coordinator)
committed
refactor: collapse mcp surface to ledgerr tools
1 parent 1595d6e commit 5472ead

18 files changed

Lines changed: 550 additions & 725 deletions

AGENTS.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ For product scope and status, read `README.md` first, then use this file for exe
99

1010
### MCP Capability Training (Concrete)
1111

12-
Use `TurboLedgerService` in `crates/turbo-mcp/src/lib.rs` as the canonical contract.
12+
Use `TurboLedgerService` in `crates/ledgerr-mcp/src/lib.rs` as the canonical contract.
1313
Use `docs/mcp-capability-contract.md` as the canonical MCP surface map (tool names, arg contracts, service mapping, contrived usage flow).
1414

15+
Published MCP surface rule:
16+
- Default `tools/list` should stay collapsed to the 7 top-level `ledgerr_*` capability families: `ledgerr_documents`, `ledgerr_review`, `ledgerr_reconciliation`, `ledgerr_workflow`, `ledgerr_audit`, `ledgerr_tax`, `ledgerr_ontology`.
17+
- Use required `action` parameters to expose sub-operations while keeping major capability families visible.
18+
- Keep any legacy `l3dg3rr_*` or proxy-style names hidden compatibility aliases only; do not advertise them in the default tool catalog.
19+
1520
Core methods:
1621
- `list_accounts` / `list_accounts_tool`: enumerate account ids from manifest.
1722
- `validate_source_filename`: enforce `VENDOR--ACCOUNT--YYYY-MM--DOCTYPE.ext`.
@@ -230,9 +235,12 @@ Treat this as a standing operational gate, not a one-time migration task.
230235
### Validation Memo
231236

232237
- 2026-04-02: executed post-commit plugin-doc validation against `https://code.claude.com/docs/en/plugins`.
233-
- Updated stale tool examples from `l3dg3rr_context_summary` to live MCP tools (`l3dg3rr_get_pipeline_status`, `l3dg3rr_list_accounts`, `l3dg3rr_get_raw_context`).
238+
- Updated stale tool examples from `l3dg3rr_context_summary` to then-live MCP tools (`l3dg3rr_get_pipeline_status`, `l3dg3rr_list_accounts`, `l3dg3rr_get_raw_context`).
234239
- Added plugin skill frontmatter `name` for plugin-doc compatibility.
235240
- Added runnable `just test` outcome flow (Rust executable) with both simple and blocked-diagnostics scenarios.
241+
- 2026-04-17: reduced the default MCP catalog to 7 top-level `ledgerr_*` tools and relocated plugin info under `ledgerr_workflow`.
242+
- Keep docs/examples aligned to the reduced surface; `tools/list` is now intended to be a trustworthy small catalog for agents.
243+
- Legacy `l3dg3rr_*` and proxy tool names remain compatibility aliases only and should not be reintroduced into the advertised catalog.
236244

237245

238246

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ See [docs/mcp-capability-contract.md](docs/mcp-capability-contract.md) for the c
1616
- Session manifest parsing and account listing
1717
- Workbook initialization with required sheet names
1818
- Git-friendly plain-text ingest output via Beancount journal entries (rustledger-compatible)
19-
- Idiomatic turbo MCP interface surface for `list_accounts` and `ingest_statement_rows`
19+
- Reduced 7-tool MCP interface surface using top-level `ledgerr_*` capabilities with action-based subcommands
2020

2121
## Prerequisites
2222

@@ -70,7 +70,7 @@ curl -fsSL "https://github.com/PromptExecution/l3dg3rr/releases/latest/download/
7070
claude mcp add ledgerr /tmp/ledgerr-mcp.mcpb
7171
```
7272

73-
After adding, restart Claude Code. The `l3dg3rr_*` tools will appear automatically.
73+
After adding, restart Claude Code. The 7 top-level `ledgerr_*` tools will appear automatically.
7474

7575
## Docker
7676

crates/ledgerr-mcp/src/bin/ledgerr-mcp-server.rs

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,35 @@ fn handle_request(request: Value) -> Option<Value> {
5757
let params = request.get("params").cloned().unwrap_or(Value::Null);
5858
let tool_name = params.get("name").and_then(Value::as_str).unwrap_or("");
5959
let result = match tool_name {
60-
mcp_adapter::LIST_ACCOUNTS_TOOL => {
61-
mcp_adapter::handle_list_accounts(global_service())
60+
mcp_adapter::DOCUMENTS_TOOL => {
61+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
62+
mcp_adapter::handle_documents_tool(global_service(), &arguments)
63+
}
64+
mcp_adapter::REVIEW_TOOL => {
65+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
66+
mcp_adapter::handle_review_tool(global_service(), &arguments)
67+
}
68+
mcp_adapter::RECONCILIATION_TOOL => {
69+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
70+
mcp_adapter::handle_reconciliation_tool(global_service(), &arguments)
71+
}
72+
mcp_adapter::WORKFLOW_TOOL => {
73+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
74+
mcp_adapter::handle_workflow_tool(global_service(), &arguments)
75+
}
76+
mcp_adapter::AUDIT_TOOL => {
77+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
78+
mcp_adapter::handle_audit_tool(global_service(), &arguments)
79+
}
80+
mcp_adapter::TAX_TOOL => {
81+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
82+
mcp_adapter::handle_tax_tool(global_service(), &arguments)
83+
}
84+
mcp_adapter::ONTOLOGY_TOOL => {
85+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
86+
mcp_adapter::handle_ontology_tool(global_service(), &arguments)
6287
}
88+
"l3dg3rr_list_accounts" => mcp_adapter::handle_list_accounts(global_service()),
6389
"l3dg3rr_get_pipeline_status" => {
6490
mcp_adapter::handle_pipeline_status(true, true, true, Vec::new())
6591
}
@@ -79,95 +105,104 @@ fn handle_request(request: Value) -> Option<Value> {
79105
Some(format!("mcp-call-{id}")),
80106
)
81107
}
82-
mcp_adapter::GET_RAW_CONTEXT_TOOL => {
108+
"l3dg3rr_get_raw_context" => {
83109
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
84110
mcp_adapter::handle_get_raw_context(global_service(), &arguments)
85111
}
86-
mcp_adapter::ONTOLOGY_QUERY_PATH_TOOL => {
112+
"l3dg3rr_ontology_query_path" => {
87113
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
88114
mcp_adapter::handle_ontology_query_path(global_service(), &arguments)
89115
}
90-
mcp_adapter::ONTOLOGY_EXPORT_SNAPSHOT_TOOL => {
116+
"l3dg3rr_ontology_export_snapshot" => {
91117
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
92118
mcp_adapter::handle_ontology_export_snapshot(global_service(), &arguments)
93119
}
94-
mcp_adapter::RECON_VALIDATE_TOOL
95-
| mcp_adapter::RECON_RECONCILE_TOOL
96-
| mcp_adapter::RECON_COMMIT_TOOL => {
120+
"l3dg3rr_validate_reconciliation" => {
97121
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
98-
mcp_adapter::dispatch_reconciliation(global_service(), tool_name, &arguments)
122+
mcp_adapter::dispatch_reconciliation(global_service(), "validate", &arguments)
99123
}
100-
mcp_adapter::HSM_TRANSITION_TOOL
101-
| mcp_adapter::HSM_STATUS_TOOL
102-
| mcp_adapter::HSM_RESUME_TOOL => {
124+
"l3dg3rr_reconcile_postings" => {
103125
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
104-
mcp_adapter::dispatch_hsm(global_service(), tool_name, &arguments)
126+
mcp_adapter::dispatch_reconciliation(global_service(), "reconcile", &arguments)
105127
}
106-
mcp_adapter::EVENT_HISTORY_TOOL => {
128+
"l3dg3rr_commit_guarded" => {
129+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
130+
mcp_adapter::dispatch_reconciliation(global_service(), "commit", &arguments)
131+
}
132+
"l3dg3rr_hsm_transition" => {
133+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
134+
mcp_adapter::dispatch_hsm(global_service(), "transition", &arguments)
135+
}
136+
"l3dg3rr_hsm_status" => {
137+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
138+
mcp_adapter::dispatch_hsm(global_service(), "status", &arguments)
139+
}
140+
"l3dg3rr_hsm_resume" => {
141+
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
142+
mcp_adapter::dispatch_hsm(global_service(), "resume", &arguments)
143+
}
144+
"l3dg3rr_event_history" => {
107145
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
108146
mcp_adapter::handle_event_history(global_service(), &arguments)
109147
}
110-
mcp_adapter::EVENT_REPLAY_TOOL => {
148+
"l3dg3rr_event_replay" => {
111149
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
112150
mcp_adapter::handle_event_replay(global_service(), &arguments)
113151
}
114-
mcp_adapter::TAX_ASSIST_TOOL => {
152+
"l3dg3rr_tax_assist" => {
115153
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
116154
mcp_adapter::handle_tax_assist(global_service(), &arguments)
117155
}
118-
mcp_adapter::TAX_EVIDENCE_CHAIN_TOOL => {
156+
"l3dg3rr_tax_evidence_chain" => {
119157
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
120158
mcp_adapter::handle_tax_evidence_chain(global_service(), &arguments)
121159
}
122-
mcp_adapter::TAX_AMBIGUITY_REVIEW_TOOL => {
160+
"l3dg3rr_tax_ambiguity_review" => {
123161
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
124162
mcp_adapter::handle_tax_ambiguity_review(global_service(), &arguments)
125163
}
126-
// P0 tools
127-
mcp_adapter::CLASSIFY_INGESTED_TOOL => {
164+
"l3dg3rr_classify_ingested" => {
128165
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
129166
mcp_adapter::handle_classify_ingested(global_service(), &arguments)
130167
}
131-
mcp_adapter::QUERY_FLAGS_TOOL => {
168+
"l3dg3rr_query_flags" => {
132169
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
133170
mcp_adapter::handle_query_flags(global_service(), &arguments)
134171
}
135-
mcp_adapter::QUERY_AUDIT_LOG_TOOL => {
172+
"l3dg3rr_query_audit_log" => {
136173
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
137174
mcp_adapter::handle_query_audit_log(global_service(), &arguments)
138175
}
139-
// P1 tools
140-
mcp_adapter::CLASSIFY_TRANSACTION_TOOL => {
176+
"l3dg3rr_classify_transaction" => {
141177
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
142178
mcp_adapter::handle_classify_transaction(global_service(), &arguments)
143179
}
144-
mcp_adapter::RECONCILE_EXCEL_CLASSIFICATION_TOOL => {
180+
"l3dg3rr_reconcile_excel_classification" => {
145181
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
146-
mcp_adapter::handle_reconcile_excel_classification(
147-
global_service(),
148-
&arguments,
149-
)
182+
mcp_adapter::handle_reconcile_excel_classification(global_service(), &arguments)
150183
}
151-
mcp_adapter::GET_SCHEDULE_SUMMARY_TOOL => {
184+
"l3dg3rr_get_schedule_summary" => {
152185
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
153186
mcp_adapter::handle_get_schedule_summary(global_service(), &arguments)
154187
}
155-
// P2 tools
156-
mcp_adapter::EXPORT_CPA_WORKBOOK_TOOL => {
188+
"l3dg3rr_export_cpa_workbook" => {
157189
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
158190
mcp_adapter::handle_export_cpa_workbook(global_service(), &arguments)
159191
}
160-
mcp_adapter::ONTOLOGY_UPSERT_ENTITIES_TOOL => {
192+
"l3dg3rr_ontology_upsert_entities" => {
161193
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
162194
mcp_adapter::handle_ontology_upsert_entities(global_service(), &arguments)
163195
}
164-
mcp_adapter::ONTOLOGY_UPSERT_EDGES_TOOL => {
196+
"l3dg3rr_ontology_upsert_edges" => {
165197
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
166198
mcp_adapter::handle_ontology_upsert_edges(global_service(), &arguments)
167199
}
168-
mcp_adapter::PLUGIN_INFO_TOOL => {
200+
"l3dg3rr_plugin_info" => {
169201
let arguments = params.get("arguments").cloned().unwrap_or(Value::Null);
170-
mcp_adapter::handle_plugin_info(&arguments)
202+
mcp_adapter::handle_workflow_tool(
203+
global_service(),
204+
&json!({ "action": "plugin_info", "subcommand": arguments.get("subcommand").cloned().unwrap_or(Value::String("check".to_string())) }),
205+
)
171206
}
172207
_ => mcp_adapter::unknown_tool_result(tool_name),
173208
};

crates/ledgerr-mcp/src/bin/mcp-outcome-test.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,10 @@ fn step_tools_list(ctx: &mut FlowContext) -> Result<(), String> {
165165
.collect::<Vec<_>>();
166166

167167
for required in [
168-
"l3dg3rr_get_pipeline_status",
169-
"l3dg3rr_list_accounts",
170-
"proxy_docling_ingest_pdf",
171-
"l3dg3rr_get_raw_context",
168+
"ledgerr_documents",
169+
"ledgerr_workflow",
170+
"ledgerr_reconciliation",
171+
"ledgerr_audit",
172172
] {
173173
if !names.contains(&required) {
174174
return Err(format!(
@@ -183,8 +183,8 @@ fn step_pipeline_status(ctx: &mut FlowContext) -> Result<(), String> {
183183
let response = ctx.client.request(
184184
"tools/call",
185185
json!({
186-
"name": "l3dg3rr_get_pipeline_status",
187-
"arguments": {}
186+
"name": "ledgerr_documents",
187+
"arguments": { "action": "pipeline_status" }
188188
}),
189189
)?;
190190
let p = parse_response_payload(&response["result"]);
@@ -199,8 +199,8 @@ fn step_list_accounts(ctx: &mut FlowContext) -> Result<(), String> {
199199
let response = ctx.client.request(
200200
"tools/call",
201201
json!({
202-
"name": "l3dg3rr_list_accounts",
203-
"arguments": {}
202+
"name": "ledgerr_documents",
203+
"arguments": { "action": "list_accounts" }
204204
}),
205205
)?;
206206
let accounts_p = parse_response_payload(&response["result"]);
@@ -217,8 +217,9 @@ fn step_ingest_sample(ctx: &mut FlowContext) -> Result<(), String> {
217217
let response = ctx.client.request(
218218
"tools/call",
219219
json!({
220-
"name": "proxy_docling_ingest_pdf",
220+
"name": "ledgerr_documents",
221221
"arguments": {
222+
"action": "ingest_pdf",
222223
"pdf_path": "WF--BH-CHK--2023-01--statement.pdf",
223224
"journal_path": path_json(&ctx.journal_path),
224225
"workbook_path": path_json(&ctx.workbook_path),
@@ -246,8 +247,9 @@ fn step_get_raw_context(ctx: &mut FlowContext) -> Result<(), String> {
246247
let response = ctx.client.request(
247248
"tools/call",
248249
json!({
249-
"name": "l3dg3rr_get_raw_context",
250+
"name": "ledgerr_documents",
250251
"arguments": {
252+
"action": "get_raw_context",
251253
"rkyv_ref": path_json(&ctx.source_ref)
252254
}
253255
}),
@@ -265,8 +267,9 @@ fn step_hsm_resume_blocked(ctx: &mut FlowContext) -> Result<(), String> {
265267
let response = ctx.client.request(
266268
"tools/call",
267269
json!({
268-
"name": "l3dg3rr_hsm_resume",
270+
"name": "ledgerr_workflow",
269271
"arguments": {
272+
"action": "resume",
270273
"state_marker": "invalid-checkpoint"
271274
}
272275
}),
@@ -286,8 +289,9 @@ fn step_reconciliation_blocked(ctx: &mut FlowContext) -> Result<(), String> {
286289
let response = ctx.client.request(
287290
"tools/call",
288291
json!({
289-
"name": "l3dg3rr_commit_guarded",
292+
"name": "ledgerr_reconciliation",
290293
"arguments": {
294+
"action": "commit",
291295
"source_total": "100.00",
292296
"extracted_total": "95.00",
293297
"posting_amounts": ["-95.00", "95.00"]
@@ -313,8 +317,9 @@ fn step_event_history_blocked(ctx: &mut FlowContext) -> Result<(), String> {
313317
let response = ctx.client.request(
314318
"tools/call",
315319
json!({
316-
"name": "l3dg3rr_event_history",
320+
"name": "ledgerr_audit",
317321
"arguments": {
322+
"action": "event_history",
318323
"time_start": "2026-12-31",
319324
"time_end": "2026-01-01"
320325
}

0 commit comments

Comments
 (0)