# MCP server

ThreadPage’s MCP server lets an assistant find, read, add and organize a person’s notes with the access that person chooses.

## Server

|  |  |
|---|---|
| URL | `https://thread.page/mcp` |
| Transport | Streamable HTTP, stateless, JSON responses |
| Sign-in | OAuth in the browser. ThreadPage asks the person to choose access. Every level. |
| Token | `Authorization: Bearer tp_…` from a **Script or API** connection, on Add to Inbox, Add only, Read only or Read and add. Edit and organize through MCP needs OAuth (`403` `oauth_required`). |
| Discovery | `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server` |
| Clients that can sign in | Claude, ChatGPT, Codex, Claude Code |

Add the URL to a client. Opened in a browser, or fetched without a token, it answers `401`.

## Connect

| Client | How |
|---|---|
| Claude | In ThreadPage, **Settings → Connections → Apps**, choose **Connect an app** → **Claude app**, then **Add to Claude**. Or in Claude, **Customize → Connectors**, **Add custom connector**, and paste the URL. |
| ChatGPT | On the web, choose **+** and **Add custom MCP server**, name it ThreadPage and paste the URL, then **Create as a plugin** and install it. |
| Codex, Claude Code | See [Coding agents](https://thread.page/help/connections/coding-agents.md). |

Sign-in opens ThreadPage, where the person chooses the level and which threads and folders. The default for an assistant is **Read and add**. Step-by-step setup for each app is in [Apps](https://thread.page/help/connections/apps.md), and for Claude Code and Codex in [Coding agents](https://thread.page/help/connections/coding-agents.md).

## What the server tells assistants

When a client connects, the server sends instructions: how ThreadPage is organized and the rules for writing, editing and retrying. Some clients, such as Claude on the web and Claude Desktop, don’t pass them to the model.

`get_guide` with `topic` `formatting` answers every Markdown and document element; `working` answers the tools, editing, retries, limits and errors. The resources `threadpage://instructions`, `threadpage://guide/formatting` and `threadpage://guide/working` hold the same texts.

The ThreadPage skill gives an assistant the same guides ahead of time, which helps where instructions don’t reach the model. See [Assistant guide](https://thread.page/help/connections/agents.md).

## Tools

Every tool has a title, a description, annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`) and strict arguments. Annotations help a client ask before a change; ThreadPage enforces the level on the server either way.

| Level | Tools |
|---|---|
| Add to Inbox | `get_access`, `create_note` (to the Inbox only), `get_operation`, `get_guide` |
| Add only | `get_access`, `list_threads` (titles), `create_note`, `upload_note_image`, `get_operation`, `get_guide` |
| Read only | The read tools: threads, folders, search, notes, versions, images, files and reminders |
| Read and add | Every tool. It adds notes, threads and reminders, and changes or trashes the notes it added. Other changes are refused. |
| Edit and organize | Every tool, on any note, thread and folder in its scope |

The full table, with each tool’s levels, annotations and the API operation that does the same:

**MCP tools by level**

| Tool | Levels | Changes notes | API operation |
|---|---|---|---|
| `get_access` | Edit and organize · Read and add · Read only · Add only · Add to Inbox | No | [`GET /connection`](https://thread.page/help/connections/api.md#getConnection) |
| `get_operation` | Edit and organize · Read and add · Read only · Add only · Add to Inbox | No | [`GET /operations/{operation_id}`](https://thread.page/help/connections/api.md#getOperation) |
| `list_threads` | Edit and organize · Read and add · Read only · Add only | No | [`GET /threads`](https://thread.page/help/connections/api/threads.md#listThreads) |
| `get_thread` | Edit and organize · Read and add · Read only | No | [`GET /threads/{thread_id}`](https://thread.page/help/connections/api/threads.md#getThread) and [`GET /threads/{thread_id}/notes`](https://thread.page/help/connections/api/notes.md#listThreadNotes) |
| `create_thread` | Edit and organize · Read and add | Yes | [`POST /threads`](https://thread.page/help/connections/api/threads.md#createThread) |
| `update_thread` | Edit and organize | Yes | [`PATCH /threads/{thread_id}`](https://thread.page/help/connections/api/threads.md#updateThread) |
| `archive_thread` | Edit and organize | Yes | [`POST /threads/{thread_id}/archive`](https://thread.page/help/connections/api/threads.md#archiveThread) |
| `unarchive_thread` | Edit and organize | Yes | [`POST /threads/{thread_id}/unarchive`](https://thread.page/help/connections/api/threads.md#unarchiveThread) |
| `trash_thread` | Edit and organize | Yes | [`POST /threads/{thread_id}/trash`](https://thread.page/help/connections/api/threads.md#trashThread) |
| `restore_thread` | Edit and organize | Yes | [`POST /threads/{thread_id}/restore`](https://thread.page/help/connections/api/threads.md#restoreThread) |
| `move_thread` | Edit and organize | Yes | [`POST /threads/{thread_id}/move`](https://thread.page/help/connections/api/threads.md#moveThread) |
| `create_note` | Edit and organize · Read and add · Add only · Add to Inbox | Yes | [`POST /inbox/notes`](https://thread.page/help/connections/api/notes.md#addToInbox) and [`POST /threads/{thread_id}/notes`](https://thread.page/help/connections/api/notes.md#createNote) |
| `upload_note_image` | Edit and organize · Read and add · Add only | Yes | [`POST /threads/{thread_id}/images`](https://thread.page/help/connections/api/notes.md#uploadImage) |
| `get_note` | Edit and organize · Read and add · Read only | No | [`GET /notes/{note_id}`](https://thread.page/help/connections/api/notes.md#getNote) |
| `update_note` | Edit and organize · Read and add | Yes | [`PUT /notes/{note_id}/content`](https://thread.page/help/connections/api/notes.md#replaceNoteContent) |
| `update_note_properties` | Edit and organize | Yes | [`PATCH /notes/{note_id}`](https://thread.page/help/connections/api/notes.md#updateNoteProperties) |
| `move_note` | Edit and organize · Read and add | Yes | [`POST /notes/{note_id}/move`](https://thread.page/help/connections/api/notes.md#moveNote) |
| `trash_note` | Edit and organize · Read and add | Yes | [`POST /notes/{note_id}/trash`](https://thread.page/help/connections/api/notes.md#trashNote) |
| `restore_note` | Edit and organize · Read and add | Yes | [`POST /notes/{note_id}/restore`](https://thread.page/help/connections/api/notes.md#restoreNote) |
| `get_note_history` | Edit and organize · Read and add · Read only | No | [`GET /notes/{note_id}/versions`](https://thread.page/help/connections/api/notes.md#listNoteVersions) |
| `get_note_version` | Edit and organize · Read and add · Read only | No | [`GET /notes/{note_id}/versions/{version_id}`](https://thread.page/help/connections/api/notes.md#getNoteVersion) |
| `restore_note_version` | Edit and organize · Read and add | Yes | [`POST /notes/{note_id}/versions/{version_id}/restore`](https://thread.page/help/connections/api/notes.md#restoreNoteVersion) |
| `read_note_attachment` | Edit and organize · Read and add · Read only | No | [`GET /notes/{note_id}/images/{image_id}`](https://thread.page/help/connections/api/notes.md#getNoteImage) |
| `read_note_file` | Edit and organize · Read and add · Read only | No | [`GET /notes/{note_id}/files/{file_id}`](https://thread.page/help/connections/api/notes.md#getNoteFile), [`GET /notes/{note_id}/files/{file_id}/preview`](https://thread.page/help/connections/api/notes.md#getNoteFilePreview) and [`GET /notes/{note_id}/files/{file_id}/text`](https://thread.page/help/connections/api/notes.md#getNoteFileText) |
| `search` | Edit and organize · Read and add · Read only | No | [`GET /search`](https://thread.page/help/connections/api/notes.md#searchNotes) |
| `list_statuses` | Edit and organize | No | [`GET /statuses`](https://thread.page/help/connections/api/statuses.md#listStatuses) |
| `list_folders` | Edit and organize · Read and add · Read only | No | [`GET /folders`](https://thread.page/help/connections/api/threads.md#listFolders) |
| `create_folder` | Edit and organize | Yes | [`POST /folders`](https://thread.page/help/connections/api/threads.md#createFolder) |
| `rename_folder` | Edit and organize | Yes | [`PATCH /folders/{folder_id}`](https://thread.page/help/connections/api/threads.md#renameFolder) |
| `move_folder` | Edit and organize | Yes | [`POST /folders/{folder_id}/move`](https://thread.page/help/connections/api/threads.md#moveFolder) |
| `trash_folder` | Edit and organize | Yes | [`POST /folders/{folder_id}/trash`](https://thread.page/help/connections/api/threads.md#trashFolder) |
| `restore_folder` | Edit and organize | Yes | [`POST /folders/{folder_id}/restore`](https://thread.page/help/connections/api/threads.md#restoreFolder) |
| `list_reminders` | Edit and organize · Read and add · Read only | No | [`GET /threads/{thread_id}/reminders`](https://thread.page/help/connections/api/reminders.md#listThreadReminders) |
| `get_reminder` | Edit and organize · Read and add · Read only | No | [`GET /notes/{note_id}/reminder`](https://thread.page/help/connections/api/reminders.md#getNoteReminder) and [`GET /threads/{thread_id}/reminder`](https://thread.page/help/connections/api/reminders.md#getThreadReminder) |
| `set_reminder` | Edit and organize · Read and add | Yes | [`PUT /notes/{note_id}/reminder`](https://thread.page/help/connections/api/reminders.md#setNoteReminder) and [`PUT /threads/{thread_id}/reminder`](https://thread.page/help/connections/api/reminders.md#setThreadReminder) |
| `remove_reminder` | Edit and organize · Read and add | Yes | [`DELETE /notes/{note_id}/reminder`](https://thread.page/help/connections/api/reminders.md#removeNoteReminder) and [`DELETE /threads/{thread_id}/reminder`](https://thread.page/help/connections/api/reminders.md#removeThreadReminder) |
| `list_requests` | Edit and organize · Read and add | No | [`GET /requests`](https://thread.page/help/connections/requests.md#listRequests) |
| `get_request` | Edit and organize · Read and add | No | [`GET /requests/{request_id}`](https://thread.page/help/connections/requests.md#getRequest) |
| `claim_request` | Edit and organize · Read and add | Yes | [`POST /requests/{request_id}/claim`](https://thread.page/help/connections/requests.md#claimRequest) |
| `report_request_status` | Edit and organize · Read and add | Yes | [`POST /requests/{request_id}/status`](https://thread.page/help/connections/requests.md#reportRequestStatus) |
| `complete_request` | Edit and organize · Read and add | Yes | [`POST /requests/{request_id}/result`](https://thread.page/help/connections/requests.md#completeRequest) |
| `get_guide` | Edit and organize · Read and add · Read only · Add only · Add to Inbox | No | MCP only |
| `delete_folder_container` | Edit and organize | Yes | MCP only |

Request tools (`list_requests`, `get_request`, `claim_request`, `report_request_status`, `complete_request`) appear for a connection that receives requests.

## Tools change with access

`tools/list` is built from the grant’s scopes:

- Read and add and Edit and organize hold the same scopes, so they list the same tools. A call Read and add may not make is refused with `needs` and `manage_url`. When the owner raises an assistant’s connection to Edit and organize, the same chat works. A `tp_` token can’t use Edit and organize on `/mcp`: raised to it, the token keeps working on the API and stops on `/mcp`.
- Read only lists no write tools, whatever the client asks for.
- A raise that adds scopes, such as Read only to Read and add, needs a new tool list. Claude, Claude Code and Codex read it in a new session. ChatGPT reads it after **Refresh** in its Plugins settings.

A tool hidden by the level, called from an older list, gets the level’s refusal.

If the person asks for something your tools don’t do, call `get_access` and give them its `manage_url`. It opens the connection in ThreadPage with the next level proposed; it changes only the level, and nothing changes until the owner saves and confirms it’s them. The same sentence ends the `get_access` description and every level refusal, because some clients don’t pass the server’s instructions to the model.

## What each level sees: get_access

`get_access` answers the object `GET /connection` answers (`level`, `can`, `cannot`, `manage_url`, `shared_places` and more), plus the grant’s `token_scopes` and the connection’s limits in `capabilities`.

```json
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_access", "arguments": {}}}
```

| `level` | `can` | `cannot` |
|---|---|---|
| `full` | Read, add and edit notes, and organize threads and folders, including Archive and Trash. | Can’t delete permanently. |
| `read_add` | Read and search notes. Add notes, threads and reminders, and change or move to Trash the notes it added. | Can’t change other notes. |
| `read` | Read and search notes, including earlier versions. | No changes. |
| `append` | Add notes to threads you choose and see those threads’ titles. | Can’t read notes. |
| `inbox` | Add notes to your Inbox. | Can’t read notes. |

## Conventions

| Argument | Rule |
|---|---|
| `operation_id` | Every write takes one: a new UUID for each logical write, in any letter case. Send the same one when you repeat that write. The same ID with different arguments answers `operation_id_reused`. |
| `expected_revision`, `context_receipt` | `update_note` and `restore_note_version` take both: the `content_revision` and the `context_receipt` from the same `get_note`. The tools that change an existing thread or folder, and `update_note_properties`, `move_note`, `trash_note` and `restore_note`, take `expected_revision` alone: the `revision` you last read. ThreadPage doesn’t compare it, and these writes apply in the order they arrive. |
| `cursor`, `limit` | Lists take `limit` (1 to 50) and answer `next_cursor`; pass it back as `cursor`. Read every page when the whole list matters. |
| `content` | A Markdown string, `{"text": …}`, `{"markdown": …}` or `{"document": …}`. Markdown up to 32 KiB, a note up to 256 KiB. |

`get_thread` and `search` answer previews, and a `truncated` one is cut. Read a note with `get_note` before you quote, summarize or edit it.

## Replace a note safely

**1.** Read the note in full. Keep its document, its content revision and the context receipt.

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_note",
    "arguments": {
      "note_id": "4c2f8e19-7a6b-4d3e-b1c0-95e8f2a7d364"
    }
  }
}
```

**2.** Change only what you were asked to in that document, and send it whole with a new operation_id. expected_revision is the content_revision you read.

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "update_note",
    "arguments": {
      "operation_id": "b8e1d4a2-6c3f-4e9b-a751-2d0f8c6e3b19",
      "note_id": "4c2f8e19-7a6b-4d3e-b1c0-95e8f2a7d364",
      "expected_revision": "$content_revision",
      "context_receipt": "$context_receipt",
      "content": {
        "document": "$document"
      }
    }
  }
}
```

## Repeat a write whose answer was lost

**1.** Add a note. The answer is lost: a timeout, or the connection dropped.

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "create_note",
    "arguments": {
      "operation_id": "6e2a9c41-0b7d-4f53-8c1e-3a9d5f7b2c80",
      "thread_id": "0e7c4b2a-91d3-4f6e-8a25-3c1b9d7f4e60",
      "content": "## Release 412\n\n- [x] Tests passed\n- [ ] Tag the build"
    }
  }
}
```

**2.** Ask what happened to that operation_id. A receipt means the note exists: don't write again.

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "get_operation",
    "arguments": {
      "operation_id": "6e2a9c41-0b7d-4f53-8c1e-3a9d5f7b2c80"
    }
  }
}
```

**3.** Or send the identical call again with the same operation_id. It answers the same receipt and adds no second note.

```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "create_note",
    "arguments": {
      "operation_id": "6e2a9c41-0b7d-4f53-8c1e-3a9d5f7b2c80",
      "thread_id": "0e7c4b2a-91d3-4f6e-8a25-3c1b9d7f4e60",
      "content": "## Release 412\n\n- [x] Tests passed\n- [ ] Tag the build"
    }
  }
}
```

If the connection was disconnected or reconnected in between, `get_operation` can’t see the earlier write. Don’t replay it: ask the person to check in ThreadPage.

## Confirmations

Moving a folder, or moving it to Trash with or without its contents, asks a confirmation when the folder holds threads:

- A client that supports elicitation shows the question, such as `Move “Clients” and its 4 threads to Trash?`. **Accept** proceeds; any other answer is `declined`.
- Without elicitation, the first call answers `confirmation_required` with `details.confirm`, the folder and how many threads and folders it holds. Send the same call with `confirm` within 10 minutes.

This guards an honest assistant against a mistake. It is not a security control; Trash and **Restore from Trash…** are.

## Errors

A refused call answers a result with `isError: true` and JSON in its text: `{"status": "rejected", "code", "message", "details"}`, without `retryable`. The codes are the same as the API’s; a scope miss is `unavailable`. See [Errors](https://thread.page/help/connections/errors.md).

`rate_limited` means nothing was done: wait `details.retry_after` seconds and send the identical call with the same `operation_id`.

## Limits

MCP calls count toward the same [rate limits](https://thread.page/help/connections/limits.md) as the API.

## Next

- [Assistant guide](https://thread.page/help/connections/agents.md): install the ThreadPage skill.
- [Access levels](https://thread.page/help/connections/access.md)
- [Integration API basics](https://thread.page/help/connections/api.md): the same capabilities for code.
