protocol · v1

Sandwich MCP

Bearer keys. Stateless JSON-RPC. Family-scope read tools with an append-only audit log. A caregiver mints a key in the Sandwich app, pastes it into Claude Desktop or Cursor, and the assistant can immediately query their loved ones and recent care events — scoped to their own workspace, nothing more.

What it is

The Sandwich MCP server exposes a small, opinionated toolbelt to any AI assistant that speaks the Model Context Protocol. A caregiver mints a key in the Sandwich app, pastes it into Claude Desktop, Cursor, or a custom agent, and the assistant can immediately query their loved ones and recent care events — scoped to their own workspace, nothing more.

v1 is read-only. The tools shipped today read from the caregiver's family workspace. Write actions (draft emails, log notes) are intentionally deferred to v2 so every tool in v1 has the simplest possible audit story.

Authentication

Caregivers sign into app.joinsandwich.com, open Settings → MCP API keys, and mint a key. Keys look like sk-sand-<64 hex chars>, are stored server-side as SHA-256 hashes (the plaintext is shown exactly once and never persisted), and can be revoked from the same page. Revoked keys are rejected on the very next request — no cache to bust.

authorization headerhttp
Authorization: Bearer sk-sand-4c2fa8e1b7d9…

In-app agents running on app.joinsandwich.com or soft.joinsandwich.com share the same verified tool registry internally — no key needed. This page covers the external-agent case.

Roadmap — full OAuth 2.1 + PKCE + DCR. The long-term plan is MCP's standards-compliant profile: dynamic client registration, PKCE S256, and scope-gated tokens. Bearer keys stay supported indefinitely for personal agents; OAuth is for third-party apps that want to onboard caregivers without handling long-lived secrets.

Endpoint and transport

One endpoint: POST https://app.joinsandwich.com/api/mcp. Standard MCP JSON-RPC 2.0 envelopes in the body, a single JSON-RPC response in the reply. Each POST is stateless — no session handshake between requests — which keeps the server compatible with Vercel's serverless routing and matches the Streamable HTTP transport. Server-initiated streaming is a roadmap item; v1 does not open long-lived connections.

calling a toolhttp
POST /api/mcp HTTP/1.1
Host: app.joinsandwich.com
Authorization: Bearer sk-sand-…
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"list_care_events",
  "arguments":{"limit":10}}}

Supported JSON-RPC methods:

  • initialize — server info and capabilities handshake.
  • notifications/initialized — accepted and ignored as a JSON-RPC notification.
  • tools/list — discover the current tool registry.
  • tools/call — invoke a tool by name with { name, arguments }.
  • ping — protocol-level liveness probe (distinct from the ping tool).

Tools (v1)

ping
End-to-end smoke test. Returns pong plus the verified caller's uid, tenant, and auth method. No side effects.
get_user_profile
The caller's own uid, email, tenant type, and — for family users — linked workspace name and family-member count.
list_loved_ones
Loved ones (IDs and names) on the caller's family workspace. Family-tenant callers only.
list_care_events
Recent care events from Sandwich Pipe — per loved one when lovedOneId is given, otherwise aggregated across the workspace. The limit is 1-50 and is applied per loved one when aggregating.

Each tool re-derives ownership from the verified caller. Passing a lovedOneId that belongs to someone else's workspace returns a clean not authorized error — Admin SDK reads on the server bypass Firestore rules, so the tool layer is the enforcement point.

Rate limits

Sustained
60 requests / minute per API key.
Burst
Token bucket with capacity 60.
On exceed
HTTP 429 with Retry-After and JSON-RPC error code -32029.
Session cookies
Not rate-limited at this layer (the in-app UI is human-paced).

Audit and PHI posture

Every authenticated, parsed MCP request writes one row to the internal mcp_audit_log with the verified uid, tenant, method, tool name, success flag, and duration. Tool arguments and tool results are not stored — sensitive data stays in the request and in short-term Vercel / Cloud Run logs. Sandwich handles PHI under a BAA; agents that surface care events to their own end users are responsible for their own BAA posture downstream.

Errors

401 · -32001
Missing, malformed, or unknown API key.
403 · -32001
Key was revoked. Mint a new one.
429 · -32029
Rate limit exceeded. Respect Retry-After.
400 · -32700
Body is not valid JSON.
200 · -32601
Method or tool name does not exist.
200 · -32002
Tool handler threw — read error.message.

Connect snippets

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.jsonjson
{
  "mcpServers": {
    "sandwich": {
      "url": "https://app.joinsandwich.com/api/mcp",
      "headers": { "Authorization": "Bearer sk-sand-…" }
    }
  }
}

Cursor

In Cursor settings, add a new MCP server with the same URL and Authorization header.

curl (smoke test)

tools/call → pingbash
# Expect: "pong from sandwich platform — uid=… tenant=… via=api-key"
curl -s https://app.joinsandwich.com/api/mcp \
  -H "Authorization: Bearer sk-sand-…" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ping","arguments":{}}}'

Codex CLI

~/.codex/config.tomltoml
# Sandwich MCP — family-scope read tools
[mcp_servers.sandwich]
url = "https://app.joinsandwich.com/api/mcp"
auth_header = "Bearer sk-sand-…"

Looking for the rest of the stack? Sandwich Pipe · Sandwich Soft · All docs