> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.morphiaone.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.morphiaone.ai/_mcp/server.

# Connect an AI agent

Two ways in, both on the public API, so scopes, rate limits, credits and test mode
apply as they do to any key:

| You use                                                                    | Connect with       | What it does                                           |
| -------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------ |
| An MCP client that can send a bearer header (Claude Code, Cursor, VS Code) | The **MCP server** | Eleven task-shaped tools that answer compact views     |
| An agent that runs shell commands                                          | The **skill**      | Drives the `morphiaone` CLI with its `--compact` views |

Use a **test-mode key** while trying it: jobs run end to end, return fixture results and
charge nothing. Start with `digitisation.read` and `digitisation.write`: the agent
proposes candidates, and a person reviews and stages them in the Platform.

Only grant `digitisation.review` if the agent may make review decisions. Grant
`catalogue.read` and `catalogue.write` together for `import_shades`, which creates or
revises known colours. `stage_candidate` needs `catalogue.write`; `find_variant`
needs `catalogue.read`. `measure_regions` requires both `digitisation.write` and
`digitisation.read`, including with `wait: false`: its tool contract covers queuing
and polling. Scopes do not imply each other.

## MCP server

Streamable HTTP at `/mcp`. Send your API key as a bearer token; without one the server
answers `401`. It keeps no key and no state between calls.

### Sign in instead of pasting a key

A client that connects through OAuth, such as a Claude.ai or Claude Desktop custom
connector, signs you in with your MorphiaOne Platform account instead. The server
publishes where to sign in at `/.well-known/oauth-protected-resource`, and its `401`
names that document, so the client finds it alone: add the connector by its URL and
sign in when asked.

The agent then acts as you, within your role in the account:

* A viewer's agent reads; a developer's drafts; an owner's or admin's may also review.
* Approving and publishing a catalogue stay in the console, for a person, whatever the
  role.
* If your account requires a person to review, the agent's decisions are refused as
  a key's are; it gives you the candidate's review link instead.
* Removing you from the account stops the agent's next call.

From your own code, pass the token where you would pass a key: `access_token=` in
Python, `accessToken` in TypeScript (one or the other, never both).

> **Note**
>
> Sign-in is being rolled out. Until the hosted endpoint offers it, use an API key.

> **Note**
>
> The hosted MCP endpoint is not public yet. Until it is, ask for its URL when you
> request access, and use it where the snippets say `MCP_URL`.

#### Claude Code

Create `.mcp.json` with literal environment references:

```json
{
  "mcpServers": {
    "morphiaone": {
      "type": "http",
      "url": "${MCP_URL}",
      "headers": { "Authorization": "Bearer ${MORPHIAONE_API_KEY}" }
    }
  }
}
```

Set `MCP_URL` and `MORPHIAONE_API_KEY` in the environment that launches Claude Code.
The client expands these references when connecting. Do not expand the key in a
shell command or save its value in the configuration. In a real client session,
check `/mcp` to confirm the connection before starting a job.
See Claude Code's [environment-variable configuration](https://code.claude.com/docs/en/mcp#environment-variable-expansion-in-mcp-json).

#### Cursor

`~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "morphiaone": {
      "url": "MCP_URL",
      "headers": { "Authorization": "Bearer ${env:MORPHIAONE_API_KEY}" }
    }
  }
}
```

#### VS Code

`.vscode/mcp.json`:

```json
{
  "inputs": [
    { "type": "promptString", "id": "morphiaone-key", "description": "MorphiaOne API key", "password": true }
  ],
  "servers": {
    "morphiaone": {
      "type": "http",
      "url": "MCP_URL",
      "headers": { "Authorization": "Bearer ${input:morphiaone-key}" }
    }
  }
}
```

### Local server

`morphiaone-mcp` runs the same tools over stdio on your machine. It reads the key from
`MORPHIAONE_API_KEY` and can upload a local photo by `path`. It needs no hosted URL and
no OAuth, so Claude Desktop can use it too. Until the package is published, install it
from a checkout: `pip install -e sdk/python -e mcp-server`.

The CLI writes the entry for you, keeping your other servers and backing up the file it
changes:

```sh
morphiaone mcp install --client claude-code   # or cursor, claude-desktop; --print to preview
```

It runs `morphiaone-mcp` from your `PATH` if it finds it, otherwise `uvx morphiaone-mcp`.
These are the entries it writes:

#### Claude Code (local)

`.mcp.json`:

```json
{
  "mcpServers": {
    "morphiaone": {
      "type": "stdio",
      "command": "morphiaone-mcp",
      "env": { "MORPHIAONE_API_KEY": "${MORPHIAONE_API_KEY}" }
    }
  }
}
```

#### Cursor (local)

`~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "morphiaone": {
      "command": "morphiaone-mcp",
      "env": { "MORPHIAONE_API_KEY": "${env:MORPHIAONE_API_KEY}" }
    }
  }
}
```

#### Claude Desktop (local)

`claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "morphiaone": {
      "command": "morphiaone-mcp",
      "env": { "MORPHIAONE_API_KEY": "<your test-mode or proposal-only key>" }
    }
  }
}
```

Claude Desktop doesn't expand environment references, so the key is stored in this
file as written. Use a test-mode key, or a live key with only `digitisation.read` and
`digitisation.write`, and revoke it when you stop using it.

`MORPHIAONE_BASE_URL` defaults to `https://api.morphiaone.ai`.

### Tools

