---
title: Connecting to Semurg over MCP: the endpoint, both protocol eras, tools and errors
slug: connect-over-mcp
date: 2026-10-12
author: Semurg
section: how-to
tags: [connections, mcp, agents, protocol, how-to]
summary: The node's Model Context Protocol endpoint, at the level a client author needs: one address, the token, both protocol eras it speaks, the tools it lists, and how a refusal comes back.
status: published
covers: [route:POST /v1/mcp, route:GET /v1/mcp, route:DELETE /v1/mcp, mcp:memory_status, mcp:memory_document, mcp:memory_remember, mcp:memory_recall, mcp:memory_relate, mcp:memory_forget, mcp:memory_context]
---
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](/blog/agent-memory-mcp-five-minutes) and the [agents page](/agents) 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.

> Diagram two-eras: A client that opens with initialize gets an earlier protocol revision; a client that sends the version in every request's metadata gets the stateless revision; both reach the same endpoint, which keeps no session, and the same seven memory tools.

## 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`.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -si http://localhost:4000/v1/mcp -H "Authorization: Bearer $SEMURG_API_TOKEN" | head -1
```

```expect
expect: HTTP 405: the endpoint takes POST only
assert: line1~^HTTP/[0-9.]+ 405
```

## 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 the `MCP-Protocol-Version` and `Mcp-Method` headers (and `Mcp-Name` on a tool call). There is no `initialize`; `server/discover` describes the server.
- The earlier revisions, `2025-11-25`, `2025-06-18` and `2025-03-26`: an `initialize` request 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:

```bash runnable kind=shell-node needs=token
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"}}}'
```

```expect
expect: the server agrees the protocol version and names itself
assert: json:.result.protocolVersion=="2025-06-18"
```

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:

```bash runnable kind=shell-node needs=token
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"}'
```

```expect
expect: the seven memory tools, among them memory_recall and memory_remember
assert: json:([.result.tools[].name]|index("memory_recall"))!=null and ([.result.tools[].name]|index("memory_remember"))!=null
```

And one tool call, which needs no arguments:

```bash runnable kind=shell-node needs=token
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":{}}}'
```

```expect
expect: the tool's answer as content: whether memory is on and which scopes hold facts
assert: json:(.result.content|length)>=1
```

## 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](/blog/connect-over-rest) 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.

> Diagram mcp-doors: An MCP tool call, an HTTP route and a WebSocket event all reach the same memory code, so the same mistake gets the same refusal and the same call the same answer on every door.

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 `_meta` field 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](/blog/agent-memory-mcp-five-minutes) connects Claude Code and shows the same memory over plain HTTP.
- The [agents page](/agents) has a tested program for each agent framework.
