Per-agent keys (no plugin)
Bootstrap a long-lived integration without the OpenClaw plugin — provision an agent-scoped credential, identify, and call MCP.
If you're building a Python / Node / Go integration directly against MemClaw — without the OpenClaw plugin runtime — bind every long-lived agent to its own agent-scoped credential rather than calling under the tenant-scoped key. Agent-scoped credentials give you trust gating, fleet membership, per-agent keystones, and a real identity in the dashboard.
Both kinds of credential use the mc_ prefix on the wire; scope (tenant vs agent) is bound at mint time on the credential row, not encoded in the prefix. (Pre-existing mca_… keys continue to authenticate via back-compat.)
The single tenant-scoped flow on the REST and MCP pages is fine for one-off scripts; the four-step flow here is for anything that ships.
1. Mint an agent-scoped credential
POST /api/v1/admin/agent-keys/provision mints an agent-scoped credential and creates the Agent row eagerly, so a follow-up PATCH /agents/{id}/trust works in the same session without a synthetic first write.
curl -X POST "https://memclaw.net/api/v1/admin/agent-keys/provision" \
-H "X-API-Key: $MC_TENANT_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "quote-agent-na",
"label": "north-america CRM",
"initial_trust": 1,
"initial_fleet": "na-sales"
}'
Response:
{
"id": "…",
"tenant_id": "…",
"agent_id": "quote-agent-na",
"raw_key": "mc_…",
"agent_row_created": true,
"created_at": "…"
}Save raw_key immediately — it is only returned once. agent_row_created: true confirms the Agent row exists; initial_trust and initial_fleet were applied in the same round-trip.
Request body fields:
agent_id(required) — stable identifier for this agent. Surfaces in/whoami, audit, and per-agent dashboards.label— human-readable hint in the dashboard.initial_trust—0read-only /1write to home fleet /2cross-fleet read /3cross-fleet write + delete. Default1.initial_fleet— fleet membership; omit for tenant-wide scope.display_name— human-readable name surfaced on the dashboard.
2. Verify identity (/whoami)
Before any real tool call, confirm MemClaw resolves your credentials the way you expect:
curl "https://memclaw.net/api/v1/whoami" \
-H "X-API-Key: $AGENT_KEY"
{
"tenant_id": "your-tenant-id",
"agent_id": "quote-agent-na",
"auth_source": "gateway-header",
"via_gateway": true
}If agent_id is null you're sending a tenant-scoped credential, not an agent-scoped one — the gateway only injects X-Agent-ID on the agent-scoped path. (Both kinds share the mc_ prefix; the dashboard "Kind" column tells you which is which.)
3. Open an MCP session
MemClaw speaks MCP streamable-http at /mcp (both /mcp and /mcp/ work). The server accepts the API key on either header — use whichever your SDK supports:
| Header | When to use |
|---|---|
X-API-Key: mc_… | Canonical. Use if you control the request shape. |
Authorization: Bearer mc_… | OAuth-style. Required by Anthropic's remote-MCP integration and SDKs that only emit Authorization headers. |
Dashboard-issued JWTs are also accepted via Authorization: Bearer <jwt>; the server tries JWT decode first.
Python (the mcp library)
import asyncio
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
headers = {"X-API-Key": "mc_..."} # agent-scoped credential
url = "https://memclaw.net/mcp/"
async with streamablehttp_client(url, headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([t.name for t in tools.tools])
result = await session.call_tool(
"memclaw_write",
{"content": "First memory from the Python harness."},
)
# Always inspect both result.isError and the content envelope.
# Gateway-side refusals (FORBIDDEN, INVALID_ARGUMENTS, NOT_FOUND, ...)
# arrive as isError=True with a JSON {"error":{...}} body — naive
# clients that only check content[0].text for "id" will silently
# treat failure as success.
if result.isError:
print("tool refused:", result.content[0].text)
else:
print(result)
asyncio.run(main())
Anthropic SDK (remote-MCP)
from anthropic import Anthropic
client = Anthropic()
msg = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{"role": "user", "content": "Save this note: pricing meets 10 May."}],
extra_body={
"mcp_servers": [
{
"type": "url",
"url": "https://memclaw.net/mcp/",
"name": "memclaw",
"authorization_token": "mc_...", # agent-scoped credential
}
]
},
)
print(msg.content)
The SDK forwards authorization_token as Authorization: Bearer mc_…. MemClaw recognises that shape and resolves tenant + agent identity from it.
4. Elevate trust (when needed)
If you set initial_trust in step 1, skip this. Otherwise:
curl -X PATCH "https://memclaw.net/api/v1/agents/quote-agent-na/trust?tenant_id=$TENANT_ID" \
-H "X-API-Key: $MC_TENANT_KEY" \
-H "Content-Type: application/json" \
-d '{"trust_level": 2}'
Trust levels:
0— read-only.1— write to home fleet.2— cross-fleet read.3— cross-fleet write + delete + update others' memories.
End-to-end bootstrap, one block
TENANT_KEY=mc_...
AGENT_ID="quote-agent-na"
FLEET_ID="na-sales"
# 1. Provision agent + Agent row + trust + fleet in one call.
RESP=$(curl -s -X POST "https://memclaw.net/api/v1/admin/agent-keys/provision" \
-H "X-API-Key: $TENANT_KEY" \
-H "Content-Type: application/json" \
-d "{\"agent_id\":\"$AGENT_ID\",\"initial_trust\":1,\"initial_fleet\":\"$FLEET_ID\"}")
AGENT_KEY=$(echo "$RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['raw_key'])")
# 2. Verify.
curl -s "https://memclaw.net/api/v1/whoami" -H "X-API-Key: $AGENT_KEY"
# 3. Use.
curl -s "https://memclaw.net/api/v1/memories" \
-H "X-API-Key: $AGENT_KEY" \
-H "Content-Type: application/json" \
-d "{\"agent_id\":\"$AGENT_ID\",\"fleet_id\":\"$FLEET_ID\",\"content\":\"Hello world\"}"
Four steps, one round-trip per agent. No provision → fake-write → patch-trust dance.
Idempotency
A write of identical content (same agent_id, same fleet_id) is retry-safe via MCP:
- First call →
201with the new memory id. - Identical retry →
200with{ "status": "duplicate", "existing_id": "…" }.
Cross-agent writes of identical content no longer collide — each agent gets its own record.
Authoring keystones
When you want an agent to author governance rules (keystones), use memclaw_keystones_set over MCP:
result = await session.call_tool(
"memclaw_keystones_set",
{
"op": "set",
"doc_id": "no-rollback-to-cve-versions",
"title": "Refuse rollback to CVE versions",
"content": "Never recommend rolling back to a version listed in any CVE entry.",
"scope": "tenant",
"weight": "high",
},
)Two gotchas worth knowing before you write the call:
agent_idinmemclaw_keystones_setnames the TARGET agent the rule binds to, not the caller. Pass it only forscope=agent. Forscope=tenantorscope=fleet, omit it — passing it returnsINVALID_ARGUMENTS.- The companion read tool
memclaw_keystonesreturns the merged rule set under the JSON keyrules(notkeystones).
Trust gating is dynamic: self-authoring (scope=agent + target = caller) needs trust ≥ 1; anything else (fleet, tenant, or targeting another agent) needs trust ≥ 2.
Common pitfalls
POST /provisionreturns the raw key once. Save it before the response goes out of scope.PATCH /agents/{id}/trustreturns 404 immediately after provisioning. Means the Agent row was not materialised atomically — should not happen on builds after 2026-05-14. Check/whoamiandGET /api/v1/agents/{id}; ifagent_row_created: falseis in the provision response, the deployment is missingCORE_STORAGE_API_URL.- MCP tool result has
isError=Falsebutcontent[0].textis{"error": …}. Pre-2026-05-15 builds leftisErrorat the default for gateway-side refusals. On current builds, every structured-error envelope arrives withisError=True. - Streaming client hangs on
/mcp(no slash). Older builds redirected/mcp→/mcp/; current builds serve both paths in-process.
Reference
POST /api/v1/admin/agent-keys/provision— atomic provisioning (this guide).GET /api/v1/whoami— identity probe.GET /api/v1/agents/{id}?tenant_id=…— agent detail.PATCH /api/v1/agents/{id}/trust?tenant_id=…— change trust level.POST /api/v1/memories— REST write (mirrorsmemclaw_writeover MCP).mcp://…/mcp/— streamable-http MCP endpoint.