Connecting to Semurg over MCP: the endpoint, both protocol eras, tools and errors
12 October 2026 · Semurg · 4 min read · Markdown
Verified: every command below ran on a fresh node of r11.78.20261012.bc0d94f (how)
connectionsmcpagentsprotocolhow-to
Most people reach Semurg's memory through an agent or a framework that already speaks the Model Context Protocol, and for them the five-minute guide and the agents page are enough. This post is for the people writing or debugging a client: what the endpoint expects, which versions of the protocol it speaks, and what comes back.
One endpoint, one token
The endpoint is /v1/mcp on your node, spoken over HTTP with POST. It takes the node's API token as a bearer token. The optional X-Semurg-Scope header sets a default scope for the connection, so a client configured once reads and writes one project's memory; a tool call can still name its own scope, and the call's argument wins.
The endpoint keeps no session. It never mints an Mcp-Session-Id and never needs one, so any request is served on its own and a client that reconnects loses nothing. For the same reason there is no event stream to open and no session to close: GET and DELETE answer 405 with Allow: POST.
export SEMURG_API_TOKEN=<your token>
curl -si http://localhost:4000/v1/mcp -H "Authorization: Bearer $SEMURG_API_TOKEN" | head -1Expected: HTTP 405: the endpoint takes POST only
Two protocol eras at one address
The endpoint speaks both generations of the protocol and chooses by how the client opens:
- The stateless revision,
2026-07-28: every request carries the protocol version and the client's capabilities in its_meta, mirrored in theMCP-Protocol-VersionandMcp-Methodheaders (andMcp-Nameon a tool call). There is noinitialize;server/discoverdescribes the server. - The earlier revisions,
2025-11-25,2025-06-18and2025-03-26: aninitializerequest selects them. The handshake is answered without keeping anything, and every request after it is served on its own.
Here is the earlier style end to end, with nothing but curl. First the handshake:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/mcp \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "curl", "version": "one"}}}'Expected: the server agrees the protocol version and names itself
Then the tools. The list is derived from the memory door itself, so every tool here is a call the node really serves, each with its description and the limits it refuses by name:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/mcp \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'Expected: the seven memory tools, among them memory_recall and memory_remember
And one tool call, which needs no arguments:
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/mcp \
-H "Authorization: Bearer $SEMURG_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"memory_status","arguments":{}}}'Expected: the tool's answer as content: whether memory is on and which scopes hold facts
The tools
memory_status, memory_remember, memory_recall, memory_relate, memory_forget, memory_context and memory_document. Each tool's description, which the tools/list answer carries, is written beside the operation it calls and states its limits, so an agent reading the list learns what the node will refuse before it tries. The same six operations are plain HTTP routes too; the REST post covers those.
When something goes wrong
A refused tool call answers isError: true, with the same status, error and detail the HTTP route would give for the same mistake: one refusal reads the same on every door, and a refusal is never turned into an empty success.
Protocol errors use JSON-RPC codes:
-32022, an unsupported protocol version: the answer lists the versions the endpoint speaks.-32020, a header that disagrees with the body, in the stateless revision.-32602, invalid parameters, including a missing_metafield in the stateless revision.-32601, an unknown method.
In the stateless revision these come back with an HTTP error status; in the earlier revisions a JSON-RPC error rides on HTTP 200, as those revisions define. A missing or wrong token is refused with 401 and a WWW-Authenticate: Bearer header before the protocol is read at all.
Next
- The five-minute guide connects Claude Code and shows the same memory over plain HTTP.
- The agents page has a tested program for each agent framework.
How these commands were checked
Our test farm ran each of the 4 command blocks marked runnable, exactly as written, with the answer checked against the expectation printed under it, against a fresh node of the published release r11.78.20261012.bc0d94f. A post is published only when every one of them passed there.