MCP Tooling
MuseHub exposes the full Muse platform as a Model Context Protocol server —
protocol version 2025-11-25. Full elicitation (form + URL), a
persistent SSE push channel for progress notifications, and no external MCP SDK —
pure async Python, hand-rolled JSON-RPC 2.0. The live tool and resource reference
is at /mcp/docs.
| Property | Value |
|---|---|
| Server name | musehub-mcp |
| Server version | 0.2.0 |
| Protocol version | 2025-11-25 |
| Transports | stdio (local), HTTP Streamable (production) |
| Tools | /mcp/docs — live reference with parameter schemas |
| Resources | /mcp/docs#resources — live reference |
| Prompts | /mcp/docs#prompts — live reference |
Transports
Stdio — local development
Newline-delimited JSON-RPC 2.0 over stdin/stdout. No authentication required —
the process is trusted. Logs go to stderr. User identity defaults to
"stdio-user".
python -m musehub.mcp.stdio_server
HTTP Streamable — production
Full MCP 2025-11-25 Streamable HTTP implementation. Three endpoints:
| Method | Path | Purpose |
|---|---|---|
POST |
/mcp |
All client→server JSON-RPC messages (requests, notifications, batches). Returns application/json or text/event-stream for elicitation-powered tools. |
GET |
/mcp |
Persistent SSE push channel. Carries elicitation requests, progress notifications, server messages. |
DELETE |
/mcp |
Client-initiated session termination. Closes SSE queues, cancels pending elicitations. |
Launching the server
MuseHub ships the MCP server as a built-in service. Use muse hub mcp
to start it without standing up the full web stack — useful for local development,
scripted agent pipelines, and IDE integrations.
# HTTP Streamable — listens on http://127.0.0.1:1337/mcp by default muse hub mcp # Custom port muse hub mcp --port 8080 # Target a specific hub instead of localhost muse hub mcp --hub http://staging.musehub.ai # Stdio mode (newline-delimited JSON-RPC on stdin/stdout — no HTTP layer) muse hub mcp --stdio
# muse hub mcp output
musehub-mcp v0.2.0 (protocol 2025-11-25)
transport : HTTP Streamable
endpoint : http://127.0.0.1:1337/mcp
tools : 119 (45 read · 59 write · 2 elicitation · 11 coord · 2 workspace)
resources : 28
prompts : 11
auth : MSign ed25519 — write tools require a fresh signature per request
Ready.
| Flag | Default | Purpose |
|---|---|---|
--port N | 1337 | HTTP listen port |
--host ADDR | 127.0.0.1 | HTTP bind address |
--hub URL | hub from repo config | Override hub target |
--stdio | off | Stdio transport instead of HTTP |
--no-auth | off | Disable MSign check (dev only) |
--log-level LEVEL | info | Logging verbosity |
Connecting
Claude Desktop
Add the MCP server to Claude Desktop's config file at
~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows).
// ~/Library/Application Support/Claude/claude_desktop_config.json { "mcpServers": { "musehub": { "command": "muse", "args": ["hub", "mcp", "--stdio"] } } }
Claude Desktop uses the stdio transport. The server process inherits your shell
environment — ~/.muse/identity.toml provides the signing key
automatically. Restart Claude Desktop after editing the config.
Cursor
// .cursor/mcp.json { "mcpServers": { "musehub": { "command": "muse", "args": ["hub", "mcp", "--stdio"] } } }
HTTP Streamable (generic client)
# 1. Initialize — get session ID POST http://staging.musehub.ai/mcp Content-Type: application/json { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": { "elicitation": { "form": {}, "url": {} } }, "clientInfo": { "name": "my-agent", "version": "1.0" } } } # Response header: Mcp-Session-Id: <session-id> # 2. Open SSE push channel GET http://staging.musehub.ai/mcp Mcp-Session-Id: <session-id> Accept: text/event-stream # 3. Call tools (include session ID on every request) POST http://staging.musehub.ai/mcp Content-Type: application/json Mcp-Session-Id: <session-id> Authorization: MSign handle="gabriel" ts=1745280000 sig="<base64url>" { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "musehub_set_context", "arguments": { "owner": "gabriel", "slug": "muse" } } } # 4. Terminate session DELETE http://staging.musehub.ai/mcp Mcp-Session-Id: <session-id>
Session lifecycle
Sessions are created at initialize and identified by a 32-byte
URL-safe token returned in the Mcp-Session-Id response header.
Every subsequent request must include this header.
| Property | Value |
|---|---|
| Session TTL | 15 minutes from last activity; each request resets the timer |
| Background GC | Stale sessions expired every 5 minutes |
| Max sessions | MUSEHUB_MAX_MCP_SESSIONS env var (default 10,000); returns 503 if exceeded |
| Event buffer | Ring buffer of 50 most recent SSE events per session — replayed on Last-Event-ID reconnect |
| Pending elicitations | Stored as asyncio.Future objects; cancelled on DELETE /mcp |
The initialize response instructions field embeds a
4-step quick-start guide for agents: focus session → orient → branch on
error_code not message text → use coordination APIs for parallel work.
Server capabilities
{
"capabilities": {
"tools": { "listChanged": false },
"resources": { "subscribe": false, "listChanged": false },
"prompts": { "listChanged": false },
"elicitation": { "form": {}, "url": {} },
"logging": {}
}
}
Tools, resources & prompts
The live reference — with expandable parameter schemas, category filtering, and always-current counts — is at /mcp/docs.
All tools follow JSON Schema inputSchema. Repo-scoped tools accept
either repo_id (sha256 genesis hash) or owner +
slug. If musehub_set_context was called first, all
three can be omitted — the session context supplies them automatically.
Tool annotations (MCP 2025-11-25)
| Category | readOnlyHint | destructiveHint | openWorldHint |
|---|---|---|---|
| Read tools | true | false | false |
| Write tools | false | false | false |
| Elicitation tools | false | false | true |
Input schema pattern
{
"name": "musehub_list_commits",
"description": "List commits on a MuseHub repository (newest first).",
"inputSchema": {
"type": "object",
"properties": {
"repo_id": { "type": "string", "description": "sha256 genesis hash (alt: owner+slug)" },
"owner": { "type": "string" },
"slug": { "type": "string" },
"branch": { "type": "string", "description": "Branch name filter" },
"limit": { "type": "integer", "default": 20, "minimum": 1 }
},
"required": [] // context from musehub_set_context satisfies repo args
}
}
Elicitation
Elicitation lets a tool pause mid-execution to ask the user (or agent) for input before continuing. MuseHub supports both modes from MCP 2025-11-25: form (JSON Schema input) and URL (open browser, then confirm).
Form elicitation flow
- Tool calls
await ctx.elicit_form(schema, message). - Server pushes an
elicitation/createSSE event to the client with mode"form", a human-readablemessage, and arequestedSchema(JSON Schema). - Client renders the form; user fills it in.
- Client POSTs a JSON-RPC response (no
method) withaction: "accept"andcontent: {…}. - Server resolves the pending
asyncio.Future; tool receives the user's data and continues.
// SSE event pushed by server { "jsonrpc": "2.0", "id": "<request_id>", "method": "elicitation/create", "params": { "mode": "form", "message": "Configure your review", "requestedSchema": { "type": "object", "properties": { "dimension_focus": { "type": "string", "enum": ["security", "performance", "all"] }, "review_depth": { "type": "string", "enum": ["quick", "thorough"] } }, "required": ["dimension_focus"] } } } // Client response (POST /mcp) { "jsonrpc": "2.0", "id": "<request_id>", "result": { "action": "accept", "content": { "dimension_focus": "security", "review_depth": "thorough" } } } // or: { "result": { "action": "decline" } }
Worked example — musehub_review_proposal_interactive
This is a four-step round-trip. The tool call arrives on POST /mcp,
the server pauses mid-execution and pushes an elicitation to the SSE channel,
the client responds, and the tool completes with a full review.
Step 1 — client calls the tool
POST /mcp
Authorization: MSign handle="gabriel" alg="ed25519" ts=1746576000 sig="Zj8…"
Mcp-Session-Id: ses_4f2a9b3d
Content-Type: application/json
{
"jsonrpc": "2.0", "id": "call-77", "method": "tools/call",
"params": {
"name": "musehub_review_proposal_interactive",
"arguments": { "owner": "gabriel", "slug": "muse", "proposal_id": "af54753d" }
}
}
# Server responds: 200 OK with Content-Type: text/event-stream
# (the response body is an SSE stream because the tool needs to elicit)
Step 2 — server pushes elicitation request to SSE channel
id: 58
event: elicitation/create
data: {
"jsonrpc": "2.0",
"id": "call-77",
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "How should I focus this review?",
"requestedSchema": {
"type": "object",
"properties": {
"dimension_focus": {
"type": "string",
"enum": ["security", "performance", "correctness", "all"],
"description": "Area to prioritize"
},
"review_depth": {
"type": "string",
"enum": ["quick", "thorough"],
"default": "thorough"
},
"reviewer_note": {
"type": "string",
"description": "Optional context for the reviewer"
}
},
"required": ["dimension_focus"]
}
}
}
Step 3 — client submits the form
POST /mcp
Mcp-Session-Id: ses_4f2a9b3d
Content-Type: application/json
{
"jsonrpc": "2.0", "id": "call-77",
"result": {
"action": "accept",
"content": {
"dimension_focus": "security",
"review_depth": "thorough",
"reviewer_note": "Pay attention to the new auth middleware"
}
}
}
Step 4 — tool completes and returns the review
{
"jsonrpc": "2.0",
"id": "call-77",
"result": {
"ok": true,
"content": [{
"type": "text",
"text": "## Security Review — Proposal #47 (af54753d)\n\n**Risk: MEDIUM**\n\n### Findings\n- `validate_token()` (src/auth/tokens.py:82) — timing-safe comparison not used; switch to `hmac.compare_digest`.\n- `SESSION_SECRET` loaded from env without fallback guard — fails silently in misconfigured deploys.\n\n### Harmony\n- 0 prior conflict patterns for this proposal's changed paths.\n\n### Verdict\nApprove with changes requested."
}]
}
}
"action": "decline" at step 3, the tool falls back to a quick non-interactive review with default settings and returns "elicitation_declined": true in the result.URL elicitation flow
Tool calls await ctx.elicit_url(url, message). Server sends an
elicitation/create SSE event with mode: "url" and a stable
elicitationId. Client opens the URL in a browser. When the user returns,
client sends a notifications/elicitation/complete notification.
Timeout and degradation
Elicitation times out after 300 seconds. The tool receives
None on timeout and should degrade gracefully (e.g. fall back to
defaults or return a partial result). Never block indefinitely.
ToolCallContext API
class ToolCallContext: user_id: str | None is_agent: bool agent_name: str | None has_session: bool has_elicitation: bool # true if client advertised elicitation capability async def elicit_form(schema, message) -> JSONObject | None async def elicit_url(url, message, elicitation_id=None) -> bool async def progress(token, value, total=None, label=None) -> None
SSE streaming
The GET /mcp channel is a persistent text/event-stream.
It carries three categories of server-push events: elicitation requests, progress
notifications, and log messages.
Event wire format (HTML Living Standard)
id: 42
event: notifications/progress
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"t1","progress":3,"total":10,"message":"Analyzing symbols"}}
Event types
| Event type | When | Key payload fields |
|---|---|---|
elicitation/create | Tool calls elicit_form() or elicit_url() | mode, message, requestedSchema or url |
notifications/progress | Tool calls ctx.progress() | progressToken, progress, total, message |
notifications/message | Server emits info/warn/error log | level, logger, data |
notifications/cancelled | Client cancels in-flight tool call | progressToken |
curl — open the SSE channel
# 1. Initialize and capture the session ID SESSION=$(curl -s -X POST http://staging.musehub.ai/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{"elicitation":{"form":{},"url":{}}},"clientInfo":{"name":"curl-test","version":"1.0"}}}' \ -D - | grep -i mcp-session-id | awk '{print $2}' | tr -d '\r') # 2. Open persistent SSE channel (blocks; -N disables buffering) curl -N \ -H "Accept: text/event-stream" \ -H "Mcp-Session-Id: $SESSION" \ http://staging.musehub.ai/mcp
: heartbeat
: heartbeat
id: 1
event: notifications/message
data: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info","logger":"musehub.mcp","data":"Session ses_4f2a9b3d ready."}}
id: 2
event: notifications/progress
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"t7","progress":5,"total":10,"message":"Analyzing symbols"}}
Heartbeat and reconnection
The server injects a : heartbeat SSE comment every 15 seconds to keep
proxies alive. On reconnect, clients pass Last-Event-ID to replay up to
the 50 most recent events from the ring buffer.
# reconnect with event replay from event 37 onward
curl -N \
-H "Accept: text/event-stream" \
-H "Mcp-Session-Id: $SESSION" \
-H "Last-Event-ID: 37" \
http://staging.musehub.ai/mcp
Authentication
Read tools and all resource/prompt reads work without authentication.
Write tools require a fresh MSign signature on every
tools/call request — not just at initialize.
# Authorization header — required on write tool calls Authorization: MSign handle="gabriel" alg="ed25519" ts=1745280000 sig="<base64url>" # Generate with muse sign header muse sign header \ --method POST \ --path /mcp \ --hub http://staging.musehub.ai --json
| Scenario | Auth required |
|---|---|
| Read tools, resources, prompts | No — anonymous access allowed |
Write tools (tools/call) | Yes — fresh MSign per request; error code -32001 if missing |
| Stdio transport | No — trusted local process |
Origin validation guards against DNS-rebinding attacks. Allowed origins are configured
via MUSEHUB_ALLOWED_ORIGINS (comma-separated). Defaults:
http://localhost, http://127.0.0.1, and the hub's own origin.
Requests without an Origin header
(curl, programmatic clients) are always allowed.
Error handling
JSON-RPC protocol errors are returned as error envelopes. Tool-level errors
always return HTTP 200 with a success envelope — branch on the machine-readable
error_code, never parse error_message text.
JSON-RPC error codes
| Code | Constant | Meaning |
|---|---|---|
-32700 | PARSE_ERROR | JSON decode failure |
-32600 | INVALID_REQUEST | Missing or invalid method, bad batch format |
-32601 | METHOD_NOT_FOUND | Unknown method name |
-32602 | INVALID_PARAMS | Parameter type or constraint mismatch |
-32603 | INTERNAL_ERROR | Unhandled server exception |
-32000 | UNAUTHORIZED | Authentication required |
-32001 | WRITE_TOOL_UNAUTHORIZED | Fresh MSign signature required for write tool |
-32042 | URL_ELICITATION_REQUIRED | Client must advertise URL elicitation capability |
Tool-level error envelope
{
"result": {
"ok": false,
"isError": true,
"error_code": "proposal_not_found",
"error_message": "Proposal 123 not found.",
"hint": "Call musehub_list_proposals() to see open proposals."
}
}
Common tool error codes
| error_code | Meaning |
|---|---|
repo_not_found | Repo doesn't exist or access denied |
branch_not_found | Branch doesn't exist on remote |
issue_not_found / proposal_not_found | Issue or proposal number not found |
symbol_not_found | Symbol address doesn't exist in the snapshot |
unauthenticated | Anonymous access, but write operation attempted |
forbidden | Authenticated but lacks permission |
missing_args | Required arguments absent (and no session context set) |
task_not_found / reservation_conflict | Coordination errors |
elicitation_declined | User declined the elicitation form |
db_unavailable | Database unreachable — transient; retry |