# MCP (AI Assistants)

The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that lets AI assistants connect to external data sources. Roam's MCP server exposes the Roam API as MCP tools — giving your AI assistant direct access to your meetings, transcripts, chat, calendar, and more. You can ask questions like "What did we decide about pricing in last week's meeting?" and get answers grounded in your actual conversations, or have the assistant post a chat message or create a calendar event on your behalf.

## Connections

MCP connections are **personal** — each is created by an individual user in **User Settings > Developer** and is scoped to that user's own meetings, chats, calendar, and groups. Most users connect Claude or ChatGPT this way.

## Admin Controls

Roam admins control whether MCP connections are allowed in your workspace. By default, new connections require admin approval before they can access data.

Admins can change the approval policy in **Administration > Developer**:

| Policy | Behavior |
|--------|----------|
| **Require Approval** (default) | New connections are pending until an admin approves them |
| **Auto-Approve** | New connections are approved automatically |
| **Disabled** | MCP connections are not allowed |

When a user creates a new connection with the **Require Approval** policy, admins will be notified and can approve or deny the request from the Administration panel.

![Administration > Developer showing approval policy options and token list with Approve/Reject/Revoke actions](/img/mcp/admin-approval-policy.png)

## Scopes & Permissions

Every tool maps to a Roam API endpoint that requires a specific OAuth **scope** (for example, `meetings:read` to read meetings, or `chat:send_message` to post a chat message). The [Tool Reference](#tool-reference) lists the scope each tool needs.

MCP uses the same OAuth scopes as the rest of the Roam API. The full set the MCP server supports is published in its [protected-resource metadata](https://api.ro.am/.well-known/oauth-protected-resource) under `scopes_supported`, so clients can discover it programmatically.

How scopes are granted depends on the client:

- **OAuth assistants (Claude, ChatGPT, Codex)** can't present a Roam scope picker, so each connection is granted the **default MCP scope set** automatically — the same set published in `scopes_supported`. It's a broad set that covers every MCP tool, including write tools such as `chat_post`, `calendar_event_create`, and the OnAir tools.
- **Personal Access Tokens** and other custom clients let you choose exactly which scopes to grant (see [Custom MCP Clients](#custom-mcp-clients)) — grant only what your integration needs.

The server **advertises every tool regardless of your granted scopes**; calling a tool whose scope you weren't granted returns a permission error (`403`). In practice this only affects Personal Access Tokens created with a narrow scope selection — a default OAuth connection receives the full set and can call every tool.

:::note
Per-connection scope selection for OAuth assistants is a planned future capability. Today these connections receive the full default MCP scope set; use a Personal Access Token if you need tighter, least-privilege scoping.
:::

## Claude

[Claude](https://claude.ai) supports MCP natively through its Connectors feature. No token or manual configuration is needed — Claude handles authentication via OAuth.

1. Open **Settings > Connectors**.
2. Click **Add custom connector**.
3. Enter a name (e.g. "Roam") and the Remote MCP Server URL: `https://api.ro.am/mcp`
4. Click **Connect** and complete the OAuth consent flow.

![Claude Settings > Connectors showing the "Add custom connector" dialog with Roam configured](/img/mcp/claude-connector-setup.png)

Once connected, Roam's tools are available in your conversations.

### Using Prompts

To use a built-in prompt, click the **+** button in the message composer, select **Add from Roam**, and choose a prompt (e.g. `morning_brief` or `weekly_digest`).

![Claude + menu showing "Add from Roam" with available prompts](/img/mcp/claude-add-prompt.png)

## ChatGPT

ChatGPT supports MCP through its Apps feature. To connect Roam:

> Using OpenAI's **Codex** coding agent instead of the ChatGPT app? It's a different product configured separately — see [Codex](#codex).

1. Open **Settings > Apps > Advanced Settings**.
2. Turn on **Developer Mode**.
3. Click **Create app**.
4. Enter a name (e.g. "Roam") and the MCP URL: `https://api.ro.am/mcp`
5. Click **Create**.

![ChatGPT Settings > Apps > Advanced Settings with Developer Mode enabled](/img/mcp/chatgpt-developer-mode.png)

![ChatGPT New App dialog with Roam name and MCP server URL configured](/img/mcp/chatgpt-create-app.png)

Once connected, you can use Roam in a conversation by clicking **+** > **More** > **Roam**.

![ChatGPT + menu showing Roam app under More](/img/mcp/chatgpt-roam-app.png)

Try asking about your recent meetings or chat messages.

:::note
ChatGPT does not support MCP prompts. Use the tools directly by asking questions in natural language.
:::

![ChatGPT conversation analyzing a meeting using Roam tools](/img/mcp/chatgpt-mcp-example.png)

## Codex

OpenAI's [Codex](https://developers.openai.com/codex) coding agent is **not** the ChatGPT app — it connects to MCP servers through Codex's own settings, not through ChatGPT's Apps settings, so the [ChatGPT](#chatgpt) steps above don't apply. Roam is served over **Streamable HTTP**, which current Codex versions support natively. Connect it from the command line or the Codex app; either way the connection authenticates over OAuth and receives the [default scope set](#scopes--permissions), the same as Claude and ChatGPT.

### Command line

Register Roam with the `codex mcp` CLI:

```bash
codex mcp add roam --url https://api.ro.am/mcp
```

Because Roam advertises OAuth, Codex detects it and opens your browser to Roam's consent flow automatically — there's no token to copy and no separate login step:

```
Added global MCP server 'roam'.
Detected OAuth support. Starting OAuth flow…
Successfully logged in.
```

Verify with `/mcp` inside Codex — Roam should appear as connected, with its tools listed. (To re-authenticate later, run `codex mcp login roam`.)

:::note Troubleshooting
If `codex mcp add` can't load the server — for example an error like ``missing field `command` in `mcp_servers.roam` `` — your Codex version is too old to use URL-based (Streamable HTTP) servers and only understands `command`-based stdio servers. Upgrade to the latest Codex and the `url` entry will load.
:::

### Codex app

In the Codex app, open **Settings > MCP servers** and click **Add server**. Fill in the form:

- **Name**: `Roam`
- Select the **Streamable HTTP** transport.
- **URL**: `https://api.ro.am/mcp`
- Leave **Bearer token env var** and the header fields empty — Roam uses OAuth.

Click **Save**.

![Codex "Connect to a custom MCP" form with Streamable HTTP selected and the Roam server URL entered](/img/mcp/codex-app-add-server.png)

Back on the **MCP servers** list, the new server shows an **Authenticate** button. Click it to complete Roam's OAuth consent flow.

![Codex MCP servers list showing the Roam server with an Authenticate button](/img/mcp/codex-app-authenticate.png)

:::note
Codex does not support MCP prompts. Use the tools directly by asking questions in natural language.
:::

## Custom MCP Clients

You can connect any MCP-compatible client to Roam's server.

**Server URL:** `https://api.ro.am/mcp`

**Transport:** Streamable HTTP

**Authentication:** Bearer token in the `Authorization` header. Clients can either complete the OAuth flow (the server supports Dynamic Client Registration) or use a Personal Access Token.

### OAuth details

Clients that implement the [MCP authorization](https://modelcontextprotocol.io/specification/basic/authorization) flow can discover everything they need from Roam's standard metadata:

| Item | Value |
|------|-------|
| Protected-resource metadata | `https://api.ro.am/.well-known/oauth-protected-resource` |
| Authorization-server metadata | `https://ro.am/.well-known/oauth-authorization-server` |
| Resource (audience) | `https://api.ro.am/mcp` |
| Authorization endpoint | `https://ro.am/oauth/authorize` |
| Token endpoint | `https://ro.am/oauth/token` |
| Registration endpoint (DCR) | `https://ro.am/oauth/register` |
| PKCE | Required for public clients; `S256` only |
| Token endpoint auth methods | `none` (public client + PKCE), `client_secret_post`, `client_secret_basic` |
| Scopes | See `scopes_supported` in the protected-resource metadata |

Both public clients (`token_endpoint_auth_method: none` with PKCE) and confidential clients (with a client secret) are supported. Public clients **must** use PKCE with `code_challenge_method=S256` — `plain` is rejected. Dynamically registered assistants that don't request specific scopes receive the [default MCP scope set](#scopes--permissions).

**Using `mcp-remote`:** the most reliable setup passes a Personal Access Token as a bearer header:

```bash
npx mcp-remote https://api.ro.am/mcp --header "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
```

`mcp-remote`'s built-in OAuth (Dynamic Client Registration) support is version-dependent, and some releases mishandle the PKCE code verifier (`No code verifier saved for session`). If its OAuth flow fails, use the bearer-token form above.

### Create a Personal Access Token

A Personal Access Token (PAT) is the simplest way to authenticate a custom client, and — unlike an OAuth connection — it lets you choose exactly which scopes to grant.

1. In Roam, open **User Settings > Developer**.
2. Click **Create Personal Access Token**.
3. Give it a descriptive name (e.g. "MCP Inspector").
4. Select the scopes you need. The [Tool Reference](#tool-reference) lists the scope each tool requires — for example, `chat:history` for `chat_history`, `calendar:write` for `calendar_event_create`, or `onair:read` for the OnAir read tools. Grant only what your integration uses.
5. Click **Create Token** and copy it (it starts with `rmp-…`).

![Personal Access Token creation dialog with scopes](/img/mcp/personal-access-token-creation.png)

### Testing with MCP Inspector

The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a browser-based tool for testing MCP servers. Use it to verify your connection and explore available tools.

1. Launch the inspector:

```bash
npx @modelcontextprotocol/inspector
```

2. In the inspector UI:
   - Set **Transport Type** to **Streamable HTTP**.
   - Set **URL** to `https://api.ro.am/mcp`.
   - Under **Request Headers**, add:
     - Header name: `Authorization`
     - Header value: `Bearer YOUR_PERSONAL_ACCESS_TOKEN`
   - Click **Connect**.

![MCP Inspector configured with Roam server URL and Authorization header](/img/mcp/mcp-inspector-connect.png)

3. Once connected, click the **Tools** tab to see all available tools. Click any tool to view its input schema and try it out.

<!-- SCREENSHOT: mcp-inspector-tools.png
Show the MCP Inspector connected to Roam, with the Tools tab open displaying the list of tools (meeting_search, chat_list, etc.).
-->

4. Click the **Prompts** tab to see built-in prompts like `morning_brief` and `weekly_digest`.

<!-- SCREENSHOT: mcp-inspector-prompts.png
Show the MCP Inspector Prompts tab with the list of available prompts.
-->

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `invalid_client` / "Client authentication failed" at token exchange | A public client omitted PKCE, or a confidential client sent its secret in a way the endpoint didn't read | Public clients: register with `token_endpoint_auth_method: none` and use PKCE (`S256`). Confidential clients: send the secret via `client_secret_post` or HTTP Basic. |
| Browser says authentication completed, but the client still shows "not logged in" | The browser callback succeeded but the background token exchange failed | Check the token-endpoint auth method and that the `resource` parameter is `https://api.ro.am/mcp`. |
| `code_challenge is required` or `code_challenge_method must be S256` | A public client started the flow without S256 PKCE | Send `code_challenge` with `code_challenge_method=S256`. Public clients must use S256; `plain` is rejected. |
| A tool is listed but returns `403` when called | Your token lacks that tool's scope | Use a token that includes the scope (see the [Tool Reference](#tool-reference)). Default OAuth connections already include the full [default scope set](#scopes--permissions); narrow Personal Access Tokens may not. |
| MCP endpoint returns `401` with `no bearer token` | Expected unauthenticated response | Follow the `WWW-Authenticate` header to the protected-resource metadata, then send a bearer token in the `Authorization` header. |
| `No code verifier saved for session` (with `mcp-remote`) | PKCE-state bug in some `mcp-remote` releases | Use the bearer-token form (`--header "Authorization: Bearer …"`) or upgrade `mcp-remote`. |

## Tool Reference

Roam's MCP server exposes the tools below, grouped by category. Each tool requires the listed OAuth **scope**.

Remember that a default OAuth connection (Claude, ChatGPT, or Codex) is granted only the [default scope set](#scopes--permissions) — tools whose scope falls outside it require a Personal Access Token until per-connection scope selection ships.

### User & Token

| Tool | Description | Scope |
|------|-------------|-------|
| `token_info` | Get info about the current token (client, user, org, scopes, expiration) | — |
| `get_me` | Get the authenticated user's profile (name, image, email, status) | — |
| `user_list` | List workspace users | `user:read` |

### Chat

| Tool | Description | Scope |
|------|-------------|-------|
| `chat_list` | List conversations (DMs, channels, team Roam, meeting channels) | `chat:read` |
| `chat_history` | Read messages from a chat or thread | `chat:history` |
| `chat_post` | Post a message to a chat, channel, or thread | `chat:send_message` |
| `chat_update` | Update a bot-posted message | `chat:send_message` |
| `chat_delete` | Delete a bot-posted message (destructive) | `chat:send_message` |
| `chat_search` | Full-text search across chats, with sender/date/attachment filters | `chat:history` |
| `search` | Simplified search alias (ChatGPT compatibility — accepts only `query`) | `chat:history` |
| `resolve_chat_link` | Resolve a Roam chat link URL to its chat/thread IDs | `chat:history` |

### Reactions

| Tool | Description | Scope |
|------|-------------|-------|
| `reaction_add` | Add an emoji reaction to a message | `chat:send_message` |
| `reaction_remove` | Remove an emoji reaction from a message | `chat:send_message` |
| `reaction_list` | List emoji reactions on a message | `chat:history` |

### Groups

| Tool | Description | Scope |
|------|-------------|-------|
| `group_info` | Get a group/channel's info by ID or name | `groups:read` |
| `group_list` | List groups/channels, with search and type filters | `groups:read` |

### Meetings

| Tool | Description | Scope |
|------|-------------|-------|
| `meeting_list` | List meetings, with optional summary/action-item/chapter expansion | `meetings:read` |
| `meeting_info` | Get details about a specific meeting | `meetings:read` |
| `meeting_participants` | List a meeting's participants | `meetings:read` |
| `meeting_transcript` | Get a meeting transcript (VTT) | `meetings:read` |
| `meeting_search` | Search meetings by keywords or natural language | `meetings:read` |
| `meeting_prompt` | Ask an AI question about a meeting's transcript | `meetings:read` |
| `meeting_link_create` | Create a meeting link | `meetinglink:write` |
| `meeting_link_info` | Get details about a meeting link | `meetinglink:read` |
| `meeting_link_update` | Update a meeting link | `meetinglink:write` |

### Calendar

| Tool | Description | Scope |
|------|-------------|-------|
| `calendar_list` | List scheduled events from connected calendars (Google, Outlook) | `meetings:read` |
| `calendar_event_create` | Create a calendar event with a Roam meeting link | `calendar:write` |

### OnAir

| Tool | Description | Scope |
|------|-------------|-------|
| `onair_event_list` | List OnAir broadcast events | `onair:read` |
| `onair_event_info` | Get details about an OnAir event | `onair:read` |
| `onair_event_create` | Create an OnAir event | `onair:write` |
| `onair_event_update` | Update an OnAir event | `onair:write` |
| `onair_event_cancel` | Cancel an OnAir event | `onair:write` |
| `onair_guest_list` | List an OnAir event's guests | `onair:read` |
| `onair_guest_info` | Get details about an OnAir guest | `onair:read` |
| `onair_guest_add` | Add guests to an OnAir event | `onair:write` |
| `onair_guest_update` | Update an OnAir guest | `onair:write` |
| `onair_guest_remove` | Remove a guest from an OnAir event | `onair:write` |
| `onair_host_list` | List OnAir hosts | `onair:read` |
| `onair_attendance_list` | List attendance records for an OnAir event | `onair:read` |

### Magicast

| Tool | Description | Scope |
|------|-------------|-------|
| `magicast_list` | List Magicast events | `magicast:read` |
| `magicast_info` | Get details about a Magicast event | `magicast:read` |

### Lobbies

| Tool | Description | Scope |
|------|-------------|-------|
| `lobby_list` | List configured lobbies | `lobby:read` |
| `lobby_booking_list` | List bookings for a lobby | `lobby:read` |

### Webhooks

| Tool | Description | Scope |
|------|-------------|-------|
| `webhook_subscribe` | Subscribe to webhook events at a URL | `webhook:write` |
| `webhook_unsubscribe` | Unsubscribe from a webhook | `webhook:write` |

## Prompts

Built-in **prompts** provide ready-made workflows. In Claude, add them from the **+** menu (see [Using Prompts](#using-prompts)). Some prompts take an argument.

| Prompt | Description |
|--------|-------------|
| `action_item_radar` | Action items by urgency and ownership, highlighting carry-over and at-risk work |
| `meeting_prep` | A concise prep brief for your next meeting |
| `morning_brief` | Yesterday's meetings and action items, today's schedule, and recent chat activity |
| `chat_recap` | Recent activity across your conversations, highlighting items needing a response |
| `group_digest` | Activity summary for a specific group _(argument: `group`)_ |
| `decision_log` | Decisions from recent meetings: what, when, and by whom |
| `project_status` | Status report for a specific project, from meetings and chat _(argument: `project`)_ |
| `weekly_digest` | Comprehensive weekly summary with key decisions and action items |

:::note
ChatGPT does not support MCP prompts. Use the tools directly by asking questions in natural language.
:::