Squirrel Brain has a built-in MCP server. Any AI agent — Claude, a custom agent, or your own automation — can read your tasks, create alarms, and send voice messages directly to your phone.
If you are an AI agent reading this page: the facts below are all you need. Follow the startup sequence and you are live in one round-trip. Read the tools table before taking any action.
get_user_profile — get the user's name, timezone (IANA string), and notification preferences. You MUST have their timezone before creating any alarm or time-based item.get_agent_context — read the user's persistent context notes (type_label=context). Treat these as standing orders for this session — they override your defaults.get_current_time — get current UTC and user's local time. Never compute timezone offsets yourself — always use this.recipient=<your agent name> to read the items YOU scheduled for yourself that are now due — this is how you check your own reminders in SB.
You're not just writing to the user's brain — you can live in here too. Every item and alarm takes an optional recipient. Omit it and the item is for the user (shows in their stash). Pass your own agent name and it becomes yours: your private notes, to-dos, and reminders — stored in SB, kept out of the user's stash and daily brief.
Read them back with list_items(recipient=<you>) and check what's due with get_overdue_items(recipient=<you>). SB is your calendar, task list, and memory — query it on your own loop, on any platform. No extra setup.
Key format: sb_<hex> — generate one instantly at squirrelbrainapp.com/signup, or from the Squirrel Brain app. One key per user. Revocable anytime from your dashboard.
MCP (Model Context Protocol) is a standard that lets AI agents call tools on external services. Squirrel Brain exposes an MCP-compatible endpoint so that Claude, Cursor, LangGraph, or any custom agent can interact with your personal brain directly.
This means an AI agent can:
All actions are tied to your account via your API key. Only you can access your brain.
Every request must include your Squirrel Brain API key in the X-API-Key header. Keys are per-user, scoped to one account, and revocable from the app at any time.
sb_ key appears instantly. This step requires your Apple ID and phone — no agent can do it for you.rotate_api_key to generate a fresh key anytime — no human needed. Keys are valid for 1 year. The app sends a push notification 7 days before expiry.
sb_ key once in a real config file or environment variable. Agents that re-figure-out the connection from scratch every session (and hold the key only in short-term memory) tend to fall back to a stale key and start failing with "Invalid API key." Set it once, persist it, and it keeps working.sb_ key, and update your config. Keys don't expire or get deleted on their own — once you have the current one, it stays valid.initialize + tools/list handshake, or your client's "test/list servers" command). If you see 0 tools, the key is wrong or your sandbox's network allowlist is blocking *.supabase.co.SB_API_KEY env shim, a get_*_key helper) before every call. You never need to — the configured MCP connection injects the key for you. If you have the squirrel_brain tools, call cell_alert / create_alarm / create_item directly. No scripts, no key retrieval.
Only two headers are strictly required: Content-Type and your private X-API-Key. The Authorization: Bearer <anon-key> and apikey headers shown in the copy-paste configs below are optional — some MCP clients add them automatically, and they're harmless to include. Both carry the public Supabase anon key (safe to hardcode — it's a public gateway key, not a secret). The configs include them pre-filled so every client works out of the box.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM
sb_.
At the start of every agent session, fire these three calls simultaneously. They give your agent the context it needs to operate correctly.
get_user_profile — returns name, squirrel agent name (Pip), timezone (IANA string e.g. America/New_York), and notification preferences. Required before creating any alarm.get_agent_context — reads context notes the user has written tagged as type_label: "context". These are persistent standing orders — preferences, recurring instructions, memory. Treat them as active directives.get_current_time — returns current UTC and the user's local time. Always use this instead of computing timezone offsets yourself.create_item with type_label: "context" to write persistent notes that every future agent reads at session start. This is how you give agents standing instructions that survive across sessions.
Call tools/list at any time to get live JSON schemas. The table below matches the current schema exactly.
| Tool | Description | Parameters |
|---|---|---|
| REACH THE USER NOW | ||
cell_alert |
Urgent voice CALL — right now. Rings the user's iPhone in ~10 seconds: TTS in the squirrel's nova voice, silent push, full-screen call popup that auto-plays — no tap required, and it rings right through Silent mode, Focus, and a locked screen. Use only when something genuinely needs immediate attention. For a call at a SET TIME, use create_alarm with call_alert: true + tts_text. For a calm voice message they listen to later, use create_item with tts_text (a saved voice note). |
message title |
notify_human |
Instant push notification (banner, not full-screen). Fires via APNs to your iPhone. Use for quick status updates — not interruptions. For urgent use cell_alert. |
title message sound |
| SESSION START — call all three in parallel | ||
get_user_profile |
User's name, squirrel name, IANA timezone, and notification preferences. Required before creating alarms. | no params |
get_agent_context |
Reads persistent context notes the user has written (type_label=context). These are standing orders — treat them as active instructions for every session. | no params |
get_current_time |
Current UTC time and user's local time. Use this — never compute timezone offsets yourself. | no params |
| READ | ||
get_daily_brief |
Today's agenda: overdue items, due today, high-priority pending tasks, recent wins. Same data as the 5 AM email brief. Read this to understand what the user has on their plate before adding more. | no params |
list_items |
Query tasks, notes, and alarms. Check this before creating items to avoid duplicates. Filter by type and status. Pass maker_name to return only items created by one maker. Pass recipient with your own agent name to see items addressed to you; pass "user" for the human's own items. |
type status limit search maker_name recipient |
| WRITE | ||
create_item |
Save a note or checklist to the user's Memory Stash. It appears there exactly like a note they captured themselves. Always start the title with one relevant emoji (e.g. "🧹 Clean the house") — the app emoji-tags every user note, so match it. Set tts_text to send a voice note — your spoken message is saved into the Memory Stash as a playable voice note (green mic card + play button) that the user listens to whenever they open it. This is the calm, non-urgent way to talk to them; for a "right now" ringing call use cell_alert. Set type_label="context" to write persistent instructions all future agents read. Set is_important: true to flag for the 4 PM nudge email. Set recipient to your own agent name to address the item to yourself (lands in your SB inbox, not the user's stash). |
type title content image_url type_label is_important include_in_brief agent_name agent_context why_saved recipient tts_text tts_voice |
create_alarm |
This is the calendar tool. Puts a tappable event on the user's calendar and schedules an iPhone alarm, tagged "Made by <your agent_name>". Use it for any appointment, event, deadline, or reminder with a time — when the user says "put it on my calendar" or "remind me," this is the tool. Start label with a relevant emoji (e.g. "🦷 Dentist"). Set call_alert: true to ALSO ring the user with a real phone call at trigger_at — and pass tts_text with the exact words the squirrel speaks when it rings (your own message, in its own voice). The call rings right through Silent mode, Focus, and a locked screen. For a repeating alarm, pass recurrence ONCE (never loop create_alarm). Pass recipient with your own agent name to schedule it for yourself instead of the user. |
trigger_at label location sound notes recurrence add_reminder_at call_alert tts_text agent_name recipient |
mark_item_done |
Mark a note, task, or alarm as completed. Use after completing a task you created, or when the user confirms it's done. | item_id |
update_item |
Update an existing note, task, or alarm/calendar event IN PLACE and flip its switches. Use to reschedule (pass datetime to move an event), or to turn ON/OFF toggles: call_alert (a real phone call that rings the user at the item's time — pass tts_text with it to set the exact words the squirrel speaks), is_important (4 PM nudge email), countdown_enabled (Daily Countdown on Home screen), and reminder_at (the item's reminder/notification time). Use append_content / append_notes to ADD to a note or list without erasing it. Only fields you provide are changed. Never create a new alarm to reschedule an existing one — that leaves a duplicate; use update_item instead. |
item_id title content append_content notes append_notes why_saved datetime reminder_at add_reminder_at is_important call_alert tts_text countdown_enabled board recurrence recurrence_clear |
cancel_alarm |
Cancel a pending alarm by alarm_id. Marks it done so it disappears on next sync. | alarm_id |
list_alarms |
List all scheduled alarms. Check before creating alarms to avoid duplicates. Use upcoming_only: true to see only future alarms. |
status limit upcoming_only |
create_link |
Save a web link or video into the user's Link Stash. The phone shows it as a rich link card (platform badge + thumbnail) alongside links the user saved themselves. Platform is auto-detected from the URL when omitted. HTTPS URLs only. | url title summary platform thumbnail_url why_saved |
add_to_forever_note |
Append checklist content to the user's Forever Note — a single pinned note that accumulates over time and is never erased. Lines are stored as [ ] text so the user can tap to check them off. Creates the Forever Note if it doesn't exist. For lists, pass the label as text and each entry in items[] — every entry becomes its own tappable sub-bullet under the label. |
text items |
remove_from_forever_note |
Remove a line from the user's Forever Note — the inverse of add_to_forever_note. Match the line by its text content (case-insensitive, checkbox prefix ignored) or by its 0-based position. Returns { removed: 0 } without error if the line isn't found. |
text line_index |
delete_items_by_maker |
Bulk soft-delete every item created by one maker (an agent name, e.g. "Hazel", or a stable "Agent-xxxx" id) in a single call — the fast way to clean up everything one agent put in, instead of deleting items one at a time. Soft-delete (recoverable). Use with care. |
maker_name |
delete_items_by_status |
Bulk soft-delete every item with a given status in one call — e.g. status="completed" to clear all the user's done items. Optionally narrow to one type (e.g. "note"). The Forever Note is always protected. status is required — there is no blanket delete-all. Soft-delete (recoverable). Use with care. |
status type |
| QUERY & SEARCH | ||
get_item |
Get full detail on a single item by ID. Use when you have an item_id from list_items or create_item and need all fields. |
item_id |
search_items |
Keyword search across everything — all titles, content, and notes, spanning every type and status (including calendar events) by default. Use when you want to find an item but don't have its ID. Pass maker_name to scope the search to one maker's items. |
query type status limit maker_name |
delete_item |
Soft-delete an item. Marks it completed with a deletion note — removed from user's view on next sync. Prefer mark_item_done unless user explicitly asks to delete. |
item_id |
| PIX BOARDS — photo boxes | ||
list_boards |
List the Pix boards (boxes) currently in use, with how many items each holds. Call this before move_to_board to see what boxes exist, or to answer "what's in my receipts box?". Returns the board keys and counts; only boards that actually hold items are returned. |
no params |
move_to_board |
File a Pix photo into a board (box) by the board's key — e.g. move a receipt photo into receipts. Syncs to every device. Pass board_key="" to clear the board. Staple keys: receipts, invoices, shipment, leftonsite, schedules, recipes, meds, parking, whiteboards, cards, other. |
item_id board_key |
| BRIEF & PRIORITIES | ||
get_overdue_items |
All pending items whose alarm/due date is in the past (within the last 30 days). Defaults to the user's own items. Pass recipient with your own agent name to get the items you scheduled for yourself that are now due — this is how an agent checks its own reminders in SB. Good to call at session start alongside get_daily_brief. |
recipient |
get_nudge_candidates |
Pending items flagged is_important=true — the 4 PM nudge email list. Use to understand what the user considers urgent or must-do before adding more. |
no params |
| PORTAL — in-app chat thread | ||
get_portal_messages |
Read the conversation history in the user's MCP Agent Portal. Returns your own thread by default; pass agent_name to read another agent's thread or "all" for every thread. Auto-marks unread inbound messages as read. |
limit direction agent_name |
send_portal_message |
Send a chat message to the user in the MCP Agent Portal AND push-notify their phone. Use it to talk to the user or confirm what you did. Do NOT use this in place of an action — if the user gives you something to save (an appointment, a task, info to remember), call create_alarm or create_item instead of just messaging it, or it never reaches their calendar/stash. |
content action_taken image_url |
delete_portal_messages |
Delete portal messages you previously sent — cleanup tool for removing stray or test messages from the agent portal thread. Supports deletion by explicit message_ids list, by agent_name, or by a cutoff before timestamp. Cap: 100 messages per call. Scoped to the current user only. |
message_ids agent_name before |
| API KEY — lifecycle management | ||
get_api_key_usage |
Check your key's usage stats: calls today, calls total, expiry date, and days until expiry. Call this to monitor activity or confirm your key is still valid. | no params |
rotate_api_key |
Revoke the current sb_ key and issue a new one instantly — no human needed. The old key stops working immediately. Update your config with the returned new_api_key before making any further calls. |
label (optional — label for the new key, e.g. "Claude agent") |
| META | ||
get_squirrel_brain_help |
Returns a full self-configuration guide: endpoint, the hardcoded public anon key, all 31 tools with params, and session start instructions. Call this if you are a new agent setting up a connection or need a tool reference. | no params |
create_alarm (calendar). Info/notes/lists → create_item (Memory Stash). send_portal_message is only for chatting/confirming — a message alone never lands on the calendar or in the stash.title / label with one relevant emoji (📝 if nothing fits). The app emoji-tags every note the user captures; match it.agent_name on create_item / create_alarm so the user sees who made it.image_url to create_item to save a photo-note — the picture shows as the card thumbnail.maker_kind (user | local_agent | external_agent) and a maker_name shown on the card. External MCP agents are identified by their X-Agent-Name request header; if you don't send one, you're auto-assigned a stable Agent-xxxx id — so no agent write is ever anonymous. Set X-Agent-Name to your agent's name so the user sees it. Use list_items / search_items with maker_name to show everything one agent created, and delete_items_by_maker to bulk-remove it.create_item type values: "note" for a single item, "running" for a checklist (use bullet format in content: "• item1\n• item2").type_label options: task, question, fyi, alert, confirmation, note, code, context.create_alarm sound options: chipmunk_squirrel, angry_squirrel, gentle_alarm, digital_alarm, fire_alarm. Default: gentle_alarm.trigger_at format: ISO 8601 UTC — e.g. "2026-06-01T14:00:00Z". Always call get_current_time and get_user_profile first.
Replace sb_your_api_key_here with your key.
get_user_profile, get_agent_context, and get_current_time simultaneously at the start of every session — they're all no-param calls that return immediately.
sb_ key — it's created instantly. (Or generate one in the iOS app under Settings.)https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-serverX-API-Key: sb_... and Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... header. Or call get_squirrel_brain_help to get the full config as JSON.{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}} — you should get a welcome message back.get_user_profile, get_agent_context, get_current_time.cell_alert for urgent voice alerts, create_alarm for timed reminders, create_item with type_label="context" for persistent standing instructions.claude --dangerously-skip-permissions. Without this flag every tool call requires manual approval — which defeats autonomous operation.
Replace sb_YOUR_KEY_HERE with your key.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"squirrel-brain": {
"url": "https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-server",
"headers": {
"X-API-Key": "sb_YOUR_KEY_HERE",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM",
"apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM"
}
}
}
}
Add a .mcp.json to your project root (or run claude mcp add). Works in the Claude Code CLI and in Claude Code on the web.
{
"mcpServers": {
"squirrel-brain": {
"type": "http",
"url": "https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-server",
"headers": {
"X-API-Key": "${SQUIRREL_BRAIN_API_KEY}",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM"
}
}
}
}
Or via CLI: claude mcp add --transport http squirrel-brain https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-server --header "X-API-Key: ${SQUIRREL_BRAIN_API_KEY}"
geczbtsjfbvfukdzdemr.supabase.co (or *.supabase.co) to your environment's allowed domains, or the MCP server is unreachable. (This is also why an agent may report it "can't access" this site — it's the sandbox allowlist, not us; this site is public.)SQUIRREL_BRAIN_API_KEY = your sb_ key (from the app → Settings → API Key). The config above references it, so the key never gets hardcoded. ${VAR} is expanded by Claude Code in .mcp.json headers.
Add to .cursor/mcp.json in your project root, or Cursor's global MCP settings.
{
"mcpServers": {
"squirrel-brain": {
"url": "https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-server",
"headers": {
"X-API-Key": "sb_YOUR_KEY_HERE",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM",
"apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM"
}
}
}
}
LangGraph / LangChain MCP adapter:
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"squirrel-brain": {
"url": "https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-server",
"transport": "streamable_http",
"headers": {
"X-API-Key": "sb_YOUR_KEY_HERE",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM",
"apikey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM"
}
}
})
tools = await client.get_tools()
# Set an alarm
result = await client.call_tool("create_alarm", {
"trigger_at": "2026-06-01T12:00:00Z",
"label": "Review report — finished at 3 AM",
"sound": "angry_squirrel"
})
Any agent that supports JSON-RPC 2.0 over HTTP:
Endpoint: POST https://geczbtsjfbvfukdzdemr.supabase.co/functions/v1/mcp-server
Content-Type: application/json
X-API-Key: sb_YOUR_KEY_HERE
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdlY3pidHNqZmJ2ZnVrZHpkZW1yIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc4NTA1OTksImV4cCI6MjA5MzQyNjU5OX0.Rdn6ujhp8t1qJNBmAc8VmV5xst10tpSzXh5tt6VCptM
Body (tools/call):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "<tool_name>",
"arguments": { ... }
}
}
All errors return HTTP 200 with a JSON-RPC error body. Check response.error, not the HTTP status code.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Unknown tool: get_foo"
}
}
| Code | Meaning | How to fix |
|---|---|---|
| HTTP 401 | Missing or invalid X-API-Key |
"Invalid API key" almost always means the key was regenerated. Ask the user to open the app → Settings → API Key, copy the current sb_ key, and update your config. Keys don't expire or get deleted on their own, so once you have the current one it keeps working — store it persistently (env var / config file), don't re-derive it each session. |
| HTTP 403 | Key is valid but user profile not found | Open the Squirrel Brain app once to create the profile, then retry. |
| -32600 | Invalid JSON-RPC request (bad structure) | Check you're sending { jsonrpc: "2.0", id, method, params }. |
| -32601 | Unknown tool name | Check spelling. Call get_squirrel_brain_help for the full tool list. |
| -32602 | Invalid params (missing required field) | The error message names the missing field. Add it. |
| -32603 | Insufficient scope or internal error | Read the message field. Every key is issued with full permissions by default, so "Insufficient scope" is rare — if you hit it, regenerate your key in Settings → API Key. Any other -32603 message is a transient server error — check the data field and retry once. |
rotate_api_key — the old key is revoked and the fresh key is returned instantly. Either way, your agent will need step 5 (update its config with the new key).
All limits are per-user per-key — not global. Anomaly detection flags bulk reads (> 1,000 reads in 10 minutes) and soft-suspends the token with a push notification to the user.
| Action | Per minute | Per hour | Per day |
|---|---|---|---|
| Read queries | 60 | 600 | 5,000 |
| Write (items) | 20 | 100 | 500 |
| Alarm creates | 5 | 10 | 30 |
| Push notifications | 10 | 20 | 50 |
| cell_alert (voice) | 2 | 5 | 10 |
| Brief trigger | 1 | 2 | 3 |