Skip to main content
Glama
odiak
by odiak

HEY MCP on Cloudflare Workers

English | 日本語

A personal, single-user server that connects your HEY account to MCP clients through OAuth. To use it, deploy your own instance to Cloudflare Workers in your Cloudflare account. Only connections approved by the owner using the admin secret can access the account.

Architecture

  • Workers secrets: ENCRYPTION_KEY (32 random bytes encoded as Base64) and a separate ADMIN_SECRET (at least 32 characters).

  • Owner Durable Object: Encrypts HEY credentials and the install_id with AES-256-GCM. Also manages admin sessions, connection permissions, revocation, and HEY token refreshes.

  • Workers KV: Cloudflare's OAuth library manages client registrations, authorization codes, and MCP tokens. HEY credentials are not stored here.

  • Streamable HTTP MCP: /mcp, using MCP SDK v2 and the stateless handler from Agents.

  • Admin UI: /admin, for uploading credentials, verifying connectivity, listing and revoking connections, and deleting credentials.

The admin UI does not ask for the encryption key. The server reads it from its secret and verifies that stored credentials can be decrypted. The admin and consent screens currently use Japanese.

Related MCP server: copilot-outlook-connector

Run locally

Use Node.js 24 or later and npm.

npm ci
npm run setup:local
npm run dev

setup:local generates two independent random secrets in the Git-ignored .dev.vars file with permissions 0600. It will not overwrite an existing file or print the values to the console. Open the file in a local editor to find ADMIN_SECRET, then sign in to the admin UI.

Prepare HEY credentials

On your own machine, log in interactively using a directory dedicated to the Worker, keeping these credentials separate from your usual CLI credentials.

XDG_CONFIG_HOME="$HOME/.config/hey-mcp-bootstrap" \
  HEY_NO_KEYRING=1 HEY_BASE_URL=https://app.hey.com hey auth login

Select these two files in the admin UI:

  • ~/.config/hey-mcp-bootstrap/hey/credentials.json

  • ~/.config/hey-mcp-bootstrap/hey/install_id

After saving, click 「HEYへの接続を確認」 (Verify HEY connection). A successful upload and successful HEY authentication are displayed as separate states.

Do not use this directory for regular local CLI operations after uploading. Refreshing the same token from both the CLI and the Worker causes a race. The locally generated credential files contain plaintext secrets; after uploading and verifying the connection, store them securely or delete them. Only oauth_type: oauth authentication is supported. Session cookies and custom HEY endpoints are not supported.

Deploy to Cloudflare

This repository includes the author's deployment settings. To deploy your own instance, clone the repository locally, or fork it and clone your fork. Before deploying, follow the steps below to adapt the Worker name, KV namespace ID, and any custom domain settings in wrangler.jsonc to your own environment.

  1. Check the target Cloudflare account and Worker name. The server derives its public URLs from the incoming request URL, so no public-origin variable is needed. For a custom domain, configure the corresponding routes in wrangler.jsonc.

  2. Replace the OAUTH_KV namespace ID included in this repository with the ID of a namespace in your own account. For a new deployment, remove the existing ID so Wrangler can create a namespace in your account.

  3. Generate and register separate production secrets, then deploy.

npm run setup:production
npx wrangler secret bulk .local/secrets-production.json
npm run types
npm run deploy

secret bulk makes changes and performs a deployment on Cloudflare. Local secrets in .dev.vars are not automatically transferred to production. Use ADMIN_SECRET from .local/secrets-production.json to sign in to the production admin UI. Keep secrets and HEY credentials out of Git, chats, and screenshots.

Back up the production encryption key securely. Losing it makes the stored credentials unreadable. To change the key, first log in to HEY again using the dedicated directory, register the new key, and upload the new credentials. There is no migration feature to re-encrypt existing ciphertext. Changing the admin secret invalidates existing admin sessions. Revoke MCP connections separately from the connection list.

Connect an MCP client

Register https://<your-worker-host>/mcp in your client. Use the same host throughout authentication and subsequent MCP requests; tokens are bound to that URL. Authentication uses the OAuth authorization code flow with PKCE S256. After signing in as the admin, review the connection name, redirect destination, and permissions, and explicitly approve each connection.

Scope

Allowed operations

hey:read

Read emails, search results, contacts, calendars, and other data. Granted by default.

hey:write

Save drafts, organize emails, and modify contacts, todos, and other data. Requires read access as well.

hey:send

Send and schedule emails. Requires write access as well.

Access tokens last 15 minutes, and refresh tokens last 30 days. MCP refresh tokens rotate when used. Reusing a consumed refresh token revokes its connection. Simultaneous refreshes with the same token also count as reuse, so clients must serialize refreshes. If a refresh fails or its response is lost after the token was consumed, retrying with that token can also require reauthorization. Connections must be approved again after 30 days. Approving the same client again does not automatically revoke an earlier connection; you can revoke each one individually from the list.

Revocation blocks new MCP operations and MCP token issuance or refreshes. Even if old records remain in OAuth KV, the Durable Object rejects the revoked connection. Requests already sent to HEY cannot be canceled. The admin action 「認証情報と全連携を無効にする」 (Disable credentials and all connections) deletes the Worker's stored credentials and revokes all connections. It does not revoke the device session on HEY itself.

