Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 |
|---|---|
Expand Down
2 changes: 1 addition & 1 deletion PROJECT_SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
28 changes: 14 additions & 14 deletions go/internal/cli/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
32 changes: 19 additions & 13 deletions go/internal/mcp/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
}
}

26 changes: 23 additions & 3 deletions go/internal/mcp/tools_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions go/internal/mcp/tools_graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading