Skip to main content
Glama
nazar256

firefox-relay-mcp

README.md
# Firefox Relay MCP Gateway

[![CI](https://github.com/nazar256/firefox-relay-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/nazar256/firefox-relay-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Stateless Streamable HTTP MCP gateway for Firefox Relay, built for Cloudflare Workers.

## Features

- Stateless remote MCP server for Firefox Relay
- OAuth Authorization Code + PKCE (`S256`) for MCP clients
- Encrypted Firefox Relay API key envelope inside signed JWT artifacts
- Streamable HTTP MCP endpoint at `/mcp`
- Random mask list/create/update/disable operations
- Optional custom-domain mask listing when the Relay account supports it

## Requirements

- Node.js 20+
- npm
- Cloudflare account with Wrangler authentication
- A Firefox Relay account with an API key

## Connector values

```text
MCP Server URL: https://firefox-relay-mcp.xyofn8h7t.workers.dev/mcp
Authorization server base URL: https://firefox-relay-mcp.xyofn8h7t.workers.dev
Resource: https://firefox-relay-mcp.xyofn8h7t.workers.dev/mcp
```

## Use the hosted MCP in ChatGPT

The currently deployed production MCP endpoint is:

```text
https://firefox-relay-mcp.xyofn8h7t.workers.dev/mcp
```

### Connector details

Use these values when ChatGPT asks for the custom app or connector details:

```text
Name: Firefox Relay
Description: Manage Firefox Relay email masks: list, create, update, and disable random masks, and list custom-domain masks.
MCP server URL: https://firefox-relay-mcp.xyofn8h7t.workers.dev/mcp
Icon: assets/firefox-relay-icon.png
```

The repository includes a square PNG icon at [`assets/firefox-relay-icon.png`](assets/firefox-relay-icon.png). It is provided only to identify Firefox Relay compatibility. Firefox Relay is a trademark of the Mozilla Foundation; this project is independent and is not affiliated with or endorsed by Mozilla. The source icon is the [official Mozilla Relay icon](https://www.mozilla.org/media/img/nav/icons/icon-relay.aca61c9bb349.svg).

### Add and authorize it

ChatGPT’s labels vary by plan and workspace. OpenAI currently documents custom MCP apps under **Settings → Apps**; some accounts may show **Plugins** or **custom connectors** instead. Workspace administrators may need to enable developer mode or custom MCP connectors first.

1. Open ChatGPT on the web and go to **Settings → Apps** (or open the **Plugins** directory if that is what your account shows).
2. Choose **Create**, **Add custom app**, **Add connector**, or the equivalent **+** button.
3. Enter the connector details above. Upload `assets/firefox-relay-icon.png` as the icon.
4. For the connection authentication method, choose **OAuth**. The MCP URL is the server URL above; the Relay API key is entered during the browser authorization step, not in the connector description.
5. If ChatGPT shows **Scan tools**, run the scan and then choose **Create** or **Save**.
6. When the authorization page opens, open [Firefox Relay](https://relay.firefox.com) in another tab and sign in.
7. In Firefox Relay, open the **profile icon → Settings**, locate your API key, and copy it. Do not paste the key into a chat message or commit it to a repository.
8. Return to the authorization page, paste the API key into the **Firefox Relay API key** field, choose the token lifetime, and submit the consent form.
9. Return to ChatGPT. In a new chat, select **+ → More → Firefox Relay** (or mention the connected app) and ask it to manage your masks.

This gateway can list, create, update, and disable masks. Disabling is reversible; delete-mask tooling is intentionally not exposed in v1. Custom-domain listing depends on the Relay account.

For the official ChatGPT setup terminology and workspace requirements, see [Apps in ChatGPT](https://help.openai.com/en/articles/11487775-connectors-in) and [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt).

## What it does

- Exposes MCP Streamable HTTP at `/mcp`
- Acts as its own OAuth Authorization Server for remote MCP clients
- Collects a user-provided Firefox Relay API key during OAuth consent
- Encrypts that API key into signed JWT artifacts and does not store it in a database
- Lets MCP clients list, create, update, and disable Firefox Relay masks

## Firefox Relay API key

Users can find the Firefox Relay API key in Firefox Relay under **profile icon → Settings**.

## Supported MCP tools

- `relay_list_random_masks`
- `relay_create_random_mask`
- `relay_update_random_mask`
- `relay_disable_random_mask`
- `relay_list_domain_masks`

## Install

```bash
npm install
```

## Quick start

1. Install dependencies.
2. Set Worker secrets.
3. Configure non-secret vars in `wrangler.toml` or via deploy-time environment overrides.
4. Run `npm run dev` for local testing.
5. Run `npm test` and `npm run typecheck`.
6. Deploy with `npm run deploy`.

## Configure non-secret Worker vars

Update `wrangler.toml` or your deployment environment with:

- `OAUTH_ISSUER`
- `MCP_RESOURCE`
- `MCP_AUDIENCE`
- `OAUTH_REDIRECT_HTTPS_HOSTS`
- `RELAY_DEFAULT_BASE_URL`
- `ACCESS_TOKEN_DEFAULT_TTL_DAYS`
- `ACCESS_TOKEN_MAX_TTL_DAYS`
- `AUTH_CODE_TTL_SECONDS`

`wrangler.toml` intentionally keeps placeholder URLs for independent deployments. If you deploy your own Worker, replace them with that deployment's values before production:

- `OAUTH_ISSUER=https://your-worker.example.com`
- `MCP_RESOURCE=https://your-worker.example.com/mcp`
- `MCP_AUDIENCE=https://your-worker.example.com/mcp`

## Set Worker secrets

Generate and pipe each secret directly into Wrangler:

```bash
openssl rand -base64 48 | tr -d '\n' | wrangler secret put OAUTH_JWT_SIGNING_KEY_B64
openssl rand -base64 32 | tr -d '\n' | wrangler secret put UPSTREAM_CONFIG_ENC_KEY_B64
openssl rand -base64 48 | tr -d '\n' | wrangler secret put CSRF_SIGNING_KEY_B64
```

## Deploy sequence

If you do not know the final public Worker URL yet, use this order:

1. Bootstrap deploy once to get the real `workers.dev` URL.
2. Set the three Worker secrets.
3. Redeploy with real `OAUTH_ISSUER`, `MCP_RESOURCE`, and `MCP_AUDIENCE` values.

## Run locally

```bash
npm run dev
```

## Validate

```bash
npm test
npm run typecheck
```

## Deploy

```bash
npm run deploy
```

## Public connector values

After deployment, configure remote MCP clients with:

```text
MCP Server URL: https://firefox-relay-mcp.xyofn8h7t.workers.dev/mcp
Authorization server base URL: https://firefox-relay-mcp.xyofn8h7t.workers.dev
Resource: https://firefox-relay-mcp.xyofn8h7t.workers.dev/mcp
```

## OAuth and stateless design summary

- `/register` issues deterministic public `client_id` values for allowlisted redirect URIs
- `/authorize` validates PKCE S256, renders consent, validates the Firefox Relay API key, and issues a short-lived signed auth-code JWT
- `/token` exchanges the auth code for bearer access and refresh JWTs
- JWTs carry an AES-GCM encrypted Relay config envelope; plaintext credentials are never persisted server-side
- `/mcp` verifies the access token, decrypts the Relay config, creates a fresh Worker-safe MCP server and transport, and serves the request

## Stateless caveats

- Auth codes are not one-time-use across all isolates because no server-side state is stored
- Refresh tokens cannot be globally revoked without a stateful primitive
- Strict global rate limiting requires Durable Objects, KV, or Cloudflare-managed rate limiting

## Manual smoke test notes

1. Register a client with an allowlisted redirect URI.
2. Complete `/authorize` with a Firefox Relay API key.
3. Exchange the returned code at `/token`.
4. Call `/mcp` with the returned bearer token.
5. Confirm tools list and successful mask operations.

## Privacy and security notes

- The Worker encrypts the Firefox Relay API key into MCP JWT artifacts and does not store it in a database.
- The Worker is designed to avoid logging API keys, bearer tokens, decrypted envelopes, cookies, or CSRF tokens.
- Upstream Relay errors are sanitized before they are returned to MCP clients.

## Known limitations

- Auth codes are not globally one-time-use without stateful storage.
- Refresh tokens cannot be globally revoked without stateful storage.
- Strict global rate limiting requires Durable Objects, KV, or a Cloudflare-managed alternative.
- Domain-mask behavior depends on account capabilities and may require Relay premium/custom-domain support.

## Project docs

- `docs/PRODUCT_REQUIREMENTS.md`
- `docs/IMPLEMENTATION_PLAN.md`
- `docs/PROJECT_STATE.md`
- `docs/DECISIONS.md`
- `docs/RUNBOOK.md`

## Contributing

See `CONTRIBUTING.md`.

## Security

See `SECURITY.md`.

## License

MIT