Connect an agent
Every agent connects the same way: one URL and one API key.
| MCP endpoint | https://api.memoryrelay.net/mcp (Streamable HTTP) |
| Authentication | Authorization: Bearer mem_… or X-API-Key: mem_… |
| REST API | https://api.memoryrelay.net with the same key |
Give each agent its own key under Connected agents in the app. You pick what the key may do and which workspaces it may touch; after the key is created, the page shows the setup below with copy buttons, already filled in. The secret is shown once, so set it where the agent runs:
export MEMORYRELAY_API_KEY=mem_prod_xxxxx
Every snippet on this page reads the key from MEMORYRELAY_API_KEY, so the
config files hold no secret and can be committed.
The memory tools work for every key. The icm_* tools work on any workspace the
key is confined to while that workspace is switched on; a workspace the operator
has switched off answers that ICM is unavailable rather than falling back to
memory search.
Claude Code
Add it once per machine:
claude mcp add --transport http memoryrelay https://api.memoryrelay.net/mcp \
--header "Authorization: Bearer $MEMORYRELAY_API_KEY"
Or commit a .mcp.json at the root of the repository. Claude Code expands
${MEMORYRELAY_API_KEY} from the environment:
{
"mcpServers": {
"memoryrelay": {
"type": "http",
"url": "https://api.memoryrelay.net/mcp",
"headers": { "Authorization": "Bearer ${MEMORYRELAY_API_KEY}" }
}
}
}
The Claude Code plugin
The memoryrelay plugin adds the same server plus the habits that make it
useful in every repository, so no CLAUDE.md has to explain them:
- Session start: the agent is told the repository (
owner/namefromgit remote get-url origin) and the step, if one is known (astepin the directory's.memoryrelay.json, else the branch prefix:fix/→fix,feat/→implement,release/,review/). It callsicm_context_for; when that answersno_bindingit looks the repository up withicm_rootandicm_route_listand builds a route withicm_context_build. Then it searches the repository's memories withmemory_search. - Stop: once per context receipt that has no report, the agent is asked to
call
icm_report_reads(the supplied files it read) and, when the task has ended,icm_report_outcome(success, partial, failed or abandoned, with the pull request as evidence). - After a compaction: the agent stores durable findings with
memory_store, under the repository's namespace and tagged with it. - Privacy filter: a
memory_storeormemory_updatewhose content looks like a password, key, token, private key or personal identifier is refused before it leaves the machine. - Commands:
/memoryrelay:memory-search,memory-store,memory-list,memory-get,memory-update,memory-delete,memory-entity,memory-agent,memory-health,icm-context,icm-searchandicm-report, each calling the tool of the same purpose.
Install it from the MemoryRelay marketplace (the plugin's source is
plugins/memoryrelay
in the API repository):
claude plugin marketplace add memoryrelay/memoryrelay-marketplace
claude plugin install memoryrelay@memoryrelay
or, inside a session, /plugin marketplace add memoryrelay/memoryrelay-marketplace
and /plugin install memoryrelay@memoryrelay. To try it from a clone of the
API repository instead: claude --plugin-dir plugins/memoryrelay.
It needs MEMORYRELAY_API_KEY set where Claude Code starts, and Node.js on the
PATH (the hooks are Node scripts, so they run the same on Windows, macOS and
Linux). Give the key icm:context, icm:observe, read and write; without
icm:context the agent skips pinned context and says so once. If you already
added the server with claude mcp add, Claude Code connects once and uses your
entry; the plugin's hooks work with either.
The plugin replaces memoryrelay/claude-plugin, whose hooks called tools the
API no longer serves. The marketplace entry keeps its name, so an existing
memoryrelay@memoryrelay install moves to this source when you refresh the
marketplace (/plugin marketplace update memoryrelay) and update the plugin;
a copy installed from memoryrelay/claude-plugin by hand should be removed.
Codex
In ~/.codex/config.toml (or the project's .codex/config.toml):
[mcp_servers.memoryrelay]
url = "https://api.memoryrelay.net/mcp"
bearer_token_env_var = "MEMORYRELAY_API_KEY"
This needs a Codex CLI that supports Streamable HTTP servers; older releases also
need experimental_use_rmcp_client = true at the top of the file.
Any other MCP client
Point any client that speaks MCP over Streamable HTTP at
https://api.memoryrelay.net/mcp with the Authorization: Bearer (or
X-API-Key) header. The server is stateless: it issues no session id, answers
every request with a single JSON body, and answers GET /mcp with 405, which
tells a client there is no server-initiated stream.
Fewer tools: ?tools=
An agent that only needs pinned context does not need the memory tools in its
tool list. Add ?tools= to the URL with one or more groups, comma-separated:
| Value | Tools |
|---|---|
icm | the icm_* tools |
core | the memory, entity and agent tools |
context | context_build_v2 |
all, or no tools= | everything (the default) |
For example https://api.memoryrelay.net/mcp?tools=icm. The filter narrows both
the tool list and what can be called: calling a tool outside it gets the same
"Unknown tool" error as a tool that does not exist. It never adds a right: each
call is still decided by the key's scopes and workspaces. An unknown group name
is refused with 400 rather than leaving the agent with no tools. The groups are
the npm package's MEMORYRELAY_TOOLS groups.
To run the server locally over stdio instead (for example in Claude Desktop):
{
"mcpServers": {
"memoryrelay": {
"command": "npx",
"args": ["-y", "@memoryrelay/mcp-server"],
"env": { "MEMORYRELAY_API_KEY": "mem_prod_xxxxx" }
}
}
}
Connectors added through claude.ai or Claude Desktop's connector settings sign
in with OAuth. MemoryRelay does not offer OAuth yet, so those connectors cannot
use the remote endpoint; use the local npx configuration above in Claude
Desktop meanwhile.
REST and the Python SDK
curl -H "Authorization: Bearer $MEMORYRELAY_API_KEY" \
https://api.memoryrelay.net/v2/icm/workspaces
# pip install memoryrelay
from memoryrelay import MemoryRelay
client = MemoryRelay() # reads MEMORYRELAY_API_KEY
print(client.icm.list_workspaces())
What the agent can do
The remote endpoint and the npm package serve the same tools. A tool call is decided exactly as the matching REST call: the key's scopes, the workspaces it was given, and its role there (a key is at most a contributor). A refusal names the missing scope; it is recorded in the workspace's activity like any other.
| Tools | Scope | What for |
|---|---|---|
icm_context_for, icm_resolve | icm:context | The context bound to this repo and step, pinned to a version |
icm_root | icm:context | What your workspaces include and what is bound; a table, never a fallback |
icm_workspace_list, icm_route_list, icm_context_build, icm_release_get, icm_receipt_get, icm_source_scan, icm_score, icm_maintenance | icm:context | Build context for an entry, route, stage, record or notes; read the repository scan, the scorecard and what needs re-verifying |
icm_search | any icm:* | Find which sections of your workspaces' files answer a question: ranked, with the path and lines to read next. It sees only what the key could read file by file |
icm_pull | any icm:* | The command that writes a workspace to a folder (Use a workspace as a folder) |
icm_run_start, icm_stage_context, icm_stage_write, icm_stage_submit, icm_run_status | icm:run | Do a stage of a run and hand it to a person |
icm_report_reads | icm:observe | Report which supplied files the agent read |
icm_draft_get, icm_draft_write, icm_draft_propose | icm:run, a key confined to the workspace | Change a workspace's files as a draft and propose it: a person publishes it, or merges its pull request on a Git workspace (Work on a Git workspace) |
memory_search, memory_store, memory_list, memory_get, … | read, write, delete | Memory as evidence |
A run, as an agent does it
icm_run_start {workspace_id, name}pins a run to the live version.icm_stage_context {workspace_id, run_id, stage}starts the stage and returns its contract and its context: the entry, the stage's contract, its inputs and the outputs earlier stages had approved. It also lists the output paths.icm_stage_write {…, path, content}saves each output. Writing over an output needs the revision you last saw, so an output a person edited is never overwritten blindly.icm_report_reads {…, receipt_id, paths}records what the agent actually read.icm_stage_submit {…, stage}hands the stage to a person and returns thereview_url.
Approval stays with people. No tool approves, sends back or publishes, and
the API refuses a key that tries. A person reads the output at the review link,
edits it in place if needed, and approves or sends it back with a note; the next
stage can only start after that. When a stage comes back, icm_stage_context
starts it again and returns the reviewer's note.
Keys and sessions
The MCP endpoint accepts API keys only. A signed-in session token is refused, because an agent holding it would act as the person, and approving or publishing must stay with that person.
What a key can see about itself
GET /v2/icm/capabilities (and the icm_capabilities tool) includes a
principal block describing the caller, never another key and never a secret:
{
"principal": {
"kind": "api_key",
"key_id": "5d0e…",
"name": "Command Center",
"scopes": ["icm:context", "icm:observe", "read"],
"icm_confined": true,
"expires_at": "2027-03-01T00:00:00+00:00",
"workspace_ids": ["9b1c…"]
}
}
workspace_ids are the workspaces granted to the key itself. For a person,
kind is person, and key_id, name and expires_at are null. A client
can use it to warn when its key is broader than it needs (write, admin,
memory scopes, not confined, no expiry). A key confined to its workspaces cannot
create a workspace (403 key_confined): a person creates it and grants the key.
For client apps: links into MemoryRelay
Some things are always a person's to do in MemoryRelay: creating a workspace,
connecting its repository, making it ICM-ready, granting a key. An app that
integrates MemoryRelay (Command Center, for example) links the person to a
prefilled form instead of doing it. Opening a link changes nothing; the person
reviews the form and presses the button. A parameter that does not pass its
check (repository owner/name, branch and folder characters, template slug,
key id) is ignored.
Link (on https://app.memoryrelay.ai) | Opens |
|---|---|
/dashboard/workspaces/new?name=&authority=git&repository=owner/name&ref=&path=&template=<slug>&form= | New workspace, prefilled; after Create, Source carries the branch and folder |
/dashboard/w/{workspace_id}/source?setup=1&repository=&ref=&path= | Source settings, prefilled when no repository is connected yet |
/dashboard/w/{workspace_id}/source?make_ready=1&template=<slug> | Make it ICM-ready, with that template preselected (people with the publisher role) |
/dashboard/w/{workspace_id}/members?grant_key=<key_id> | Members, with that key preselected in the grant form (role contributor by default) |
The app then reads the result back with its own key: the workspace appears in
principal.workspace_ids once it is granted.
Observations are idempotent
POST /v2/icm/workspaces/{id}/receipts/{receipt_id}/observations takes an
event_id (1 to 128 characters) that is unique per receipt. Retrying with the
same event_id writes nothing and answers 200 with the observation already
stored (recorded: false, the same observation_id); the first answer is 201
with recorded: true. Use a stable id such as <run id>:<delivery id>.