You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add an ix mcp subcommand that runs ix as a local Model Context Protocol (MCP) server. Exposes a curated set of read + write tools to MCP-aware AI clients (Claude Code, Cursor, OpenCode, etc.) so the model can invoke ix operations directly without going through shell subprocess invocations.
This is orthogonal to and complementary with --format llm (#206), not a replacement. --format llm shrinks the output payload; MCP changes how the call is made and discovered. Tool handlers internally render with the same compact format defined in #206.
Motivation
Today, AI agents invoke ix via shell:
ix-claude-plugin hooks shell out to ix text, ix overview, etc. and inject results as additionalContext.
Skills under skills/ instruct the model to run ix <cmd> --format json in its reasoning, which Claude Code executes as a Bash tool call.
This is fine but has friction:
Model has to know about ix. The CLAUDE.md routing table is ~3-4 KB of context loaded into every session teaching the model what commands exist.
Shell escaping is fragile. See [IX Error][ix] ix-text: text search failed #196 — multi-line patterns and backslash escapes blew up because the hook concatenated shell strings. MCP passes args as structured JSON; this class of bug disappears.
Per-invocation overhead. Each shell call spawns a Node process (~50-200 ms before any actual work). For fast commands (ix locate, ix entity) startup dominates. A long-lived MCP server amortizes this.
No tool discovery. Adding a new command requires updating CLAUDE.md prose. With MCP, tool schemas are auto-discovered at session start.
MCP solves the transport + discovery problem. --format llm solves the payload size problem. Both stack: a session that uses both gets the byte savings of --format llm on tool responses plus the discovery and shell-escape wins of MCP.
Where the code lives
ix mcp subcommand inside ix-cli. Considered alternatives:
Approach
Verdict
ix-cli subcommand (this proposal)
New ix mcp in this repo
OK. Single source of truth, reuses existing handlers + renderers, no version skew, works for every MCP client
Plugin-side wrapper in ix-claude-plugin
Plugin ships an MCP server that shells out to ix per call
Rejected. Reimplements logic, doesn't help other AI tools, per-call subprocess cost
Standalone ix-mcp repo
New repo, depends on the @ix/client package
Rejected. Three-repo coordination burden, separate release cadence
Files (new):
ix-cli/src/cli/commands/mcp.ts — registers ix mcp subcommand
ix-cli/src/mcp/server.ts — MCP server boilerplate (uses @modelcontextprotocol/sdk)
ix-cli/src/mcp/tools/index.ts — tool registry
ix-cli/src/mcp/tools/<command>.ts — one file per tool, exposes the tool's JSON schema and wraps the existing CLI handler
ix-cli/src/mcp/format.ts — shared response formatter, delegates to the --format llm renderers
docs/mcp.md — user-facing setup guide
Transport
stdio only in v1. Client spawns ix mcp as a subprocess; JSON-RPC over stdin/stdout. SSE/HTTP transports add lifecycle and security surface we don't need. If a remote use case appears, that is a v3 addition with explicit auth design.
Tool catalog (v1 — 28 tools)
Tools map roughly 1:1 to CLI commands the model already invokes per CLAUDE.md routing. Naming is flat (search, locate, etc.); MCP clients namespace by server name (e.g., ix:search in Claude Code).
Read tools — navigate (10)
search, locate, explain, read, entity, overview, inventory, rank, stats, text
diff, patches — niche; add when explicit demand appears
Workflow-aggregated tools (investigate, audit, discover) — see v2 below
User controls (toggling tools and the server)
Users should be able to constrain what the MCP server exposes without editing every plugin manifest that registers it. v1 ships four layers of control, each addressing a different threat model:
1. Server kill switch
Env: IX_MCP_ENABLED=false
Config: mcp.enabled: false in ~/.ix/config
When disabled, ix mcp exits immediately with a clear stderr message. The MCP client treats this as server-not-available and falls back to whatever the model would do without ix tools.
Use case: user doesn't want MCP at all, even if a plugin registers it.
2. Read-only mode
Flag: ix mcp --read-only
Env: IX_MCP_READ_ONLY=1
Disables all 5 write tools (decide, bug_create, bug_update, goal_create, plan_create). Server still advertises the 23 read tools.
Use case: CI runs, sandboxed contexts, demos, untrusted-model paths, "I want navigation but no state mutations."
3. Per-tool allowlist / blocklist
ix mcp --enable-tools search,locate,overview — explicit allowlist (most restrictive)
ix mcp --disable-tools decide,read — blocklist (more common)
Same values readable from ~/.ix/config so users don't repeat them per session
Allowlist wins if both are set (warn at startup).
Use case: lock down to a known-good subset; reduce session-context cost from tool descriptions.
4. Per-call confirmation (client-side)
Write tools are annotated as destructive in MCP tool metadata. Compatible clients (Claude Code, Cursor) honor the annotation and prompt the user before each call. The server just emits the annotation; the actual prompt policy lives with the client.
Use case: "I'm OK with this tool existing but want explicit consent each time."
Defaults
All four default to permissive: server enabled, full toolset, no filter, destructive annotation present (so client-side confirmation kicks in for writes). Users opt into restriction.
Tool response format
Each tool returns --format llm-style text by default, optionally JSON if the caller passes format: "json" in its args. Internally, tool handlers call the same renderers Ix#206 ships. The MCP layer adds no per-call rendering; it just selects which renderer to invoke.
This is why Ix#206 (--format llm) must land first. The MCP server's reason for existing token-wise is to deliver compact responses; if --format llm isn't ready, the MCP server has nothing to render with.
Config and auth
Reuse the existing CLI mechanisms with no new surface:
Env: IX_ENDPOINT, IX_ORG_ID, IX_HOME, IX_DEBUG
Files: ~/.ix/config, workspace .ix/config
The MCP server is the same logical client as ix CLI; same IxClient, same getEndpoint.
The server probes backend health and schema version at startup (same as today's CLI commands).
State and caching
V1: stateless. Each tool call hits the backend. No in-process cache.
V2 follow-up: TTL cache for read-heavy reads (entity, overview, read) that don't change mid-session. Per-tool TTLs.
Build / install impact
New dependency: @modelcontextprotocol/sdk (~50 KB minified, no native bindings)
Bundle size of ix increases marginally; no install-script changes
ix mcp is discoverable via ix --help so users find it the same way they find other subcommands
Test strategy
Unit: per-tool handler tests with mocked IxClient, assert schema validation and response shape
Integration: spin up ix mcp as a subprocess in a vitest test, drive it with the MCP client SDK, assert tool list + call responses against running memory-layer
Manual: Claude Code and Cursor sessions with the server registered; verify auto-discovery, tool execution, error paths
.claude-plugin/plugin.json gets an mcpServers.ix entry pointing at ix mcp
Skills under skills/ migrate from ix <cmd> --format json invocations to ix:<tool> MCP tool calls where the call is model-invoked
Hooks under hooks/ stay on CLI; they fire on shell events, not model events, so MCP doesn't help them
Other adopters (ix-cursor-plugin, ix-codex-plugin, ix-opencode-plugin, ix-gemini-plugin) get analogous adoption issues filed in their respective repos.
Decisions locked in
Subcommand name:ix mcp
Transport: stdio only in v1
Code home: ix-cli subcommand (Option A from the alternatives table)
Tool naming: flat names (search, subsystems, ...); MCP clients namespace by server
Response format: delegates to --format llm renderers from Ix#206; not a separate format
Config: reuses existing IxClient/env/config files, no new mechanism
State: stateless v1; caching deferred to v2
User controls: four-layer model (server kill switch, read-only mode, per-tool allow/blocklist, MCP-annotated destructive flag for client-side confirmation). All default to permissive.
Per-call confirmation: server emits the destructive annotation on write tools; the actual per-call prompt policy lives with the MCP client.
Out of scope for v1 (named explicitly so they don't sneak in)
Workflow-aggregated tools (investigate, audit, etc.); design once we have v1 usage data
Streaming responses
Result caching
map, ingest, or any expensive/destructive operation
SSE / remote transport
Server-side rate limiting or per-session metrics
Auth beyond the existing IxClient mechanism
Open questions
Write tools in v1? Currently planned to ship 5 write tools (decide, bug_create, etc.) annotated as destructive so clients can prompt; the new --read-only flag lets users disable them per-session. Alternative: defer all write tools to v2 and keep v1 purely read-only. Trade-off: read-only v1 ships sooner with smaller surface; write-included v1 means skills don't need to fall back to CLI for the write paths.
Tool description length budget. Every tool description is permanent session context. Cap at ~150 chars/tool? At 28 tools that is a 4 KB ceiling.
Subcommand visibility: should ix mcp show up in ix --help? Lean yes; discoverability beats hiding.
Dependencies and sequencing
Hard prerequisite: Ix#206 (--format llm) must land first, at least Tier 1 renderers. MCP tool responses depend on having compact renderers to call into. Once --format llm PR 1 ships, MCP work can begin.
v2 / v3 follow-ups (not this issue)
v2: workflow-aggregated tools, result caching, streaming for large responses, map/ingest exposure
v3: SSE/HTTP transport for remote scenarios, MCP "resources" exposure (graph nodes as resources), subscribe-style updates
Summary
Add an
ix mcpsubcommand that runs ix as a local Model Context Protocol (MCP) server. Exposes a curated set of read + write tools to MCP-aware AI clients (Claude Code, Cursor, OpenCode, etc.) so the model can invoke ix operations directly without going through shell subprocess invocations.This is orthogonal to and complementary with
--format llm(#206), not a replacement.--format llmshrinks the output payload; MCP changes how the call is made and discovered. Tool handlers internally render with the same compact format defined in #206.Motivation
Today, AI agents invoke ix via shell:
ix text,ix overview, etc. and inject results asadditionalContext.skills/instruct the model to runix <cmd> --format jsonin its reasoning, which Claude Code executes as aBashtool call.This is fine but has friction:
ix locate,ix entity) startup dominates. A long-lived MCP server amortizes this.MCP solves the transport + discovery problem.
--format llmsolves the payload size problem. Both stack: a session that uses both gets the byte savings of--format llmon tool responses plus the discovery and shell-escape wins of MCP.Where the code lives
ix mcpsubcommand insideix-cli. Considered alternatives:ix mcpin this repoixper callix-mcprepoFiles (new):
ix-cli/src/cli/commands/mcp.ts— registersix mcpsubcommandix-cli/src/mcp/server.ts— MCP server boilerplate (uses@modelcontextprotocol/sdk)ix-cli/src/mcp/tools/index.ts— tool registryix-cli/src/mcp/tools/<command>.ts— one file per tool, exposes the tool's JSON schema and wraps the existing CLI handlerix-cli/src/mcp/format.ts— shared response formatter, delegates to the--format llmrenderersdocs/mcp.md— user-facing setup guideTransport
stdio only in v1. Client spawns
ix mcpas a subprocess; JSON-RPC over stdin/stdout. SSE/HTTP transports add lifecycle and security surface we don't need. If a remote use case appears, that is a v3 addition with explicit auth design.Tool catalog (v1 — 28 tools)
Tools map roughly 1:1 to CLI commands the model already invokes per CLAUDE.md routing. Naming is flat (
search,locate, etc.); MCP clients namespace by server name (e.g.,ix:searchin Claude Code).Read tools — navigate (10)
search,locate,explain,read,entity,overview,inventory,rank,stats,textRead tools — relationships (6)
callers,callees,contains,imports,imported_by,dependsRead tools — structural analysis (3)
smells,subsystems,impactRead tools — history & decisions (4)
history,decisions,conflicts,briefingWrite tools (5, Pro-gated)
decide,bug_create,bug_update,goal_create,plan_createWrite tools are annotated as destructive in their MCP descriptions so clients (Claude Code, Cursor) can apply per-call confirmation policies.
Deliberately not in v1
map,ingest— expensive (seconds to minutes); defer to v2 with explicit time-cost annotationsdocker,instance,status,doctor,upgrade,view,reset,config,savings— ops/admin, not model-invokedquery— deprecated per CLAUDE.mddiff,patches— niche; add when explicit demand appearsinvestigate,audit,discover) — see v2 belowUser controls (toggling tools and the server)
Users should be able to constrain what the MCP server exposes without editing every plugin manifest that registers it. v1 ships four layers of control, each addressing a different threat model:
1. Server kill switch
IX_MCP_ENABLED=falsemcp.enabled: falsein~/.ix/configix mcpexits immediately with a clear stderr message. The MCP client treats this as server-not-available and falls back to whatever the model would do without ix tools.2. Read-only mode
ix mcp --read-onlyIX_MCP_READ_ONLY=1decide,bug_create,bug_update,goal_create,plan_create). Server still advertises the 23 read tools.3. Per-tool allowlist / blocklist
ix mcp --enable-tools search,locate,overview— explicit allowlist (most restrictive)ix mcp --disable-tools decide,read— blocklist (more common)IX_MCP_ENABLED_TOOLS,IX_MCP_DISABLED_TOOLS~/.ix/configso users don't repeat them per session4. Per-call confirmation (client-side)
Write tools are annotated as
destructivein MCP tool metadata. Compatible clients (Claude Code, Cursor) honor the annotation and prompt the user before each call. The server just emits the annotation; the actual prompt policy lives with the client.Defaults
All four default to permissive: server enabled, full toolset, no filter, destructive annotation present (so client-side confirmation kicks in for writes). Users opt into restriction.
Tool response format
Each tool returns
--format llm-style text by default, optionally JSON if the caller passesformat: "json"in its args. Internally, tool handlers call the same renderers Ix#206 ships. The MCP layer adds no per-call rendering; it just selects which renderer to invoke.This is why Ix#206 (
--format llm) must land first. The MCP server's reason for existing token-wise is to deliver compact responses; if--format llmisn't ready, the MCP server has nothing to render with.Config and auth
Reuse the existing CLI mechanisms with no new surface:
IX_ENDPOINT,IX_ORG_ID,IX_HOME,IX_DEBUG~/.ix/config, workspace.ix/configixCLI; sameIxClient, samegetEndpoint.The server probes backend health and schema version at startup (same as today's CLI commands).
State and caching
V1: stateless. Each tool call hits the backend. No in-process cache.
V2 follow-up: TTL cache for read-heavy reads (
entity,overview,read) that don't change mid-session. Per-tool TTLs.Build / install impact
@modelcontextprotocol/sdk(~50 KB minified, no native bindings)ixincreases marginally; no install-script changesix mcpis discoverable viaix --helpso users find it the same way they find other subcommandsTest strategy
IxClient, assert schema validation and response shapeix mcpas a subprocess in a vitest test, drive it with the MCP client SDK, assert tool list + call responses against running memory-layerAdopter-side work
Tracked in ix-infrastructure/ix-claude-plugin#22 (analogous to plugin#21 for
--format llm). Scope on the plugin side:.claude-plugin/plugin.jsongets anmcpServers.ixentry pointing atix mcpskills/migrate fromix <cmd> --format jsoninvocations toix:<tool>MCP tool calls where the call is model-invokedhooks/stay on CLI; they fire on shell events, not model events, so MCP doesn't help themOther adopters (ix-cursor-plugin, ix-codex-plugin, ix-opencode-plugin, ix-gemini-plugin) get analogous adoption issues filed in their respective repos.
Decisions locked in
ix mcpsearch,subsystems, ...); MCP clients namespace by server--format llmrenderers from Ix#206; not a separate formatIxClient/env/config files, no new mechanismdestructiveannotation on write tools; the actual per-call prompt policy lives with the MCP client.Out of scope for v1 (named explicitly so they don't sneak in)
investigate,audit, etc.); design once we have v1 usage datamap,ingest, or any expensive/destructive operationOpen questions
decide,bug_create, etc.) annotated as destructive so clients can prompt; the new--read-onlyflag lets users disable them per-session. Alternative: defer all write tools to v2 and keep v1 purely read-only. Trade-off: read-only v1 ships sooner with smaller surface; write-included v1 means skills don't need to fall back to CLI for the write paths.ix mcpshow up inix --help? Lean yes; discoverability beats hiding.Dependencies and sequencing
Hard prerequisite: Ix#206 (
--format llm) must land first, at least Tier 1 renderers. MCP tool responses depend on having compact renderers to call into. Once--format llmPR 1 ships, MCP work can begin.v2 / v3 follow-ups (not this issue)
map/ingestexposure