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.
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.
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: 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.
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.
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.
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).pong plus the verified caller's uid, tenant, and auth method. No side effects.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.
Retry-After and JSON-RPC error code -32029.
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.
Retry-After.error.message.{
"mcpServers": {
"sandwich": {
"url": "https://app.joinsandwich.com/api/mcp",
"headers": { "Authorization": "Bearer sk-sand-…" }
}
}
}
In Cursor settings, add a new MCP server with the same URL and Authorization header.
# 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":{}}}'
# 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