MCP Integration: Shomei Governed Memory
This page explains how to integrate Shomei Governed Memory as an MCP server into Claude Desktop, Cursor, IDEs, and agent frameworks. It covers the ten governed tools exposed, the governed boundary (governed verbs + content-free receipts), and a working setup example.
What is the MCP Server?
shomei_memory_mcp is a server-side wrapper that exposes only Shomei's governed verbs to MCP clients over stdio. It is its own top-level package—not embedded in the engine-bearing service package—so importing it pulls no engine code. The engine (crypto-erasure, KEK, keyed commitments, lineage), raw database access, and secret key material stay entirely behind the governed service's HTTP boundary.
This engine-free invariant is enforced mechanically: the package imports only stdlib (json, os), httpx, the optional MCP SDK (mcp.server.fastmcp.FastMCP), and Pydantic — plus, lazily and only inside verify_receipt, the standalone shomei_memory_verify package. A fresh-subprocess sys.modules test (tests/test_import_guard.py) fails the build if any engine module is ever pulled in transitively.
The MCP server's governed boundary:
MCP client (Claude Desktop / Cursor / agent)
↓ stdio (MCP protocol)
shomei_memory_mcp ← 10 governed tools, NO engine code
↓ HTTPS (bearer token)
Shomei governed service ← holds engine, KEK, commitments, lineage, DB
proof boundary and verification:
- proof boundary: Governance is achieved through governed verbs (no raw DB access), crypto-erasure on delete, and signed receipts. Deletion is cryptographic erasure of the per-row key; once that key is destroyed under production KMS custody the row is cryptographically unrecoverable, with strength bounded by your key custody, and receipts do not assert erasure of copies in other systems. Key custody is the proof boundary — see the Tier-2 section below.
- Verification: Most governed operations return a content-free, ed25519-signed receipt. Verify offline with the
verify_receipttool, which performs the crypto check locally with no network round-trip and needs no API key at all: it is the one tool that works withSHOMEI_MCP_API_KEYunset.
The Ten Tools
The server registers exactly ten tools. Each tool takes a single structured params argument (input models forbid unknown fields and strip surrounding whitespace on strings) and maps to a single HTTP verb on the governed service. Responses are JSON; errors are actionable and contain no secrets, internal state, or key material.
| # | Tool | Endpoint | Admin key required? |
|---|---|---|---|
| 1 | add_memory |
POST /v1/memories |
– |
| 2 | search_memory |
POST /v1/search |
– |
| 3 | update_memory |
PUT /v1/memories/{id} |
✓ |
| 4 | delete_memory |
DELETE /v1/memories/{id} |
✓ |
| 5 | forget_subject |
POST /v1/forget |
✓ |
| 6 | restrict_memory |
POST/DELETE /v1/memories/{id}/restrict |
✓ |
| 7 | get_receipt |
GET /v1/memories/{id}/verify |
– |
| 8 | lineage_neighbors |
GET /v1/memories/{id}/lineage/neighbors |
– |
| 9 | lineage_graph |
GET /v1/memories/{id}/lineage/graph |
– |
| 10 | verify_receipt |
local crypto (no HTTP) | – |
Admin scope.
update_memory,delete_memory,forget_subject, andrestrict_memoryare control-plane operations and require an admin-scoped bearer key. IfSHOMEI_MCP_API_KEYis a data-plane key, these four tools return403("this action requires an admin-scoped key"); the read/search tools work with any valid key.
add_memory
Endpoint: POST /v1/memories
Store a new governed memory. Retention is explicit (delete or forget); the service applies governance gates.
Input:
messages(required, string, min 1 char) – The memory text or conversation to store.user_id(optional, string) – Subject the memory is about; enables scoped recall and erasure.semantic_type(optional, string, default: "GENERIC", max 64 chars) – Governed data category for policy labels and export.
Output:
Returns a JSON object with a results array. Each result includes:
id– The opaque logical memory id (e.g.,lm_...).memory– The stored text, echoed back.event–"ADD".receipt/receipt_hash– The content-free receipt body and its hash.signed_receipt– The signed receipt envelope (present when a receipt signer is configured); pass toverify_receiptto check offline.
Example:
{
"results": [
{
"id": "lm_abc123",
"memory": "User prefers email notifications",
"event": "ADD",
"receipt": { },
"receipt_hash": "sha256:...",
"signed_receipt": {
"schema": "shomei.memory_add.v1",
"receipt_hash": "sha256:...",
"signer_id": "shomei-receipt:...",
"public_key_hex": "...",
"signature_hex": "...",
"body": { }
}
}
]
}
search_memory
Endpoint: POST /v1/search
Semantic search over recallable memories. Restricted and consent-invalid memories are never returned (enforced server-side).
Input:
query(required, string, min 1 char) – Semantic search query.user_id(optional, string) – Restrict search to one subject's scope.limit(optional, integer, default: 10, range: 1–100) – Maximum results to return.
The MCP layer does not expose a min_score parameter.
Output:
Returns a JSON object with a results array; each result carries at least id, memory (the retrieved text), user_id, and score. Treat results as the stable envelope.
Example:
{
"results": [
{
"id": "lm_abc123",
"memory": "User prefers email notifications",
"user_id": "alice",
"score": 0.92
}
]
}
update_memory
Endpoint: PUT /v1/memories/{id} (admin)
Governed update via revoke-and-recreate: the prior content is crypto-erased, and a successor is created carrying governance forward. Refused on held, restricted, or consent-invalid memories.
Input:
memory_id(required, string, min 1 char) – Opaque id of the memory to update.data(required, string, min 1 char) – New memory text.
Status codes: legal hold → 423; restriction or withdrawn-consent → 409 (the two are deliberately indistinguishable); unknown or already-erased → 404.
Output:
Returns a JSON object with a results array. Each result includes id (the new successor id), previous_id (the crypto-erased prior id), event: "UPDATE", and a signed_receipt; verify offline with verify_receipt.
Example:
{
"results": [
{
"id": "lm_xyz789",
"previous_id": "lm_abc123",
"event": "UPDATE",
"signed_receipt": { }
}
]
}
delete_memory
Endpoint: DELETE /v1/memories/{id} (admin)
Crypto-erase one memory (governed crypto-erasure: the per-row key is destroyed; recovery is bounded by your key custody). Returns a signed deletion receipt. A legal hold blocks this operation.
Input:
memory_id(required, string, min 1 char) – Opaque memory id to delete.
Status codes: 200 erased; 423 blocked by a legal hold; 404 unknown or already-erased.
Output:
On success, a JSON object with message, deleted: true, receipt/receipt_hash, key_custody, retirement_hashes, and signed_receipt (when signing is configured). A hold returns deleted: false, held: true; a no-op/not-found returns deleted: false.
Example:
{
"message": "memory deleted (governed crypto-erasure)",
"deleted": true,
"key_custody": "...",
"retirement_hashes": ["..."],
"signed_receipt": { }
}
forget_subject
Endpoint: POST /v1/forget (admin)
GDPR Article 17 subject erasure across the subject's scoped memory set. Server-side operation. Returns a per-subject closure receipt plus per-memory receipts. Memories under a legal hold are not erased now — they are restricted (Art. 18) and carry a durable obligation to auto-erase when the hold is released.
Input:
user_id(required, string, min 1 char) – Subject to erase across their scoped memory set.
Output:
A JSON object whose top-level subject key is subject, with counts including erased_count and deferred_count, plus per-kind count/list fields and a note. Per-memory proofs live under signed_receipts (a list). When a signer and commitment key are configured, the subject-level closure block adds closure_receipt, closure_receipt_hash, and signed_closure_receipt (the signed envelope — note the signed_ prefix). Verify any of these offline with verify_receipt.
Example:
{
"subject": "alice",
"erased_count": 5,
"deferred_count": 0,
"signed_receipts": [ { "id": "lm_abc123", "signed_receipt": { } } ],
"closure_receipt": { },
"closure_receipt_hash": "sha256:...",
"signed_closure_receipt": {
"schema": "shomei.subject_closure.v1",
"receipt_hash": "sha256:...",
"signer_id": "shomei-receipt:...",
"public_key_hex": "...",
"signature_hex": "...",
"body": { }
},
"note": "..."
}
restrict_memory
Endpoint: POST /v1/memories/{id}/restrict (restrict) or DELETE /v1/memories/{id}/restrict (un-restrict) (admin)
GDPR Article 18 restriction: take a memory out of recall (preserved, reversible) or restore it. Restricted memories will not be returned by search; they are preserved for audit/legal holds.
Input:
memory_id(required, string, min 1 char) – Opaque memory id.restricted(optional, boolean, default: true) –trueto restrict (take out of recall);falseto un-restrict (restore).reason(optional, string, default: "art18_request", max 64 chars) – Controlled-vocabulary restriction reason code, applied when restricting.
On un-restrict (
restricted=false), the service ignores the caller'sreasonand records the fixed reason codeart18_lifted.
Output:
A JSON object with id, restricted (boolean or null), and changed (boolean), plus receipt/receipt_hash/signed_receipt when the state changed and signing is configured.
Example:
{
"id": "lm_abc123",
"restricted": true,
"changed": true,
"signed_receipt": { }
}
get_receipt
Endpoint: GET /v1/memories/{id}/verify
Fetch a memory's current governed verification status (not a signed receipt). Tells you whether a memory is valid, retrieval-safe, and physically purged.
Input:
memory_id(required, string, min 1 char) – Opaque memory id.
Output:
A JSON object with valid, retrieval_safe, embedding_retired, physically_purged, and failures (a list). For a hidden or absent memory it returns {"valid": null, "reason": "not_found"}.
Example:
{
"valid": true,
"retrieval_safe": true,
"embedding_retired": false,
"physically_purged": false,
"failures": []
}
Note: This returns a status, not a signed receipt. To verify a receipt offline, pass a signed_receipt from an add/update/delete/forget/restrict response to verify_receipt.
lineage_neighbors
Endpoint: GET /v1/memories/{id}/lineage/neighbors?direction=&limit=
Return the content-free provenance neighbors of a memory (the immediate sources/derivations recorded in its lineage). All identifiers are content-free.
Input:
memory_id(required, string, min 1 char) – Opaque memory id.direction(optional, string, default: "out", one ofout/in/both) – Traversal direction; validated client- and server-side.limit(optional, integer, default: 50, range: 0–100) – Maximum neighbors to return.
Output:
Content-free provenance neighbors JSON. If lineage is unsupported for this deployment, the service returns 501.
lineage_graph
Endpoint: GET /v1/memories/{id}/lineage/graph?direction=&limit=
Return the content-free immediate provenance graph (nodes + edges) around a memory.
Input: the same fields as lineage_neighbors (memory_id, direction, limit with the same defaults and ranges).
Output:
Content-free nodes and edges JSON. If lineage is unsupported, the service returns 501.
verify_receipt
Fully offline: no service call, no API key.
Verify a signed receipt using the engine-free standalone verifier (shomei_memory_verify). The cryptographic check is local with no network round-trip and no key material on the client, and the tool does not touch the service client at all: it works with SHOMEI_MCP_API_KEY unset, so a receipt can be verified from a machine that has no Shomei credentials.
Input:
receipt(required, object) – Asigned_receiptobject from an add/update/delete/forget/restrict response.expected_public_key_hex(optional, string) – Pin the signer's public key (obtained out-of-band) for an authenticated result. Without this, a valid receipt is returned asauthenticated=false, proving only self-consistency, not authorship.
The receipt is size- and depth-capped before verification: at most 64 KiB and 64 levels of nesting. Oversized or over-nested input is rejected up front.
Output: A JSON object with:
valid– Boolean; the receipt is well-formed and the ed25519 signature is consistent over the body, withreceipt_hashmatching the canonical body hash.authenticated– Boolean; the signature was additionally verified against the caller-pinned public key. (valid=true, authenticated=falsemeans self-consistent but unverified authorship.)reason– String status code:'ok','ok_unpinned','public_key_mismatch','signature_or_hash_invalid', or'malformed: <detail>'.public_key_hex– The key the signature verifies under (the authenticated signer identity). Trustworthy only whenauthenticated=true.schema– The signed schema from the receipt body (e.g.,'shomei.memory_add.v1').
Example:
{
"valid": true,
"authenticated": true,
"reason": "ok",
"public_key_hex": "abcd1234...",
"schema": "shomei.memory_add.v1"
}
Setup: Install and Run
Installation
pip install 'shomei-governed-memory[mcp]'
This pulls the mcp and httpx optional extras. The shomei_memory_verify offline verifier ships in the base package, so it is available regardless of the [mcp] extra.
Configuration (Environment Variables)
| Variable | Required | Default | Description |
|---|---|---|---|
SHOMEI_MCP_API_KEY |
✓ | – | Bearer token for the governed service. Maps to a single tenant; the server refuses to start without it. An admin-scoped key is needed for the four governed-write tools. |
SHOMEI_MCP_BASE_URL |
– | http://127.0.0.1:8088 |
The governed service base URL. Always use https:// for non-loopback hosts; the bearer token is sent on every request. |
Running the Server
export SHOMEI_MCP_BASE_URL="https://memory.your-vpc.internal"
export SHOMEI_MCP_API_KEY="shm_..."
python -m shomei_memory_mcp.server
The server runs on stdio (standard input/output), which is what MCP clients expect.
Claude Desktop Integration
Add Shomei to your Claude Desktop config:
// ~/.config/Claude/claude_desktop_config.json (macOS/Linux)
// or %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
"mcpServers": {
"shomei-memory": {
"command": "python",
"args": ["-m", "shomei_memory_mcp.server"],
"env": {
"SHOMEI_MCP_BASE_URL": "https://memory.your-vpc.internal",
"SHOMEI_MCP_API_KEY": "shm_..."
}
}
}
}
Restart Claude Desktop. The Shomei tools are now available to Claude in every conversation.
Other IDEs and Agent Frameworks
The server runs on stdio, so any MCP client can use it:
- Cursor: Copy the same config to Cursor's
~/.cursor/mcp_config.json(or equivalent). - Agent Frameworks (e.g., LangChain, CrewAI): Spawn the process as a subprocess stdio MCP server and connect it to the framework's MCP transport layer.
Security & Governance Boundaries
Tenant Isolation
Enforced by the governed service against the bearer token. The MCP server never selects a tenant itself; authentication and tenant isolation are the service's responsibility.
Transport
- The bearer token rides every request as an
Authorization: Bearerheader. - For any remote host, use
https://; the loopback default (http://127.0.0.1:8088) is for local/dev only.
Error Messages
Only the governed, content-free service message is surfaced (the service error detail is capped at 200 characters). Never tracebacks, internal state, key material, or the bearer token.
The MCP relay also runs a fail-closed name-screen over relayed data as defense-in-depth. This is not the authoritative no-secret boundary — that is the governed service's content-free egress validation, which runs before anything leaves the service host.
Receipts
- Signed receipts are content-free: verifying one (locally or via
get_receipt) never exposes raw memory content, commitments, or keys. - Receipts prove authorship (via ed25519) of a content-free statement. They do not prove that copies or backups held elsewhere were erased.
- Verify offline with
verify_receiptusing only the public key (obtained out-of-band and pinned inexpected_public_key_hex).
No Engine on the Client
This package imports no engine code (the engine packages, crypto internals, KEK providers, key custody). The engine, commitment keys, and raw database stay entirely on the governed service host. The MCP server is a thin relay plus a local offline verifier.
Example: Add, Search, and Verify
# This is pseudocode; actual Claude agent code would use the MCP tool calls.
# Add a memory
result = add_memory(
messages="Alice's timezone is UTC+2",
user_id="alice"
)
memory_id = result["results"][0]["id"]
signed_receipt = result["results"][0]["signed_receipt"]
# Search
search_result = search_memory(query="timezone", user_id="alice", limit=5)
print(search_result["results"][0]["memory"])
# Verify the add receipt offline (no service call, but SHOMEI_MCP_API_KEY must be set)
pin = "abcd1234..." # obtained out-of-band and pinned
verify_result = verify_receipt(
receipt=signed_receipt,
expected_public_key_hex=pin
)
assert verify_result["valid"] and verify_result["authenticated"]
# Delete (requires an admin-scoped key) and verify
delete_result = delete_memory(memory_id)
delete_receipt = delete_result["signed_receipt"]
verify_result = verify_receipt(receipt=delete_receipt, expected_public_key_hex=pin)
assert verify_result["authenticated"]
print(f"Deletion verified: {verify_result}")
A note on Article 15 export
Subject-access export (GDPR Art. 15), including the just-landed disclosure of a subject's derived-artifact set (content-free opaque artifact:<hex> ids, each with a recallable flag, bound into a signed witness), is an HTTP-only capability (GET /v1/export). It is not exposed as an MCP tool. See the HTTP API and governance-and-receipts pages for details.
Tier 2 Verification (Current)
Shomei is Tier 2 today:
- Receipts are operator-side / key-holder-verifiable using public-key crypto offline.
- No TEE or independent attestation. Key custody is the proof boundary: a receipt is only as trustworthy as control of the signing key.
- Crypto-erasure strength depends on your key custody. Production requires a real KMS/BYOK provider; per-row DEK envelope encryption is used at rest (this is not SQLCipher page-encryption). Dev custody is dev-only.
Tier 3 (roadmap, not shipped): measured-enclave attestation, enabling independent verification with a pinned signer key.
Feedback & Limitations
- No implicit schemas: Every memory and operation is explicitly governed; there is no "default" or implicit retention policy.
- Receipts prove authorship, not external erasure: A signed receipt proves authorship (via ed25519) of a content-free statement; it does not prove a third-party time-ordered ledger, nor that copies held in other systems were erased. Tier 3 (TEE attestation) is the roadmap for operator-independent verification.
- Memory IDs are opaque: They are percent-encoded into the URL path (e.g.,
DELETE /v1/memories/lm_abc%2Fdeffor idlm_abc/def). Never trust a path-encoded id to be a different memory.