# MCP server

Connect Claude, Cursor, VS Code or any other MCP client to your Anki collection. The agent can then search your notes, add flashcards, organise decks and tags, and run a review session, with the same rules as the REST API. Every [SDK function](https://ankinize.com/docs/sdk) is a tool.

## Connect

The server is at `https://ankinize.com/mcp`. Most clients only need that address: they open a browser window, you sign in to Ankinize and approve, and the agent is connected. Nothing is copied or pasted.

```
claude mcp add --transport http ankinize https://ankinize.com/mcp
# then, inside Claude Code, run /mcp and choose "Authenticate"
```

Then ask in plain language: *"Make ten cards from this article in my Languages deck"*, or *"What is due today?"*. The agent starts by reading your decks and note types, so it uses the right fields.

## Connect ChatGPT

ChatGPT on the web takes a custom MCP server in a few clicks. It signs in with OAuth, so there is no key to copy.

1. Open [chatgpt.com/plugins](https://chatgpt.com/plugins) (**Plugins** in the sidebar), choose **Add**, then **Add custom MCP server**.
2. **Name**: `Ankinize`. The description is optional.
3. **Connection**: leave **Server URL** selected and paste `https://ankinize.com/mcp`.
4. **Authentication**: leave **OAuth**. Skip **Advanced OAuth settings**: ChatGPT finds the sign-in details and registers itself.
5. Read the risk warning, tick **I understand and want to continue**, then choose **Create as a plugin**.
6. Sign in to Ankinize if asked and approve on the consent screen. Untick `write` there for read-only access.
7. Start a new chat, type `@` and pick Ankinize, then ask, for example *"What is due today?"*.

> ChatGPT cannot use an API key
> 
> OpenAI does not let ChatGPT send a key you made yourself, so use OAuth here. An [API key](https://ankinize.com/docs/authentication) is for the other clients and for scripts.

Your plan or workspace may limit custom MCP servers; if **Add custom MCP server** is missing, ask your workspace admin. If you run Ankinize yourself and the server is not reachable from the internet, choose **Tunnel** instead of **Server URL** (see OpenAI's [Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) guide). After the server's tools change, open the plugin and choose **Refresh**.

## What you approve

MCP clients register themselves with Ankinize the first time, so there is nothing to set up in the dashboard. The approval screen shows the name the client gave itself, where it will send you back to, and what it asks for. Approve only a connection you started.

- **Read and write** is asked for by default. Untick `write` to give **read-only** access: the agent can look at your collection but the tools that change it are not even offered to it.
- The connection works only with this server. It cannot call the REST API and it cannot sync: the `sync` scope is never offered.
- You can disconnect an app at any time under [API keys > Connected apps](https://ankinize.com/dashboard/keys). It stops working at once and has to ask you again to come back.
- Access is renewed automatically while the client stays connected. Each call in the [activity log](https://ankinize.com/dashboard/api-activity) is labelled `MCP · name of the client`.

## Using an API key instead

For a script or a server with no browser, send an [API key](https://ankinize.com/docs/authentication) as a Bearer token. It is the same key the REST API takes, with the same scopes: `read` for looking, `write` for changing. A key that only has `sync` is refused.

```
claude mcp add --transport http ankinize https://ankinize.com/mcp \
  --header "Authorization: Bearer $ANKINIZE_API_KEY"
```

## What the agent can do

One tool per SDK function, named in `snake_case`: `listNotes` is `list_notes`. A tool takes the same arguments as the function and returns the same answer as JSON. The [API reference](https://ankinize.com/docs/api) describes each one. A read-only connection is offered only the tools without *Changes data*.

### Start here

| Tool | What it does | Changes data |
| --- | --- | --- |
| `describe_collection` | Decks, note types and the field keys to use when writing notes. The agent calls this first. |   |

### Decks

| Tool | What it does | Changes data |
| --- | --- | --- |
| `list_decks` | List decks, optionally by name. |   |
| `get_deck` | One deck, with its card counts. |   |
| `create_deck` | Create a deck (Parent::Child nests). | Yes |
| `rename_deck` | Rename or move a deck. | Yes |
| `delete_deck` | Delete a deck and its cards. | Yes |

### Notes

| Tool | What it does | Changes data |
| --- | --- | --- |
| `list_notes` | Search notes with Anki search syntax. |   |
| `get_note` | One note with its fields, tags and cards. |   |
| `create_note` | Add one note. | Yes |
| `update_note` | Change a note's fields, tags or deck. | Yes |
| `delete_note` | Delete a note and its cards. | Yes |
| `create_notes` | Add many notes; split into calls of at most 200. | Yes |
| `update_notes` | Change many notes at once. | Yes |
| `delete_notes` | Delete many notes at once. | Yes |

### Note types

| Tool | What it does | Changes data |
| --- | --- | --- |
| `list_note_types` | List note types with their fields and templates. |   |
| `get_note_type` | One note type in full. |   |
| `create_note_type` | Add a note type. | Yes |
| `update_note_type` | Change fields, templates or styling. Removing a field or template forces a Full Sync on every device. | Yes |
| `delete_note_type` | Delete a note type and its notes. | Yes |

### Cards and review

| Tool | What it does | Changes data |
| --- | --- | --- |
| `list_cards` | Search cards (due, suspended, new, by deck...). |   |
| `get_card` | One card with its scheduling state. |   |
| `render_card` | The question and answer a card shows. |   |
| `get_card_scheduling` | What each of the four answer buttons would do. |   |
| `get_study_queue` | What Anki would show next. |   |
| `answer_card` | Record an answer. The agent should only do this when you really answered. | Yes |
| `update_card` | Move, flag, suspend or bury a card. | Yes |
| `delete_card` | Delete a card. | Yes |
| `update_cards` | Change many cards at once. | Yes |
| `delete_cards` | Delete many cards at once. | Yes |

### Tags

| Tool | What it does | Changes data |
| --- | --- | --- |
| `list_tags` | List tags with their note counts. |   |
| `get_tag` | One tag. |   |
| `create_tag` | Add a tag. | Yes |
| `rename_tag` | Rename a tag everywhere. | Yes |
| `delete_tag` | Remove a tag from every note. | Yes |
| `create_tags` | Add many tags. | Yes |
| `update_tags` | Rename many tags. | Yes |
| `delete_tags` | Remove many tags. | Yes |

### Statistics

| Tool | What it does | Changes data |
| --- | --- | --- |
| `get_stats` | Review counts, retention, intervals and forecasts, for a search. |   |

### Media

| Tool | What it does | Changes data |
| --- | --- | --- |
| `list_media` | List the collection's media files. |   |
| `get_media` | Fetch a file (images are shown to the model; up to 2 MiB). |   |
| `put_media` | Store a file for use in a note (up to 3 MiB). | Yes |
| `delete_media` | Delete a file. | Yes |

The agent is also given a short guide it can read (`ankinize://guide`, `ankinize://guide/search`) and three prompts your client may show as commands: `make_cards`, `quiz_me` and `tidy_up`.

## What is not a tool

The SDK also has helpers that only make sense inside a program: `pipe`, `flow`, `iter`, `indexBy`, `groupBy`, `nest`, the paging streams and the `q` search builders. An agent does not need them. A search is the string in `q` (for example `deck:Languages is:due`), and a list continues with its `cursor`.

## Safety and limits

| Concern | What happens |
| --- | --- |
| Deleting | Delete tools are marked as destructive, so clients ask you before running them. The server's instructions also tell the agent to ask first. |
| Changing a note type | Removing a field or template forces a Full Sync on every device. The call is refused until the agent sends `acceptFullSync: true`, and the account allows it. |
| Retries | Writes take an `idempotencyKey`: the same key does the work once. |
| Rate limits | The REST API's limits apply, per account. A refused call returns `rate_limited` and says how many seconds to wait. |
| Big answers | An answer over 150,000 characters is refused with advice to narrow the search. Media over 2 MiB is not returned and over 3 MiB is not stored through MCP; use the REST API for those. |
| Failures | A failed call comes back as an error the agent can read: `code`, `message` and, where it helps, a `hint`. |

> Stateless
> 
> The server keeps no session. Every request carries its own credential, so it works behind any proxy and needs no reconnecting after a restart. GET and DELETE are answered with 405; there is no server-to-client stream.

## When it does not connect

| You see | Meaning |
| --- | --- |
| `401 unauthorized` | No credential, or an expired one. The `WWW-Authenticate` header points the client to the sign-in; clients that support it start it themselves. With an API key, check it is sent as `Authorization: Bearer ank_…`. |
| `403 forbidden` | The account is suspended or its email is not verified, or the key only has the sync scope. |
| `insufficient_scope` | A write was tried on a read-only connection. Connect again and leave write ticked. |
| 401, not an access token for this server | The client signed in without naming the resource (RFC 8707), which the MCP specification requires. Update the client, or use an API key. |
| ChatGPT cannot connect | Check that the URL ends in `/mcp` and opens from the internet over HTTPS (a server on your own machine does not; use **Tunnel**), and that **Authentication** is **OAuth**. Then try **Refresh** on the plugin. |
| Cannot register | Registration only takes public clients (no secret) that use the authorization code flow with PKCE. A client that needs a secret should use an API key. |

Look at it in the MCP Inspector

```
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP   URL: https://ankinize.com/mcp
```
