Wire format
rousseau mcp starts an MCP server that speaks JSON-RPC 2.0 over stdio, per the Model Context Protocol specification revision 2024-11-05 (declared in ProtocolVersion in internal/mcp/protocol.go).
- One request per line on stdin (
bufio.Scannerreads up to 8 MiB per line). - One response per line on stdout (
json.NewEncoderemits newline-delimited JSON). - The server blocks until stdin closes or
ctxis cancelled.
JSON-RPC 2.0 envelope
Every request, notification, and response uses this envelope (from internal/mcp/protocol.go line 38):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Fields present depend on the envelope kind:
| Field | Request | Notification | Response |
|---|---|---|---|
jsonrpc |
always "2.0" |
always "2.0" |
always "2.0" |
id |
required | absent | echoed from the request |
method |
required | required | absent |
params |
optional | optional | absent |
result |
absent | absent | success only |
error |
absent | absent | failure only |
Notifications carry no id and receive no response. rousseau only receives one notification (notifications/initialized), which is silently accepted.
Method reference
Rousseau's Server.dispatch (internal/mcp/server.go line 112) routes these methods:
| Method | Purpose | Response |
|---|---|---|
initialize |
Handshake. Client declares protocol version and capabilities. | InitializeResult |
notifications/initialized |
Client confirms it is ready. | (notification, no response) |
ping |
Liveness probe. | {} |
tools/list |
Enumerate registered tools. | ToolsListResult |
tools/call |
Invoke a tool. | ToolsCallResult |
resources/list |
Placeholder. Returns { "resources": [] } today. |
{"resources": []} |
prompts/list |
Placeholder. Returns { "prompts": [] }. |
{"prompts": []} |
shutdown |
Client-initiated shutdown. | {} |
Request/response examples
1. initialize
Client sends:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"claude-desktop","version":"0.7.0"}}}
Server replies:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","serverInfo":{"name":"rousseau","version":"0.6.0"},"capabilities":{"tools":{"listChanged":false}}}}
listChanged: false because rousseau's tool set is static at process start — no runtime add/remove.
2. tools/list
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
Server replies with the registered tools in insertion order:
{"jsonrpc":"2.0","id":2,"result":{"tools":[
{"name":"read","description":"Read a file...","inputSchema":{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}},
{"name":"grep","description":"Search for a regex...","inputSchema":{"type":"object","properties":{"pattern":{"type":"string"},"path":{"type":"string"}},"required":["pattern"]}},
{"name":"bash","description":"Execute a shell command...","inputSchema":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}}
]}}
3. tools/call
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read","arguments":{"path":"/etc/hostname"}}}
Success:
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"my-host.example.com\n"}]}}
Handler-level failure (surfaced as content, not as a JSON-RPC error — this is MCP convention):
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"read: open /nope: no such file or directory"}],"isError":true}}
4. ping
{"jsonrpc":"2.0","id":4,"method":"ping"}
{"jsonrpc":"2.0","id":4,"result":{}}
Error codes
Rousseau uses the standard JSON-RPC 2.0 error range plus one MCP extension:
| Code | Constant | Meaning | When emitted |
|---|---|---|---|
| -32700 | CodeParseError |
Invalid JSON in the envelope. | Envelope failed json.Unmarshal. |
| -32600 | CodeInvalidRequest |
Envelope shape is wrong. | jsonrpc field is not "2.0". |
| -32601 | CodeMethodNotFound |
Method not implemented. | Dispatch fell through to the default case. |
| -32602 | CodeInvalidParams |
Params failed to decode. | params did not unmarshal into the expected shape. |
| -32603 | CodeInternalError |
Something went wrong marshalling the response. | Rare — indicates a bug. |
| -32000 | CodeToolNotFound |
Tool name is not registered. | tools/call with an unknown name. |
What is exposed
Two surfaces:
- Tools. Every
mcp.ToolSpecregistered beforeServeis advertised intools/listand callable viatools/call. rousseau wires the same tool implementations the local agent loop uses:read,write,edit,grep,bash. - Sessions. rousseau's SQLite session store is exposed so an MCP host can enumerate and read past conversations.
resources/listreturns one entry per session.
Tool failures are surfaced through the content channel with isError=true, not the JSON-RPC error channel. This is the MCP convention.
Client configuration — Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the platform equivalent:
{
"mcpServers": {
"rousseau": {
"command": "rousseau",
"args": ["mcp"],
"env": {
"HOME": "/Users/you"
}
}
}
}
Restart Claude Desktop. rousseau will appear in the tools palette; every registered tool is invocable.
For a rousseau built into a Podman image, the entry becomes:
{
"mcpServers": {
"rousseau": {
"command": "podman",
"args": [
"run", "--rm", "-i",
"-v", "/Users/you/.local/share/rousseau:/home/rousseau/.local/share/rousseau:rw,Z",
"localhost/rousseau-agent:local",
"mcp"
]
}
}
}
Bind-mount the state directory so the MCP host sees the same sessions as the daemon.
Registering a custom tool
Embedding the MCP server in your own binary:
srv := mcp.NewServer("rousseau", "0.1.0", logger)
srv.MustRegister(mcp.ToolSpec{
Name: "count_files",
Description: "Count files under a path.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"]
}`),
Handler: func(ctx context.Context, args json.RawMessage) ([]mcp.Content, error) {
var in struct{ Path string }
if err := json.Unmarshal(args, &in); err != nil {
return nil, fmt.Errorf("bad input: %w", err)
}
// ... count files ...
return []mcp.Content{{Type: "text", Text: fmt.Sprintf("%d", n)}}, nil
},
})
_ = srv.Serve(ctx, os.Stdin, os.Stdout)
Duplicate registrations return an error; MustRegister panics on duplicate (reserved for wire-up in main).
Concurrency
Serve may be called concurrently on independent transports (stdin/stdout for the MCP host, plus a control channel if you want one). The server's tool map is protected by an RWMutex; handler execution is not serialised — implementations must be safe for concurrent use.
Debugging
Every request/response envelope is logged at debug level by default. Enable with:
log:
level: debug
format: text
Or:
ROUSSEAU_LOG_LEVEL=debug rousseau mcp 2>/tmp/mcp.log
The MCP host consumes stdout; keep the log stream on stderr.
Troubleshooting
Claude Desktop / Cursor never shows the rousseau tools
Almost always a wiring error, not a rousseau problem. Check: (1) the command and args in the host config invoke rousseau mcp (not rousseau chat); (2) the config file was saved and the host restarted; (3) rousseau mcp </dev/null from a shell doesn't crash — if it does, fix that first.
parse error on the very first message
The host is not sending line-delimited JSON. Some early MCP implementations send framed messages (Content-Length: N\r\n\r\n<body>); rousseau expects \n-delimited. Update the host to a build that uses stdio framing (all current major hosts do).
method not found: <foo>
The host is calling a method rousseau does not implement. Empty resources/list and prompts/list are provided as no-ops for the common probes; anything else returns -32601. Check internal/mcp/server.go dispatch() for the full method list.
Tool calls succeed but the host reports them as errors
The tool handler returned an error the wrong way. Handlers should return []Content{{Type: "text", Text: err.Error()}}, err != nil — rousseau catches the error and wraps it into isError: true. Do not return the error via the JSON-RPC error channel unless it is a protocol-level failure.
Container-based MCP fails with permission denied on state directory
The podman run invocation from Claude Desktop must include a -v for the state directory with the right SELinux label. Use :Z (private) unless the container is shared with other Podman workloads. Also verify the host UID inside the container matches the file ownership.
Related pages
- MCP: Exposed Tools — the tool set rousseau publishes.
- MCP: Exposed Resources — session enumeration and read.
- MCP: Compatibility — tested host matrix.
- Tutorials: Expose Tools via MCP — end-to-end walkthrough.
- Agent loop — how the same tools are used inside rousseau.
Further reading
internal/mcp/protocol.go— envelope, method names, error codes.internal/mcp/server.go—Serve,dispatch, tool registry.internal/mcp/tools.go— helpers for registering rousseau's built-in tools.internal/cli/mcp.go— therousseau mcpcommand wiring.- Model Context Protocol spec — external reference.