MCP overview
Outcome: wire an editor or agent so it can deposit, read the field, and observe the mux — without inventing tool names.
Castellan exposes stigmergy, memory, and harness tools via castellan mcp (stdio JSON-RPC) and live episodes (castellan run --mcp). Both paths share one registry, so tool names, schemas, and governance behavior never drift between editor use and headless runs.
Same tools, two surfaces
castellan mcp
# or during a live episode:
castellan run --goal "…" --plugin shell --mcp
stdio JSON-RPC on stdin/stdout. Methods: initialize, tools/list, tools/call.
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"castellan_multiplexer_status","arguments":{}}}
Same LIVE_TOOL_NAMES registry as the CLI path — discover → describe → invoke.
Start the server
castellan mcp
Wire your MCP client to stdin/stdout JSON-RPC. Same registry as live episodes — see crates/castellan-mcp/src/registry.rs.
Anatomy: discovery → describe → invoke
flowchart LR
A[Agent starts] --> B{castellan_discover_tools<br/>or castellan_search_tools}
B --> C[castellan_describe_tool]
C --> D[Invoke operational tool]
D --> E[Governance + trace]
classDef runtime stroke:#0969da,stroke-width:2px
classDef substrate stroke:#5b6b8c,stroke-width:2px
classDef gate stroke:#1a7f37,stroke-width:2px
class A,B runtime
class C,D substrate
class E gate
- Discover —
castellan_discover_toolsreturns domain-indexed cards (no full schemas). Optionaldomainfilter. - Search —
castellan_search_toolswithquerynarrows by intent; BM25-ranked, substring fallback for partial words. - Describe —
castellan_describe_toolwithnamereturns the full schema, preconditions, side effects, and examples. - Invoke —
tools/call(stdio) or themcp_toolsobservation payload (live episodes).
Meta-tools always ship full schemas in tools/list; operational tools ship compact cards to keep context small — call castellan_describe_tool before invoking one you haven’t used yet.
Full tool catalog
Verified against LIVE_TOOL_NAMES in crates/castellan-mcp/src/registry.rs — do not invent tool names; if a tool you need isn’t here, it doesn’t exist yet.
Meta tools
| Tool | Purpose |
|---|---|
castellan_discover_tools | Browse domains and tool names |
castellan_search_tools | BM25-ranked keyword search over the catalog |
castellan_describe_tool | Full schema + preconditions + side effects for one tool |
Operational tools
| Tool | Domain | Summary | Annotations |
|---|---|---|---|
castellan_deposit_signal | substrate | Deposit pheromone into a zone | destructive, governed pre/post |
castellan_read_signal | substrate | Read signal strength from a zone | read-only |
castellan_read_blackboard | substrate | Read a blackboard slot by key | read-only |
castellan_topology_snapshot | substrate | Harness topology JSON (agents, edges, zones) | read-only |
castellan_metrics | substrate | Session counters (tool calls, deposits, mutations) | read-only |
castellan_multiplexer_status | multiplexer | ObservabilitySnapshot + freshness (stale, age_secs, schema_version) | read-only |
castellan_propose_mutation | evolve | Preview a topology mutation (diff + drift); does not apply | preview-only |
castellan_resolve_mutation | evolve | Accept or reject a persisted proposal | destructive, high-impact gate |
castellan_read_resource | governance | Resolve a typed URI (episode://, genome://, pane://, blackboard://) | read-only, allowlisted schemes |
castellan_recall | memory | Query durable memory (episodes, checkpoint, observations, topology, traces) | read-only |
castellan_remember | memory | Persist an agent observation to durable memory | destructive |
castellan_query_field_history | memory | Query checkpoint-time pheromone field snapshots | read-only |
All 12 operational tools are in LIVE_TOOL_NAMES, so they work identically on castellan run --mcp.
Full schemas, examples, and design rationale: MCP tools reference.
Discovery sequence (copy-paste)
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"castellan_search_tools","arguments":{"query":"deposit","domain":"substrate"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"castellan_describe_tool","arguments":{"name":"castellan_deposit_signal"}}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"castellan_deposit_signal","arguments":{"zone":"grid","signal":"coordination","amount":2.0}}}
Offline / no CDN — static transcript
$ printf '{"jsonrpc":"2.0","id":1,"method":"initialize"}\n' | castellan mcp
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05",...}}
$ echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"castellan_search_tools","arguments":{"query":"deposit"}}}' | castellan mcp
{"jsonrpc":"2.0","id":2,"result":{"hits":[{"name":"castellan_deposit_signal",...}]}}
Live field instrument (castellan mcp --attach)
While castellan run is active it publishes a live session under .castellan/live/
(active.json pointer, per-goal snapshot JSON, and an append-only deposit queue).
The transport is a file bridge (not a Unix domain socket): the engine drains the
queue at tick boundaries—the same semantics a socket would provide, without an async
listener inside the sync run loop. External MCP clients hit the same field the
scheduler ticks:
castellan mcp --attach auto # bind to the active live session
castellan mcp --attach <goal_id> # bind to a specific run
castellan_deposit_signalenqueues into the live run; the engine drains the queue each tick, emitsCastellanEvent::Deposit, and records the deposit on the episode’sinstrument_summarywithsource: "mcp_attach".castellan_topology_snapshotreturns the last published live snapshot (tick, topology, field zones, wake stats).- Mutation tools (
castellan_propose_mutation,castellan_resolve_mutation) are denied in attach sessions (attach_deniederror). - If no live session exists,
--attachfails at startup (attach_unavailable) instead of silently degrading to a standalone engine. If the run ends while attached, calls fail withattach_lost.
Without --attach, plain castellan mcp still auto-routes deposits to an active
live session when one exists (source: "external_mcp"), falling back to its own
in-memory engine otherwise. castellan doctor reports the live_session stage.
Episode JSON records all MCP instrument activity:
"instrument_summary": {
"mcp_tool_calls": 3,
"mcp_deposits": [
{ "tick": 2, "zone": "grid", "signal": "coordination", "amount": 1.5, "source": "mcp_attach" }
],
"topology_snapshots": [{ "tick": 2, "nodes": 2, "edges": 1 }]
}
Live episode example (castellan run --mcp)
{
"mcp_tools": [
{
"tool": "castellan_deposit_signal",
"args": { "zone": "grid", "signal": "coordination", "amount": 2.0 }
}
]
}
Put entries under mcp_tools in a plugin’s observation payload; CastellanEngine::call_mcp_tool dispatches them against the same registry and governance pipeline as stdio.
Failure paths
| Symptom | Cause | Fix |
|---|---|---|
JSON-RPC -32601 | Unknown method or tool name | Check spelling against the table above; run tools/list to confirm |
JSON-RPC -32602 | Invalid params for a tool | Call castellan_describe_tool first — required fields are enumerated in inputSchema |
castellan_search_tools query rejected | Query over 256 characters | Shorten the query string |
Tool call silently no-ops on --mcp | Tool not in LIVE_TOOL_NAMES | All 12 operational tools currently are — check mcp_tools payload shape matches the JSON above |
castellan_propose_mutation never applies | Two-phase by design | Call castellan_resolve_mutation with accept: true and the returned proposal_id |
| Deposits rejected mid-episode | Governance pre_execute hook denied | Check castellan.toml governance section and --audit <PATH> JSONL |
Trace JSONL emits mcp_discovery for meta-tools and mcp_tool for operational calls — use --audit on castellan run to capture both.
Editor integration
- Cursor setup — wire MCP in Cursor settings
- Agent guide — onboarding for Claude/Codex/Cursor/Pi
castellan mcpCLI page — stdio recipes