Tools and usage

The server implements the same seven domains and 71 operations as the reference HEY CLI's MCP server.

Tool

Coverage

hey_boxes

Imbox, Feed, Paper Trail, stacks, groups, and change listings

hey_search

Advanced search and search options

hey_threads

Topics, messages, drafts, replies, sending, and organization

hey_contacts

Contacts, notes, and the Screener

hey_todos

Create, update, complete, and delete todos. Read them through hey_calendar.

hey_calendar

Calendar listings and reading recordings

hey_identity

Identity information such as accounts, senders, and users

Each tool accepts action, params, and an optional account_id. Use action: "describe" to list available operations. Add params: { "action": "get_topic" }, for example, to retrieve an operation's input schema. Action names are API operation IDs converted to snake_case. As in the SDK, account_id is passed as filtered_account_id; it is a presentation filter, not an account authorization boundary.

{"action":"get_topic","params":{"topicId":123}}

Results have the shape {status, data, location?, next_page?, next_since?, next_v?}. Pagination uses cursors rather than numeric page numbers. Pass the returned cursor to the corresponding parameter, such as page. There is no tool for fetching arbitrary returned URLs.

Writes are not automatically retried. On a 401 response, the server refreshes the HEY credentials but does not resend the write; it asks you to check the outcome before trying again. Reads are retried once after a refresh. Network failures and 429/5xx responses do not discard credentials. A 4xx invalid_grant response from the refresh endpoint requires a new login.

Saving or updating a message without hey:send is allowed only when entry.status: "drafted" is set and no scheduling fields are present. UpdateMessage replaces fields rather than patching them: read the current draft first, and deliberately specify its subject, body, recipients, and schedule. This server exposes API operations rather than running the CLI's high-level commands. Features outside the CLI's MCP domains, such as event editing, habits, and journal entries, are not supported.

Security scope

  • AES-GCM protects against disclosure of stored data alone. An attacker with Worker execution or deployment access may still be able to decrypt it.

  • Admin sessions use random tokens, with only their SHA-256 hashes stored. Cookies use Secure/HttpOnly/SameSite=Strict and expire after one hour. State-changing requests validate both Origin and a CSRF token.

  • Admin login is limited to 10 attempts per IP per 10 minutes. Dynamic client registration is limited to 30 registrations per hour. MCP operations are limited to eight running or queued requests across all connections; excess requests receive 429. Disabling all connections revokes them before queued operations finish. The public DCR endpoint only registers clients; accessing HEY requires the owner's approval.

  • OAuth client metadata is treated as untrusted and HTML-escaped. External logos and scripts are not loaded. Consent uses a one-time handle bound to the browser.

  • HEY API and refresh endpoints are fixed. Credentials are never sent to arbitrary uploaded URLs or API redirect destinations.

  • Read-only permissions are checked when listing tools, executing MCP operations, and executing operations inside the Durable Object. Email content is external data and must not be trusted as instructions to an AI.

  • Application logs record event names, HTTP status codes, and similar metadata. CIMD fetch failures retain the domain and ordinary path, replacing query values, UUIDs, and long random-looking path strings with ***. Path masking is heuristic. Email content, raw client metadata or request URLs, and upstream error details are not logged. Cloudflare logs and traces redact query strings, and invocation logs are disabled.

Development and verification

npm run types
npm run check
npm test
npm run build

Tests run in Cloudflare's Workers runtime without real HEY credentials. They cover encryption, input validation, permissions, CSRF, PKCE, OAuth code exchange, refresh and revocation, and HEY refresh races and error handling. build is a dry run, not a production deployment.

The OAuth library is pinned to 1.2.1. The security patch in patches/ is applied by the postinstall script during npm install / npm ci. Do not disable installation scripts. See patches/README.md for the patch details and upgrade requirements.

To update the API model, specify a HEY CLI checkout:

npm run sync:model -- /path/to/basecamp/hey-cli

The source commit, SDK snapshot, and operation count are recorded in src/model/provenance.json. After updating the model, review changes to input schemas and the classification of operations that can send email.

References and licenses

This project is licensed under the MIT License. Copyright (c) 2026 Kaido Iwamoto.

HEY authentication, install_id, token refreshes, and MCP domains are based on basecamp/hey-cli. The API model is derived from the basecamp/hey-sdk snapshot included in the CLI. Upstream MIT licenses are included in licenses/. This project is not an official HEY hosted service.

OAuth is implemented with workers-oauth-provider, and MCP transport uses the Cloudflare Agents handler.

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    C
    maintenance
    Lets MCP clients read and search mail, manage calendars and events, and check availability for personal Microsoft accounts, with per-account delegated authentication and human approval for sensitive writes.
    22
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables existing apps to expose their users and data to external AI agents via MCP with OAuth 2.1 authentication and scoped tools.
    356 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables MCP clients to access your Substack account, including subscriptions, combined feed, full posts (paid ones you have access to), search, publication chats, direct messages, and free subscription management.
    12
    MIT