| Tool                                                                   | Kind        | Does                                                                                                                                 |
| ---------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `start_upload`                                                         | write       | Starts a single-use link the person sends one photo through, with no API key                                                         |
| `get_upload`                                                           | read        | Reads an upload link: `pending`, `stored` with its `asset_id`, or `expired`                                                          |
| `retain_evidence`                                                      | write       | Keeps a photo as evidence: a local file (`path`, local server only), an uploaded asset (`asset_id`) or a public `https` link (`url`) |
| `measure_regions`                                                      | write       | Measures up to four regions of an image and waits for the result, sending progress notifications while it waits                      |
| `make_candidate`                                                       | write       | Freezes one measured region for review                                                                                               |
| `edit_candidate`                                                       | write       | Corrects a field, deriving a new pending candidate                                                                                   |
| `review_candidate`                                                     | destructive | Accepts or rejects a candidate, bound to its digest                                                                                  |
| `stage_candidate`                                                      | destructive | Stages an accepted candidate as a catalogue draft                                                                                    |
| `import_shades`                                                        | destructive | Creates or revises variants from known colours, row by row                                                                           |
| `list_review_queue`, `get_assessment`, `get_candidate`, `find_variant` | read        | Browse                                                                                                                               |

`retain_evidence` takes exactly one of `path`, `asset_id` or `url`. Only a
[local server](#local-server) reads `path`, and only a `.jpg`, `.png` or `.webp` file
whose bytes are that image; anything else is refused before it leaves your machine. A
deployment that fetches no links answers a `url` with `400 INVALID_REQUEST` and
`remote_input_disabled`.

With a hosted server the agent never holds the photo, so it asks the person for it:
`start_upload` answers an `upload_url` to send them. They open it to choose the photo,
or run `curl -T photo.jpg "<upload_url>"`; it needs no API key, works once and expires
after 15 minutes. The agent reads `get_upload` until it is `stored`, then passes its
`asset_id` to `retain_evidence`. The link is shown only when the session starts, so
start a new one rather than retrying. The account's key network allowlist does not
apply to the person's send: the link itself is the permission. You can also upload
the photo yourself with the SDK or `morphiaone digitisation retain --file` and hand
the agent its evidence or asset id.

`tools/list` shows only tools for which your key has **every** required scope. A key
with both read scopes sees four read tools. If the key cannot be checked, discovery
fails instead of advertising unrestricted tools. Each tool call checks scopes again,
so a client cannot bypass this check by calling a hidden tool and scope changes take
effect on the next call. The gateway still authorizes each API operation.
`GET /v1/api-key` (`client.whoami()`, `morphiaone whoami`) answers the key's scopes.

Each tool answers the fields a reviewer needs and the digest the next call takes. Pass
`raw: true` for the full response with its audit digests. Writes derive their
`Idempotency-Key` from their arguments, so an agent's retry replays the first answer,
including a failed assessment. To measure the same regions again on purpose, pass
`attempt: 1` (then 2, and so on) to `measure_regions`.

The server tells the agent to show the user each measured colour and to accept only on
an explicit yes. That is guidance to the agent, not proof that a person approved.

Every decision records who made it: a candidate's `reviewed_by` and a stage's
`reviewed_by` and `staged_by` are `{"type": "user"}` for a member in the Platform or
`{"type": "api_key"}` for a key. When the review must be a person's, an owner or admin
turns on **Settings → Review policy** in the Platform. Then the server enforces it for
every key of the account, whatever its scopes:

* `accept` and `reject` answer `403` [`HUMAN_REVIEW_REQUIRED`](/api-reference/errors#HUMAN_REVIEW_REQUIRED).
* `stage` answers the same unless a member accepted the candidate.
* The candidate's `review_url` opens it in the Platform. The agent gives that link to
  the user and waits; once a member accepts, the agent may stage it.

Each key decision and each refusal is a row in the account's audit log. A key without
`digitisation.review` and `catalogue.write` is still the narrowest grant: it can retain,
measure and propose candidates and nothing more. The policy does not cover generic
`import_shades`, which copies a known hex into a draft with `catalogue.write` and no
candidate; do not grant that scope to an agent whose colours a person must approve.
Staging is not publishing: a staged draft renders nothing until someone approves and
publishes it in the Platform.

## Skill

For agents that run shell commands (Claude Code, Claude Agent SDK), the
`morphiaone-digitisation` skill ships in the SDK repository under
`sdk/skills/morphiaone-digitisation/`. Copy that folder into `.claude/skills/` (one
project) or `~/.claude/skills/` (every project). It drives `pip install morphiaone` and
reads the key only from `MORPHIAONE_API_KEY`.

The agent commands (`whoami`, `--compact`) need the current SDK; upgrade an older install.

### Qualify locally before hosting

Claude Code and Cursor can connect to the local Streamable HTTP service; a public
hostname is not required for a real-client test. Use a test-mode key, retain a local
photo with `morphiaone digitisation retain --file`, and give its evidence id to the
agent. Verify measurement, candidate creation, human Platform review and staging.
Check retries, missing scopes and revocation with separate test keys. Record client
and package versions and request ids, never credential values.

Public hosting needs a separate authentication review: the current static API-key
adapter forwards the caller's key to the public API. It is not an OAuth deployment.
OAuth must validate tokens issued for the MCP endpoint and use separately authorized
upstream credentials. Keep URL fetching disabled until its deployment gates pass;
local-file retention works without arbitrary URL fetching.

Building your own agent on the SDK? Name it with `app_info="my-agent/1.0"` (`appInfo` in
TypeScript): it goes first in the `User-Agent` the SDK sends.

## The order of calls

Both follow [Digitise and import shades](/guides/digitise-shades): retain, measure,
make a candidate, review, stage.