Integrate with Caura without the plugin
September 23, 2026 · View on GitHub
Audience: developers building Python, Node, or any other SDK client against caura.ai or a self-hosted Caura (formerly MemClaw) instance, without installing the OpenClaw plugin runtime.
Time to first tool call: ~5 minutes.
One credential model. Every API credential — tenant-scoped, agent-scoped, install, cross-tenant — uses the
mc_prefix on the wire and lives in one underlying credential table. Scope is bound to the credential row at mint time, not encoded in the prefix. Pre-existingmca_…/mci_…keys continue to authenticate via back-compat; new mints all returnmc_….
What you need
- A tenant-scoped
mc_credential — get one fromcaura.ai/settings/api-keys(or self-host). - An MCP client. Examples below use
mcp(Python) andcurl. The same flow works withanthropic(Python SDK's remote-MCP integration),openai, or any client that speaks MCP streamable-http.
1. Mint an agent-scoped credential
Every long-lived integration should bind to a named agent identity rather than calling under a tenant-scoped credential. Two ways:
- Dashboard (recommended for humans):
caura.ai/settings/organization/api-credentials— single-card wizard, one-time raw-key reveal, manages cross-tenant + read-scope settings. - API (for scripted provisioning, shown below):
POST /api/v1/admin/agent-keys/provision— atomic call that creates the Agent row eagerly so subsequent trust-elevation or fleet-assignment endpoints work immediately:
curl -X POST https://caura.ai/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's only returned once. The returned credential uses the mc_ prefix regardless of kind; the gateway derives scope (tenant / agent / cross-tenant) from the credential row, not the prefix. agent_row_created: true confirms the Agent row exists and PATCH /agents/quote-agent-na/trust will work without a synthetic first write.
Optional fields on the provision request:
initial_trust—0,1,2,3(default1).initial_fleet— fleet membership; absent = no fleet, and writes then landfleet_id: null, which is tenant-shared by design: every fleet'ssearchandrecallsee the row. Note the asymmetry before you use counts as a health check — a fleet-filteredGET /memoriesor/statsdoes not return those rows today, so a fleet-less agent's writes answer teammates' searches while being absent from their listings.display_name— human-readable name surfaced on the dashboard.
2. Verify your identity (/whoami)
Before making real tool calls, confirm Caura resolves your credentials the way you expect:
curl https://caura.ai/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 or doesn't match what you provisioned, your credential isn't recognized as agent-scoped. Common causes:
- You're sending a tenant-scoped credential, not the agent-scoped one you provisioned.
- The credential was revoked or rotated.
- A proxy in front of Caura is stripping the
X-API-Keyheader.
Latency expectation:
POST /searchreturns 23 ms p50 / 27 ms p95 warm on our reference benchmarks. Recall (caura_recall/POST /recall) sits in the same band — it wraps search plus a small scoring step. Seeperformance.mdfor the full numbers and methodology.
3. Open an MCP session
Caura speaks MCP streamable-http at /mcp (the trailing slash is optional; both /mcp and /mcp/ work).
Authentication: two headers, your choice
Caura accepts the credential on either of these — pick 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 other SDKs that only emit Authorization headers. |
Legacy mca_… / mci_… keys continue to authenticate on both headers via back-compat. JWTs from the dashboard are also accepted via Authorization: Bearer <jwt>; Caura distinguishes them by trying 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://caura.ai/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(
"caura_write",
{"content": "First memory from the Python harness."},
)
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://caura.ai/mcp/",
"name": "caura",
"authorization_token": "mc_...", # agent-scoped credential
}
]
},
)
print(msg.content)
The SDK forwards authorization_token as Authorization: Bearer mc_…. Caura recognises that shape and resolves your tenant + agent identity from the credential row.
4. Elevate trust (when needed)
The default trust_level=1 lets the agent write to its home fleet. Elevate when you need cross-fleet writes, keystone authoring, or deletes:
curl -X PATCH "https://caura.ai/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.
If you provisioned with initial_trust, this step is already done — confirm with /whoami.
5. Author a keystone (governance rule)
Keystones are mandatory rules that override conflicting instructions. Authoring a
scope=fleet/scope=tenant rule needs trust ≥ 2 (see above). The one tier below
that is self-authoring: scope=agent with an explicit agent_id equal to the
calling agent drops to trust ≥ 1. Omitting agent_id on a scope=agent rule
does not mean "myself" — it names no target, so it falls back to the trust ≥ 2
bar (and is rejected by shape validation regardless).
Two things can hold the floor at ≥ 2 even for a correctly-shaped self rule, so treat trust 1 as the floor for the shape rather than a guarantee for the call:
- An existing rule at the same
doc_id. The required trust is the higher of what your new body needs and what the already-stored rule needs. Overwriting ascope=fleetrule takes trust ≥ 2 no matter how you shape the replacement — this is what stops a trust-1 agent quietly replacing a fleet-wide rule with a private one. - An unverified caller identity. The self-author tier needs an agent-scoped
credential (
POST /admin/agent-keys/provision), not anX-Agent-IDheader sent alongside a tenant/admin key, so that an admin-key holder can't forge a rule in another agent's name.
curl -X POST "https://caura.ai/api/v1/keystones" \
-H "X-API-Key: $MC_TENANT_KEY" \
-H "X-Agent-ID: quote-agent-na" \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "'"$TENANT_ID"'",
"doc_id": "no-secrets-in-logs",
"title": "No secrets in logs",
"content": "Never log credentials or API keys.",
"scope": "tenant",
"weight": "high"
}'
doc_id is required — a stable kebab-case slug you choose
(^[a-z0-9][a-z0-9._-]{0,99}$). It's the rule's identity: re-POSTing the same
doc_id upserts (edits) that rule rather than creating a duplicate. weight is
low / med / high (stored as 25 / 50 / 100). For scope=fleet/agent
also pass fleet_id; scope=agent additionally takes the target agent_id
(omit agent_id for tenant/fleet).
End-to-end bootstrap, one block
TENANT_KEY=mc_... # tenant-scoped credential
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://caura.ai/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'])")
# AGENT_KEY is an mc_… agent-scoped credential.
# 2. Verify.
curl -s https://caura.ai/api/v1/whoami -H "X-API-Key: $AGENT_KEY"
# 3. Use.
curl -s https://caura.ai/api/v1/memories \
-H "X-API-Key: $AGENT_KEY" \
-H "Content-Type: application/json" \
-d "{\"tenant_id\":\"$TENANT_ID\",\"agent_id\":\"$AGENT_ID\",\"fleet_id\":\"$FLEET_ID\",\"content\":\"Hello world\"}"
Four steps, one round-trip per agent. The bootstrap dance from earlier integration attempts (provision → fake-write → patch trust → seed) is no longer necessary.
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.
Write bodies reject unknown fields
Every write endpoint (POST /api/v1/memories, /api/v1/documents,
PATCH /api/v1/memories/{id}, …) responds 422 to a field it does not
declare, and names it:
{
"error": {
"code": "INVALID_ARGUMENTS",
"message": "unknown field 'tags' is not permitted on this request body (at 'tags')",
"details": { "unknown_fields": ["tags"] }
}
}
This used to be a silent 201: the key was dropped and the write reported
success without it. If you are porting an integration that predates this, run
your write payloads once and fix whatever comes back 422 — those fields were
never being stored.
Search and filter bodies (/api/v1/search, /api/v1/recall,
/api/v1/documents/query, /api/v1/documents/search) still ignore unknown
fields, deliberately. See
api-surfaces.md
for why the two differ.
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. This should not happen post-2026-05-13; if it does, the Agent row was not materialized atomically. CheckwhoamiandGET /api/v1/agents/{id}./mcpreturns 401 with anAuthorization: Bearer mc_…(or legacymca_…) header but works withX-API-Key. Make sure you're hitting a Caura build dated 2026-05-13 or later — earlier builds rejected non-JWT bearer tokens.- A write that used to return
201now returns422. Readerror.details.unknown_fields. The named field is not part of the request model — it was being discarded before, so the fix is to remove it or move it undermetadata, not to retry. scopeandvisibilityare different axes, andscopeitself means two things. On a read,scope=agent|fleet|allchooses how wide to look. On a write,visibility=scope_agent|scope_team|scope_orgstamps who may see the row — it is not a read breadth and passing it as one filters rather than widens. Keystone routes takescopewith a third enum,tenant|fleet|agent, soscope=allthere is not a value. And a memory's ownscopefield in a response is none of the above: it carries validity qualifiers such as role or task. Four spellings, one word; check which surface you are on before copying a value between them.- Streaming client hangs on initialize. If hitting
/mcp(no slash) caused a hang on older builds, append the trailing slash or upgrade — current builds serve both paths without redirect. caura_insightsis cut off by your client's default tool timeout. It is LLM-backed and runs roughly 7–9 s — an order of magnitude slower than the other tools — while many MCP clients default to a 5–10 s tool timeout. There is no cheap/counts-only mode and no partial result, so a client that times out loses the whole call. Raise the timeout for this tool to ~30 s. See Tool latency below.
Tool latency
Set your MCP client's tool timeout from the slowest tool you actually call, not from an average.
| Tool | Observed | Notes |
|---|---|---|
caura_insights | 6.8 s and 8.7 s | LLM-backed synthesis. No depth/quick mode and no partial results — a timeout loses the entire call. |
caura_write (MCP) | 0.86 s | Inline enrichment on the default write mode. |
| REST calls, warmed | 0.7–1.4 s | Storage-routed; no LLM on the request path. |
A 30 s tool timeout covers all of these with margin. A 5–10 s default — common
in MCP clients — will truncate caura_insights specifically.
Two caveats on these numbers, so they are read for what they are. They come from two runs of the parity smoke against production on 2026-08-24, not from a sustained benchmark — treat them as an order of magnitude, not an SLA. And the first call of a session is unrepresentative: that run measured a 16 s first write, which later investigation did not explain (core-api holds a min-instance floor, so it was not a scale-from-zero cold start). Warm the connection before timing anything.
caura_recall with include_brief=true adds a second LLM round-trip on top of
the search; it is not in the table because this pass did not measure it
separately. Time it yourself before choosing a timeout if you rely on it.
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 (mirrorscaura_writeover MCP).mcp://…/mcp/— streamable-http MCP endpoint.