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.

Find Sandwich in the MCP Registry

Sandwich publishes two servers to the official MCP Registry under the com.joinsandwich namespace (DNS-verified on joinsandwich.com):

com.joinsandwich/care-circle
Authenticated MCP for a caregiver’s own care circle. Read loved ones and care events for the signed-in user. app.joinsandwich.com/api/mcp
com.joinsandwich/directory
Public, read-only MCP for the Sandwich aging-parent care directory and the U.S. cost-of-care calculator. No auth. www.joinsandwich.com/api/mcp

Browse all matching listings: registry.modelcontextprotocol.io — search com.joinsandwich. Any MCP client that reads the registry can install Sandwich by name.

Authentication — two paths

Sandwich supports two ways to authenticate external clients. Both reach the same tools and enforce the same per-user ownership; they differ in how the user grants access.

Path 1 — Bearer API key (for personal agents)

Best for Claude Desktop, Cursor, Codex CLI, and other clients you run yourself with a long-lived config file. Sign into app.joinsandwich.com, open Settings → MCP API keys, mint a key, copy it once (the plaintext is shown one time, then only its SHA-256 hash is kept), paste it into your client’s config. Keys look like sk-sand-<random>. Revoke any time from the same page; the next request after revocation is rejected.

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

Path 2 — OAuth 2.1 + PKCE (for hosted connectors)

Best for hosted assistants such as Claude.ai custom connectors and any third-party app that wants to onboard caregivers without asking them to copy-paste a secret. The client registers itself via Dynamic Client Registration, redirects the user to Sandwich’s consent screen, and receives a short-lived access token plus a rotating refresh token.

  • Standards: OAuth 2.1, PKCE S256, Dynamic Client Registration (RFC 7591), Token Revocation (RFC 7009), Authorization Server Metadata (RFC 8414), Protected Resource Metadata (RFC 9728).
  • Tokens: access token expires in 15 minutes; refresh tokens rotate on every exchange with replay detection.
  • Scopes the user approves: profile:read (name, email, workspace) and care_circle:read (list loved ones and read recent care events). Users approve scopes individually on the consent screen.
  • Revoke any time: app.joinsandwich.com/settings/connected-agents. Disconnecting kills both the standing consent and all refresh tokens for that client.

Discovery URLs an OAuth client uses automatically:

Authorization server metadata
/.well-known/oauth-authorization-server
Protected resource metadata
/.well-known/oauth-protected-resource
JWKS
/.well-known/jwks.json
Authorize / Token / Register / Revoke
/oauth/authorize · /oauth/token · /oauth/register · /oauth/revoke

In-app agents running on app.joinsandwich.com or soft.joinsandwich.com use the signed-in session automatically — no key, no OAuth round-trip. This page covers external clients.

Compatible clients

Claude Desktop
Works today via Bearer key (paste into claude_desktop_config.json) or, once the connector is approved, via the in-app connector flow.
Claude.ai (web) custom connectors
Add https://app.joinsandwich.com/api/mcp in Settings → Connectors → Add custom connector. Claude auto-detects OAuth, walks the consent screen, and connects. Requires a Claude Pro, Max, Team, or Enterprise plan.
Claude.ai featured connectors
Submission to Anthropic’s curated directory is in progress. Until it lists, the custom-connector flow above is identical functionally.
Cursor
Add an MCP server in Cursor settings with the same URL and a Bearer header.
Codex CLI
Add the URL and Bearer header to ~/.codex/config.toml — see the snippet at the bottom of this page.
Any MCP-compliant client
Streamable HTTP transport, JSON-RPC 2.0, MCP protocol version 2025-03-26. Both auth paths above are RFC-standard so any spec-compliant client should connect.

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 & scopes

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

Every tool re-derives ownership from the verified caller. Passing a lovedOneId that belongs to a different caregiver’s workspace returns a clean not authorized error. Bearer-key callers are not scope-gated today (the key grants full read access to the owning caregiver’s data). OAuth callers see only the scopes they approved on the consent screen.

What it can & can’t do

Today, the MCP can:

  • Identify the signed-in caregiver and tell an agent which workspace they belong to.
  • List the loved ones in that caregiver’s circle.
  • Return recent care events (appointments, medications, visits, notes) for any loved one in the circle.
  • Let an agent answer natural-language questions like “what’s on Mom’s timeline this week?” by composing those reads.

Today, the MCP cannot:

  • Write anything. v1 is intentionally read-only. Posting notes, scheduling, drafting emails, and adding loved ones are deferred to v2 so v1 has the simplest possible audit story.
  • Reach across caregivers. A caller only ever sees data tied to their own verified identity. There is no “impersonate another user” or admin endpoint.
  • Pull from a hospital portal directly. The MCP returns events that Sandwich Pipe has already ingested. If a partner integration isn’t live for a given provider, those records won’t appear here — see Sandwich Pipe and Family Inbox for how data gets in.
  • Open server-pushed streams. v1 is request/response only. Server-initiated streaming is on the roadmap.
  • Be used as a fan-out to other patients’ data. Even with the right loved-one ID, the workspace check rejects the call.

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.ai (web) — custom connector, OAuth

  1. In claude.ai, open Settings → Connectors → Add custom connector.
  2. Name: Sandwich. URL: https://app.joinsandwich.com/api/mcp.
  3. Click Add. Claude auto-detects OAuth, opens the Sandwich consent screen in a popup.
  4. Sign in to Sandwich if prompted, review the requested scopes, click Approve.
  5. Tools become available in your next chat — ask in plain English, Claude calls them automatically.

Requires a Claude Pro, Max, Team, or Enterprise plan. No copy-pasting secrets.

Claude Desktop — Bearer key

~/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