spark-mcp-remote
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@spark-mcp-remotesearch my inbox for invoices from Acme"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
spark-mcp-remote
Reach your Spark mailbox from Claude on your phone / claude.ai.
Spark's official Spark for Claude is a stdio MCP server that shells out to the Spark CLI, which only exists where Spark Desktop is running (macOS/Windows). That's fine for Claude Desktop, invisible to Claude mobile and claude.ai.
This is a small bridge that spawns Readdle's server as a child process and exposes it over MCP Streamable HTTP, authenticated with OAuth 2.1 so the hosted Claude surfaces can actually connect to it. Run it on the Mac next to Spark, tunnel it out, add the URL as a custom connector, done.
Claude mobile / claude.ai
│ HTTPS (Cloudflare Tunnel)
▼
spark-mcp-remote (this) ← OAuth 2.1 + DCR, send/read-only gates
│ stdio
▼
readdle/spark-claude-extension ← official, vendored, unmodified
│ execFile
▼
spark CLI → Spark Desktop → your accountsIt proxies at the MCP level (tools/list, tools/call), so upstream tool changes flow through without touching this repo. It does not run on Linux/Coolify — there is no headless Spark. It has to live on the machine that runs Spark Desktop.
Requirements
macOS or Windows with Spark Desktop signed in
Spark CLI enabled: Spark → Settings → AI Agents → Spark CLI Setup
Node ≥ 20.6 (for
--env-file)A domain on Cloudflare (free plan is fine) for the tunnel
Related MCP server: iCloud Email MCP Server
Install
git clone https://github.com/kevincolten/spark-mcp-remote.git
cd spark-mcp-remote
npm install
npm run setup # clones readdle/spark-claude-extension into vendor/, writes .env with a random token
npm startCheck it:
curl -s localhost:8787/healthz
# /mcp is OAuth-protected: unauthenticated requests must 401 and point at the
# metadata that starts the handshake.
curl -si -X POST localhost:8787/mcp | grep -i 'HTTP/\|www-authenticate'There is no way to hand-craft a bearer for /mcp — a token has to be issued
through the OAuth flow. npm test drives that flow end to end if you want to
see it work locally.
Expose it (Cloudflare Tunnel)
The server binds 127.0.0.1 on purpose; cloudflared handles ingress and TLS.
Use a remotely-managed tunnel: the ingress rules live in Cloudflare, not in a
local config.yml, so there is nothing to keep in sync on the Mac and no
cert.pem to obtain — which also means no cloudflared tunnel login and no
browser. An API token with Cloudflare Tunnel: Edit, DNS: Edit on the zone and
Account Settings: Read is enough.
brew install cloudflared
export CLOUDFLARE_API_TOKEN=... ACCOUNT=<account id> ZONE=<zone id>
API=https://api.cloudflare.com/client/v4
auth="Authorization: Bearer $CLOUDFLARE_API_TOKEN"
# 1. create the tunnel (config_src=cloudflare => remotely managed)
TUNNEL=$(curl -s -X POST -H "$auth" -H 'content-type: application/json' \
--data '{"name":"spark-mcp","config_src":"cloudflare"}' \
"$API/accounts/$ACCOUNT/cfd_tunnel" | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["id"])')
# 2. ingress: the hostname, then a catch-all
curl -s -X PUT -H "$auth" -H 'content-type: application/json' \
--data '{"config":{"ingress":[
{"hostname":"spark-mcp.example.com","service":"http://127.0.0.1:8787"},
{"service":"http_status:404"}]}}' \
"$API/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL/configurations"
# 3. proxied CNAME
curl -s -X POST -H "$auth" -H 'content-type: application/json' \
--data "{\"type\":\"CNAME\",\"name\":\"spark-mcp\",\"content\":\"$TUNNEL.cfargotunnel.com\",\"proxied\":true}" \
"$API/zones/$ZONE/dns_records"
# 4. run token -> chmod-600 file the supervisor wrapper reads
mkdir -p ~/.cloudflared
curl -s -H "$auth" "$API/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL/token" \
| python3 -c 'import sys,json;sys.stdout.write(json.load(sys.stdin)["result"])' \
> ~/.cloudflared/spark-mcp.token
chmod 600 ~/.cloudflared/spark-mcp.tokenTest it in the foreground, then hand it to supervisord (below):
supervisor/run-tunnel.sh
curl -s https://spark-mcp.example.com/healthzThe token is the only local secret. It stays in ~/.cloudflared/spark-mcp.token
and reaches cloudflared through TUNNEL_TOKEN, so it is in neither the
world-readable supervisor .ini nor the argv ps shows.
Optional: add a Cloudflare Access policy on the hostname for a second factor in front of OAuth.
Add to Claude
Claude.ai → Settings → Connectors → Add custom connector → URL
https://spark-mcp.example.com/mcp
Leave the OAuth Client ID/Secret fields empty. Claude discovers the bridge's own
authorization server, registers itself, and opens a consent screen; paste the
SPARK_MCP_TOKEN from .env there and it connects. It shows up on mobile too.
Claude Code uses the same flow — no header, no token argument:
claude mcp add --transport http spark-mcp https://spark-mcp.example.com/mcp
# then `/mcp` in a session to run the OAuth flowInstall Spark.skill from the Readdle releases page for the full workflow guidance.
Why OAuth
The hosted Claude surfaces authenticate a connector over OAuth. The connector
dialog has no field for a static bearer token — static_headers exists only as
an organization-admin beta — so a server that just checks a shared secret is
reachable from curl and unreachable from Claude.ai and mobile, which is the
entire point of this bridge.
So the bridge is also its own OAuth 2.1 authorization server (src/oauth.js):
RFC 9728 protected-resource metadata, RFC 8414 server metadata, RFC 7591 dynamic
client registration, S256 PKCE, audience-bound access tokens and rotating
refresh tokens. It adds /.well-known/oauth-protected-resource,
/.well-known/oauth-authorization-server, /oauth/register, /oauth/authorize
and /oauth/token.
There is one user and no user database. SPARK_MCP_TOKEN is the credential the
consent screen checks, and it doubles as the HMAC key signing every client_id,
authorization code and token — so the server holds no session state (supervisord
restarts it on every deploy without logging Claude out) and rotating the secret
revokes everything at once.
SPARK_MCP_TOKEN is not accepted as a bearer token on /mcp. Only tokens
the server issued are, which keeps the long-lived secret off the wire: what
travels on each request is a short-lived, audience-bound token that expires in an
hour.
Keep it running (macOS)
Two options. Both must run as your user, never as root — the Spark CLI talks to the Spark Desktop GUI session.
Option A: supervisord (web UI, recommended)
Gives you a local start/stop/restart/tail-logs dashboard for this and any other bare-metal services on the Mac. No containers, no cloud.
brew install supervisor
mkdir -p $(brew --prefix)/etc/supervisor.d
for ini in spark-mcp-remote cloudflared-spark; do
sed "s#/Users/YOU#$HOME#g; s#^user=YOU\$#user=$(whoami)#" "supervisor/$ini.ini" \
> "$(brew --prefix)/etc/supervisor.d/$ini.ini"
done
# merge supervisor/supervisord.conf into $(brew --prefix)/etc/supervisord.conf (inet_http_server + include)
brew services start supervisor # user-level launchd agent, survives reboots
supervisorctl statusUI is at http://127.0.0.1:9001. Keep it on loopback (or a Tailscale IP if you run Tailscale) — never 0.0.0.0. OAuth protects /mcp, but the supervisor UI can restart things and read logs.
Intel Macs: replace /opt/homebrew with /usr/local in the ini files.
Option B: launchd directly
cp launchd/com.kevincolten.spark-mcp-remote.plist ~/Library/LaunchAgents/
# edit WorkingDirectory + node path in the plist
launchctl load ~/Library/LaunchAgents/com.kevincolten.spark-mcp-remote.plistThe bridge respawns the upstream process if it dies (e.g. Spark restarted).
Safety switches
Env | Default | Effect |
| required | Consent-screen credential and token signing key, ≥ 24 chars. Not a bearer token — rotating it revokes every issued token. |
| off | Hides and blocks |
| off | Enables the |
|
| Point at a different upstream (e.g. the extracted |
| upstream default | Passed through to Readdle's server |
| derived from | Pin the external origin used in OAuth metadata when the proxy does not set forwarding headers |
Spark's own per-account access levels (read-only / triage / send in Settings → AI Agents) still apply underneath. This bridge only ever narrows, never widens.
Calls are tagged AI_AGENT=claude-remote in Spark's audit log so you can tell remote from desktop.
Test
npm test # boots the bridge against a fake stdio upstream; checks auth, tool
# listing, send gate, and the full OAuth flow (discovery, DCR, PKCE,
# code replay, audience binding, refresh rotation)License
MIT. The vendored upstream is MIT © Spark Mail Limited.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Connect any mailbox to Claude, ChatGPT & AI: read, send, reply, schedule & search emails.
- QuallaaOAuthcom.quallaa
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
MCP server for Nylas — read email, calendars, events and contacts, and send email or create events.
Related MCP Servers
- AlicenseAqualityDmaintenanceA Model Context Protocol (MCP) server that enables Claude Desktop to manage emails via SMTP and IMAP. Send emails, fetch unread messages, and create draft replies directly from conversations.345MIT
- FlicenseNot gradedqualityDmaintenanceA Model Context Protocol (MCP) server that enables Claude Desktop to interact with iCloud email accounts. This server provides full email functionality including reading, sending, and managing emails through your iCloud account.1-
- AlicenseAqualityCmaintenanceMCP server that connects Claude to Microsoft 365 mail for inbox triage, search, classification, and idempotent rule provisioning via Microsoft Graph, with device-code auth and read-only default.61MIT
- FlicenseNot gradedqualityCmaintenanceLocal IMAP/SMTP MCP server that lets Claude read, search, draft, send, flag, and move mail across multiple IMAP mailboxes. Credentials stay on your machine.-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kevincolten/spark-mcp-remote'
If you have feedback or need assistance with the MCP directory API, please join our Discord server