From a9c9ba2e34aba4a15348248be9947e52f3301861 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 13 May 2026 09:39:59 +0000 Subject: [PATCH] chore(mcp): drop 24 narrow tools from user-facing MCP surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.3.0 release exposed 34 MCP tools — 6 consolidated mode-driven plus 28 narrow tools (20 graph + 9 topology + 4 intelligence, minus run_cypher / read_file / generate_flow / review_changes which stay). Greenfield project, no pinned consumers — drop the 24 narrow tools that the consolidated layer subsumes from the MCP surface. Surface drops 34 -> 10: - 6 consolidated (graph_summary, find_in_graph, inspect_node, trace_relationships, analyze_impact, topology_view) - 4 first-class (run_cypher, read_file, generate_flow, review_changes) Implementation: keep all toolXxx(d) Tool builder functions in tools_graph.go / tools_intelligence.go / tools_topology.go because tools_consolidated.go delegates to them at the Go-API level for mode dispatch. RegisterGraph / RegisterIntelligence / RegisterTopology remain available for unit tests that exercise narrow tool behavior directly. Production cli wiring (registerAllTools) now calls a new RegisterGraphUserFacing that registers only run_cypher + read_file. Doc sweep: CLAUDE.md, README.md, PROJECT_SUMMARY.md updated to reflect the 10-tool surface; integration_test.go updated to assert 10 and spot-check the consolidated names. --- CLAUDE.md | 18 ++++++++++------ PROJECT_SUMMARY.md | 2 +- README.md | 18 ++++++++-------- go/internal/cli/mcp.go | 28 ++++++++++++------------- go/internal/mcp/integration_test.go | 32 +++++++++++++++++------------ go/internal/mcp/tools_graph.go | 26 ++++++++++++++++++++--- go/internal/mcp/tools_graph_test.go | 13 ++++++++++++ 7 files changed, 91 insertions(+), 46 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 409ccd51..19826420 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,8 +74,12 @@ mcp: Kuzu → QueryService → 6 consolidated MCP tools + run_cypher escape (mutation gate in `cypher.go`). - **`internal/mcp`** — 6 consolidated mode-driven tools (`graph_summary`, `find_in_graph`, `inspect_node`, `trace_relationships`, - `analyze_impact`, `topology_view`), `run_cypher` escape hatch, the - 34 deprecated narrow tools, plus `review_changes`. + `analyze_impact`, `topology_view`), `run_cypher` escape hatch, + `read_file` utility, `generate_flow`, and `review_changes` — 10 + user-facing tools total. The narrow toolXxx(d) builder funcs remain + in tools_graph.go/tools_intelligence.go/tools_topology.go as Go-API + delegation targets for the consolidated layer; they are NOT + registered as user-facing MCP tools. - **`internal/review`** — diff parser, Ollama-compatible chat client, ReviewService orchestrator. Default endpoint = local Ollama; `OLLAMA_API_KEY` flips to Ollama Cloud. @@ -197,10 +201,12 @@ codeiq mcp /path/to/repo # for Claude / Cursor wiring ## MCP Tools -The MCP server registers 6 consolidated mode-driven tools + `run_cypher` -+ `review_changes`. The 34 narrow tools from the Java side stay wired -for one release (v1.0.x) for back-compat with agents pinned to old -names; they'll be removed in a future minor. +The MCP server registers 10 user-facing tools — 6 consolidated +mode-driven, `run_cypher` (escape hatch), `read_file` (utility), +`generate_flow`, and `review_changes`. The 24 narrow tools that the +consolidated layer subsumes were dropped from the MCP surface; +their Go-API implementations (`toolXxx(d) Tool`) stay in the package +because the consolidated tools delegate to them. | Consolidated tool | mode dispatch | |---|---| diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md index 49d8612c..0c7d289f 100644 --- a/PROJECT_SUMMARY.md +++ b/PROJECT_SUMMARY.md @@ -37,7 +37,7 @@ | CLI / MCP server | `go/cmd/codeiq/main.go` | The only binary. All subcommands live in `internal/cli`. | | Subcommand registry | `internal/cli/root.go` | Sets up cobra root + registers per-subcommand inits. | | Detector registry | `internal/cli/detectors_register.go` | Blank-imports every detector package leaf. **Choke point** — forget it and detectors silently no-op. | -| Stdio MCP | `internal/cli/mcp.go` + `internal/mcp/server.go` | Wires consolidated tools + the deprecated 34 + `review_changes`. | +| Stdio MCP | `internal/cli/mcp.go` + `internal/mcp/server.go` | Wires 10 user-facing tools: 6 consolidated + `run_cypher` + `read_file` + `generate_flow` + `review_changes`. | | Analyzer pipeline | `internal/analyzer/analyzer.go` | FileDiscovery → parser → detectors (pool) → GraphBuilder → SQLite. | | Enrich pipeline | `internal/analyzer/enrich.go` | SQLite → Kuzu + linkers + layer classifier + intelligence. | diff --git a/README.md b/README.md index c8bd356e..8ab4e414 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,11 @@ framework usage. Same input ⇒ same output, every time. - **100 detectors** across 35+ languages — Java, Kotlin, Scala, Python, TypeScript/JavaScript, Go, Rust, C#, C++, Terraform, Bicep, Helm, Kubernetes, Docker, GitHub Actions, GitLab CI, … -- **MCP server included** — `codeiq mcp` runs an MCP stdio server with - 6 consolidated mode-driven tools (plus 34 deprecated narrow tools for - back-compat) so Claude / Cursor / any MCP-aware agent can query the - graph directly. +- **MCP server included** — `codeiq mcp` runs an MCP stdio server + exposing 10 user-facing tools (6 consolidated mode-driven + + `run_cypher` + `read_file` + `generate_flow` + `review_changes`) + so Claude / Cursor / any MCP-aware agent can query the graph + directly. - **LLM-driven PR review** — `codeiq review` walks the diff, queries the indexed graph for evidence, and asks Ollama (Cloud or local) for review comments. @@ -116,11 +117,10 @@ Add to your MCP client config (e.g. `.mcp.json` at the project root): } ``` -Six mode-driven tools (`graph_summary`, `find_in_graph`, `inspect_node`, -`trace_relationships`, `analyze_impact`, `topology_view`) plus -`run_cypher` (escape hatch) and `review_changes` (in-agent PR review). -The deprecated 34 narrow tools remain wired for one release for -back-compat. +Ten user-facing tools: six mode-driven (`graph_summary`, +`find_in_graph`, `inspect_node`, `trace_relationships`, +`analyze_impact`, `topology_view`) plus `run_cypher` (Cypher escape +hatch), `read_file` (utility), `generate_flow`, and `review_changes`. ## CLI reference diff --git a/go/internal/cli/mcp.go b/go/internal/cli/mcp.go index 98cfc964..5ecde2a5 100644 --- a/go/internal/cli/mcp.go +++ b/go/internal/cli/mcp.go @@ -143,26 +143,26 @@ To register with Claude Code, add to .mcp.json at the repo root: return cmd } -// registerAllTools wires every tool family onto srv. All four families -// land here unconditionally — graph (20) + topology (9) + flow (1) + -// intelligence (4) = 34 tools — matching the Java McpTools registration -// count. The `optionalRegisterHooks` slice remains for forward-compat -// with new tool families that may land in later phases (drill-down -// flows, query planner v2, etc.) without re-touching this function. +// registerAllTools wires every user-facing MCP tool family onto srv: +// graph (run_cypher + read_file = 2) + flow (generate_flow = 1) + +// consolidated (6 mode-driven) + review_changes = 10 tools. +// +// The narrow graph / topology / intelligence tool implementations are +// retained inside the mcp package because the consolidated tools +// delegate to them at the Go-API level, but they are no longer +// registered as user-facing MCP tools (greenfield project, no +// external consumers — the back-compat surface was dropped). +// +// `optionalRegisterHooks` remains for forward-compat with new tool +// families that may land in later phases without re-touching this +// function. func registerAllTools(srv *mcp.Server, d *mcp.Deps) error { - if err := mcp.RegisterGraph(srv, d); err != nil { + if err := mcp.RegisterGraphUserFacing(srv, d); err != nil { return fmt.Errorf("register graph tools: %w", err) } - if err := mcp.RegisterTopology(srv, d); err != nil { - return fmt.Errorf("register topology tools: %w", err) - } if err := mcp.RegisterFlow(srv, d); err != nil { return fmt.Errorf("register flow tools: %w", err) } - if err := mcp.RegisterIntelligence(srv, d); err != nil { - return fmt.Errorf("register intelligence tools: %w", err) - } - // Plan §2 — consolidated tools alongside the deprecated 34. if err := mcp.RegisterConsolidated(srv, d); err != nil { return fmt.Errorf("register consolidated tools: %w", err) } diff --git a/go/internal/mcp/integration_test.go b/go/internal/mcp/integration_test.go index bc230e05..ef407fe1 100644 --- a/go/internal/mcp/integration_test.go +++ b/go/internal/mcp/integration_test.go @@ -2,7 +2,8 @@ // End-to-end MCP integration test. Spawns the real `codeiq mcp` binary, // exchanges JSON-RPC frames over its stdin / stdout, and asserts the -// initialize handshake completes and tools/list returns all 34 tools. +// initialize handshake completes and tools/list returns the 10 +// user-facing tools (2 graph + 1 flow + 6 consolidated + 1 review). // // Build tag `integration` keeps this out of the default `go test ./...` // loop because it does a full `go build` first and stands up a fresh @@ -240,7 +241,7 @@ func TestMCPServerInitializeAndListTools(t *testing.T) { t.Fatalf("tools/list had no result: %v", listResp) } tools, _ := listResult["tools"].([]any) - if len(tools) != 34 { + if len(tools) != 10 { names := make([]string, 0, len(tools)) for _, tl := range tools { if m, ok := tl.(map[string]any); ok { @@ -249,11 +250,15 @@ func TestMCPServerInitializeAndListTools(t *testing.T) { } } } - t.Fatalf("tools/list returned %d tools, want 34. names=%v", len(tools), names) + t.Fatalf("tools/list returned %d tools, want 10. names=%v", len(tools), names) } - // 4. Spot-check one tool from each family. - wantNames := []string{"get_stats", "get_topology", "generate_flow", "find_node", "get_capabilities"} + // 4. Spot-check representative tool names from the 10-tool surface. + wantNames := []string{ + "graph_summary", "find_in_graph", "inspect_node", + "trace_relationships", "analyze_impact", "topology_view", + "run_cypher", "read_file", "generate_flow", "review_changes", + } have := map[string]bool{} for _, tl := range tools { if m, ok := tl.(map[string]any); ok { @@ -268,33 +273,34 @@ func TestMCPServerInitializeAndListTools(t *testing.T) { } } - // 5. Call get_capabilities — synchronous round trip that exercises - // the full tool dispatch path. + // 5. Call graph_summary in capabilities mode — synchronous round + // trip that exercises the consolidated tool dispatch path (which + // internally delegates to the toolGetCapabilities builder). callResp := client.rpc(t, map[string]any{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": map[string]any{ - "name": "get_capabilities", - "arguments": map[string]any{}, + "name": "graph_summary", + "arguments": map[string]any{"mode": "capabilities"}, }, }) callResult, ok := callResp["result"].(map[string]any) if !ok { - t.Fatalf("tools/call get_capabilities had no result: %v", callResp) + t.Fatalf("tools/call graph_summary had no result: %v", callResp) } content, _ := callResult["content"].([]any) if len(content) == 0 { - t.Fatalf("get_capabilities returned empty content") + t.Fatalf("graph_summary returned empty content") } first, _ := content[0].(map[string]any) text, _ := first["text"].(string) var body map[string]any if err := json.Unmarshal([]byte(text), &body); err != nil { - t.Fatalf("parse get_capabilities body: %v\ntext=%s", err, text) + t.Fatalf("parse graph_summary body: %v\ntext=%s", err, text) } if _, hasMatrix := body["matrix"]; !hasMatrix { - t.Fatalf("get_capabilities body missing matrix: %v", body) + t.Fatalf("graph_summary capabilities body missing matrix: %v", body) } } diff --git a/go/internal/mcp/tools_graph.go b/go/internal/mcp/tools_graph.go index 335394f0..c245992e 100644 --- a/go/internal/mcp/tools_graph.go +++ b/go/internal/mcp/tools_graph.go @@ -17,9 +17,16 @@ import ( "github.com/randomcodespace/codeiq/go/internal/graph" ) -// graphTools returns the slice of graph-facing Tool definitions for d. -// Each tool is fully self-contained — no shared mutable state. The -// returned slice is registered in order by RegisterGraph. +// graphTools returns every graph-tier Tool definition for d — the 18 +// narrow tools the consolidated layer delegates to, plus the two +// user-facing tools that survive the consolidation: run_cypher (Cypher +// escape hatch) and read_file (utility). +// +// Production wiring (cli/mcp.go → RegisterGraphUserFacing) registers +// only the 2 user-facing tools; tests that exercise the narrow tool +// implementations directly call RegisterGraph to surface all 20. The +// narrow toolXxx(d) builders are also called from tools_consolidated.go +// for Go-API delegation, independent of MCP registration. func graphTools(d *Deps) []Tool { return []Tool{ toolGetStats(d), @@ -45,6 +52,19 @@ func graphTools(d *Deps) []Tool { } } +// RegisterGraphUserFacing registers only the user-facing graph-tier +// tools (run_cypher + read_file). Used by production cli wiring — +// the 18 narrow tools were dropped from the user MCP surface in +// favor of the 6 consolidated mode-driven tools. +func RegisterGraphUserFacing(srv *Server, d *Deps) error { + for _, t := range []Tool{toolRunCypher(d), toolReadFile(d)} { + if err := srv.Register(t); err != nil { + return fmt.Errorf("mcp: register graph tool %q: %w", t.Name, err) + } + } + return nil +} + // RegisterGraph appends every graph-facing tool to srv. Errors halt the // loop so a duplicate name surfaces immediately during server boot. func RegisterGraph(srv *Server, d *Deps) error { diff --git a/go/internal/mcp/tools_graph_test.go b/go/internal/mcp/tools_graph_test.go index e780451a..517e41ea 100644 --- a/go/internal/mcp/tools_graph_test.go +++ b/go/internal/mcp/tools_graph_test.go @@ -130,6 +130,19 @@ func TestRegisterGraphRegistersAllTwentyTools(t *testing.T) { } } +func TestRegisterGraphUserFacingRegistersTwoTools(t *testing.T) { + srv, _ := mcp.NewServer(mcp.ServerOptions{Name: "x", Version: "0"}) + if err := mcp.RegisterGraphUserFacing(srv, &mcp.Deps{}); err != nil { + t.Fatalf("RegisterGraphUserFacing: %v", err) + } + want := []string{"read_file", "run_cypher"} + got := srv.Registry().Names() + sort.Strings(got) + if !reflect.DeepEqual(got, want) { + t.Fatalf("registered tools:\n got=%v\nwant=%v", got, want) + } +} + func TestGetStatsReturnsCounts(t *testing.T) { d := fixtureDeps(t) out := callTool(t, d, "get_stats", nil)