> ## Documentation Index
> Fetch the complete documentation index at: https://docs.artifacta.io/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server

> Use Artifacta from Claude Code, Codex, Cursor, Windsurf, Claude Desktop, and any MCP-compatible client.

Your coding agent just generated a build report, a test transcript, a
screenshot, or a 200 MB bundle. Where does it go? Pasted into chat (gets
lost), dumped to `/tmp` (gets cleaned), pushed into a random S3 bucket (no
session view, no metadata, no shareable link). **Artifacta is the artifact
store built for that** — purpose-built for AI agents from the ground up —
and `@artifacta-mcp/mcp` is how your agent talks to it.

The server runs over MCP stdio, so it works in **Claude Code, Codex, Cursor,
Windsurf, Cline, Zed, and Claude Desktop**, plus any other MCP-compatible
host. Once configured, your agent can store files, list past artifacts, mint
private download URLs for its own follow-up calls, and — with consent —
publish public share links, all without leaving the editor.

This guide gets you from zero to first artifact in under five minutes.

<Note>
  **Heads-up for Claude Desktop and Cursor.** Three tools are classified
  destructive — `create_download_link` (public share URLs), `delete_artifact`
  (soft-delete by id), and `seal_session` (irreversible, no unseal) — and are
  hidden from `tools/list` unless you add `--allow-destructive` to the launch
  `args`. See [Claude Desktop & Cursor: destructive
  tools](#claude-desktop--cursor-destructive-tools) for the recommended config
  and [Autonomy boundary](#autonomy-boundary) for the full compliant /
  non-compliant client behavior.
</Note>

## Install

<Note>
  **Recommended for most users: the hosted MCP endpoint.** Connect by URL —
  `https://mcp.artifacta.io/mcp` — and authenticate with a one-time browser login,
  no package install and no API key to copy. See [Connect Claude Code
  (Hosted)](/mcp/install/claude-code-hosted). The local stdio install below remains
  fully supported for CI, restricted networks, and explicit credential control —
  same Artifacta tools, different connection method.
</Note>

The local stdio server is distributed via npm. Most clients launch it with
`npx`, so there is nothing to install globally on your machine.

You will need:

* **Node.js 20 or newer.** The package's `engines` field rejects earlier
  versions before any tool executes.
* An Artifacta API key. Create one in the [dashboard's API keys
  page](https://app.artifacta.io/dashboard/keys). The key shape is
  `ak_live_` followed by 32 alphanumeric characters.

**A note on version pinning.** The snippets below leave `@artifacta-mcp/mcp`
unpinned so `npx` resolves to the latest published release on host restart;
patch and minor fixes (including security patches) roll out without a config
edit. Pin to a specific version — e.g. `@artifacta-mcp/mcp@1.0.0` — only if
you need a frozen install, such as a managed deployment with gated config
rollouts. The current published line is **`1.0.4`** — the stable contract;
no further breaking changes to the existing 13 tools / 4 resources without
v2.

### Claude Desktop

Open Claude Desktop's settings → **Developer** → **Edit Config**, and add an
entry under `mcpServers`. The full file lives at
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS and
`%APPDATA%\Claude\claude_desktop_config.json` on Windows.

```json claude_desktop_config.json theme={null}
{
  "mcpServers": {
    "artifacta": {
      "command": "npx",
      "args": ["-y", "@artifacta-mcp/mcp", "--allow-path", "/Users/you/uploads", "--allow-destructive"],
      "env": {
        "ARTIFACTA_API_KEY": "ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}
```

Claude Desktop does not advertise MCP write confirmations, so the three
destructive tools — `create_download_link` (public share URLs),
`delete_artifact`, and `seal_session` — are hidden unless you add
`--allow-destructive`. See [Autonomy boundary](#autonomy-boundary) for the
matrix and [`create_download_link` consent](#create_download_link-consent)
for how to approve share links in chat.

Restart Claude Desktop. The Artifacta tools appear in the tool palette and
the agent can call them directly.

### Cursor

Add the same block to `~/.cursor/mcp.json` (create the file if it does not
exist):

```json ~/.cursor/mcp.json theme={null}
{
  "mcpServers": {
    "artifacta": {
      "command": "npx",
      "args": ["-y", "@artifacta-mcp/mcp", "--allow-path", "/Users/you/uploads", "--allow-destructive"],
      "env": {
        "ARTIFACTA_API_KEY": "ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  }
}
```

Cursor does not advertise MCP write confirmations either — add
`--allow-destructive` so `create_download_link`, `delete_artifact`, and
`seal_session` appear in the tool list. See [Autonomy
boundary](#autonomy-boundary) and [`create_download_link`
consent](#create_download_link-consent).

Restart Cursor and the Artifacta tools become available in agent mode.

### Claude Code

For a single project, drop a `.mcp.json` at the repo root (commit it so the
whole team picks up the server):

```json .mcp.json theme={null}
{
  "mcpServers": {
    "artifacta": {
      "command": "npx",
      "args": ["-y", "@artifacta-mcp/mcp", "--allow-path", "/Users/you/project", "--allow-destructive"],
      "env": {
        "ARTIFACTA_API_KEY": "${ARTIFACTA_API_KEY}"
      }
    }
  }
}
```

Claude Code expands `${ARTIFACTA_API_KEY}` from your shell environment at
launch, so the literal key stays out of the committed file. For a one-line
equivalent without editing JSON, use the CLI:

```bash theme={null}
claude mcp add artifacta -- npx -y @artifacta-mcp/mcp \
  --allow-path /Users/you/project --allow-destructive
```

Then export `ARTIFACTA_API_KEY` in the shell that launches Claude Code. The
`--allow-destructive` rule from Claude Desktop / Cursor applies here too —
without it `create_download_link`, `delete_artifact`, and `seal_session`
are hidden from the tool list. See [Autonomy
boundary](#autonomy-boundary). For an end-to-end walkthrough of storing and
sharing a build artifact this way, see the [publish from Claude Code use
case](https://artifacta.io/use-cases/publish-from-claude-code).

### Codex

Codex (OpenAI CLI) reads MCP configuration from `~/.codex/config.toml` —
**TOML, not JSON**. Add an `[mcp_servers.artifacta]` table:

```toml ~/.codex/config.toml theme={null}
[mcp_servers.artifacta]
command = "npx"
args = ["-y", "@artifacta-mcp/mcp", "--allow-path", "/Users/you/project", "--allow-destructive"]

[mcp_servers.artifacta.env]
ARTIFACTA_API_KEY = "ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

The section name is `mcp_servers` with an **underscore** — `mcp-servers` or
`mcpservers` is silently ignored. For a project-scoped install, the same
table goes in `.codex/config.toml` at the project root (Codex must trust
the project first). Restart Codex; Artifacta tools appear in the next
session, with the same `--allow-destructive` rule.

### Other MCP clients

Windsurf, Cline, Zed, and Continue all accept the same `mcpServers` JSON
shape as Claude Desktop — drop the Claude Desktop block above into the
client's MCP config file (consult the client's docs for the path) and
restart. The `--allow-path` and `--allow-destructive` flags carry over
unchanged.

### Verifying the install

Run the version flag from any shell:

```bash theme={null}
npx -y @artifacta-mcp/mcp --version
# 1.0.4
```

If that prints `1.0.4` the binary resolved correctly through `npx`.

### Claude Desktop & Cursor: destructive tools

* Three tools are classified **destructive**: `create_download_link` mints
  a **public** `dl.artifacta.io/lnk_…` URL, `delete_artifact` soft-deletes
  an artifact by id, and `seal_session` marks a session as **irreversible**
  (no `unseal`).
* Hosts that advertise `experimental.confirmations` prompt before each call;
  **Claude Desktop and Cursor do not**, so all three tools are hidden from
  `tools/list` by default.
* Add `--allow-destructive` to the launch `args` to expose them; each call
  then emits a one-line stderr audit `[artifacta-mcp] destructive call: <tool>(<args>)` instead of a host confirmation UI. The flag is **never
  read from the environment or `mcp.toml`** — it must be in the launch
  command.
* Approve destructive calls explicitly in chat before the agent runs them.
  For share links, see [`create_download_link`
  consent](#create_download_link-consent); for delete and seal, the same
  principle applies — read the agent's plan, confirm, then proceed.
* If you use `store_artifact.path`, combine `--allow-destructive` with
  `--allow-path` in the same `args` array — see [Path confinement and
  `--allow-path`](#path-confinement-and-allow-path).

## First call: `whoami`

`whoami` is the right starting tool: it confirms authentication, surfaces the
plan tier, and reports current usage so the agent can size subsequent
operations against quota. It is free of side effects and quota-cheap.

A typical agent prompt that exercises it:

> "Confirm we're authenticated against my Artifacta account, then list the
> last 10 artifacts."

The agent will call `whoami`, receive a response shaped like the example
below, then chain `list_artifacts` with `limit: 10`:

```json whoami response theme={null}
{
  "tenant_name": "maya",
  "plan": "free",
  "api_key_last_4": "abcd",
  "usage_storage_bytes": 1048576,
  "plan_storage_limit_bytes": 1073741824,
  "usage_requests_month": 142,
  "plan_requests_limit_month": 10000,
  "active_links": 3,
  "max_active_links": 10,
  "rate_limit_sustained": 100,
  "rate_limit_burst": 200
}
```

The response is the canonical surface — the same payload is also available as
the resource `artifacta://whoami`. The MCP server caches the
`api_key_last_4` so that a later authentication failure can include the key
suffix in the remediation message ("Last-known key suffix: \*\*\*\*abcd.").

## Using Artifacta from your coding agent

Once `whoami` confirms auth, here are the four flows that cover most agent
work. Paste any of these prompts into your coding agent — it will pick the
right tool(s) on its own.

### 1. Save a generated file

> "Save `./out/build_report.html` to session `build_2118` and tag it
> `kind=report, status=green`."

The agent calls `store_artifact` with `path: "./out/build_report.html"`,
`session_id: "build_2118"`, and `metadata: { kind: "report", status: "green" }`,
then returns the new `art_…` id. Re-running the same prompt after a crash
returns the *same* artifact instead of creating a duplicate — the MCP server
auto-injects an `Idempotency-Key` and surfaces it on success as
`_meta.idempotency_key`. Local file paths must clear
[path confinement](#path-confinement-and-allow-path).

### 2. Find prior work

> "Show me the test outputs from session `ci_run_42` tagged `kind=failure`,
> newest first."

The agent calls `list_artifacts` with `session_id: "ci_run_42"` and
`metadata: { kind: "failure" }`. Results come back in
`created_at DESC, artifact_id DESC` order — the API's only sort contract —
with opaque cursor pagination if the list is long.

### 3. Hand a file to a human

> "Publish a 24-hour share link for artifact `art_a1b2c3d4e5f6g7h8`."

The agent calls `create_download_link` with `expires_in: 86400`, then prints
the resulting `https://dl.artifacta.io/lnk_…` URL. On Claude Desktop, Cursor,
Claude Code, and Codex this requires `--allow-destructive` in the launch
`args` — without it the tool is hidden from `tools/list`.
**`get_artifact_download_url` is not a substitute**: that returns a private
1-hour presigned URL for the authenticated caller, not a shareable public
link.

### 4. Resume a session across runs

> "Re-open session `nightly_eval_2026_05_25`, list its artifacts, and append
> today's run output from `./eval/out.jsonl`."

The agent calls `list_artifacts` with the session id, then `store_artifact`
with the same `session_id` to append. Sessions are **user-defined strings
supplied at upload** — there is no separate `create_session` tool, and the
session row appears the first time an artifact is stored against that id.
Subsequent runs just keep writing to the same string.

## Write tools

v0.2 added four write tools and one preview resource so an agent can
produce, persist, and share artifacts end-to-end. They are unchanged in
v1.0.

| Tool                   | What it does                                                                                                                                                                                                                                                                  | Idempotency / retry                                   |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `store_artifact`       | Create an artifact from inline `content` or a local `path`.                                                                                                                                                                                                                   | Auto `Idempotency-Key`; safe to replay.               |
| `request_upload_url`   | Mint a presigned R2 upload URL for files larger than `store_artifact`'s 500 MB ceiling. **Pro-only — Free-tier keys receive `quota_exceeded` with an upgrade URL.**                                                                                                           | **Non-idempotent** — no auto-retry on 5xx.            |
| `complete_upload`      | Finalize a presigned upload into a committed artifact.                                                                                                                                                                                                                        | Idempotent — auto-retries 5xx.                        |
| `create_download_link` | Mint a public `dl.artifacta.io/lnk_…` share link.                                                                                                                                                                                                                             | **Non-idempotent**; consent-gated.                    |
| `publish_artifact`     | Publish an existing artifact as a shareable page at `artifacta.io/a/{slug}`. Re-publishing the same `artifact_id` upserts the page and keeps the URL. Requires `artifact_id` plus optional `title`, `visibility` (`"unlisted"`/`"public"`), `access` (`"none"`/`"password"`). | Idempotent (`writeIdempotent`); safe to retry on 5xx. |
| `unpublish_artifact`   | Remove the public page for an artifact. The artifact itself is not deleted. Calling unpublish on an already-unpublished artifact is a no-op. Requires `artifact_id`.                                                                                                          | Idempotent (`writeIdempotent`); safe to retry on 5xx. |

The companion `artifacta://artifact/{id}/bytes` resource exposes an artifact's
bytes inline for preview. See [Resources](#resources) for the full resource
surface, including the 100 MB cap behavior.

### A note on sessions

`session_id` is a **user-defined string** supplied at upload time
(`store_artifact.session_id`, or carried through `request_upload_url` →
`complete_upload`). There is no separate `create_session` tool — a session row
appears the first time an artifact is stored against that id, and surfaces in
`list_sessions` and `artifacta://session/{session_id}` from that point on.

Sessions can be **sealed** with `seal_session` (v1.0 — irreversible, no
`unseal`). Once sealed, further uploads against that `session_id` are
refused at the API layer with `session_sealed`. `list_sessions` and
`artifacta://session/{session_id}` continue to report `is_sealed` for
read-only visibility. See [Destructive tools](#destructive-tools) for the
full description and the autonomy-boundary gating that protects the call.

### `store_artifact`: `content` vs `path`

`store_artifact` accepts **exactly one** of two inputs:

* **`content`** — the bytes inline, base64-encoded, up to **10 MB** decoded.
  Best for small, in-memory results (a generated report, a JSON blob).
* **`path`** — a path to a local file, streamed to the API as multipart, up to
  **500 MB**. Best for files already on disk. The path must pass
  [path confinement](#path-confinement-and-allow-path). For files larger than
  500 MB, switch to `request_upload_url` → `complete_upload` — note that path
  is **Pro-only**; Free-tier keys receive `quota_exceeded` with an upgrade URL,
  so on Free `store_artifact.path` is the upload ceiling.

See [Save a generated file](#1-save-a-generated-file) above for an end-to-end
prompt example using `path`.

### Path confinement and `--allow-path`

`store_artifact.path` reads files from the local filesystem, so it is guarded by
the path-confinement engine:

* **Default allow-list:** the directory the MCP server was launched in (its
  working directory). A path outside it is refused.
* **Extend it** with one or more `--allow-path=<dir>` launch flags:

```json claude_desktop_config.json theme={null}
{
  "mcpServers": {
    "artifacta": {
      "command": "npx",
      "args": ["-y", "@artifacta-mcp/mcp", "--allow-path", "/Users/you/project/out", "--allow-destructive"],
      "env": { "ARTIFACTA_API_KEY": "ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
    }
  }
}
```

Both `--allow-path` and `--allow-destructive` belong in the same `args`
array when you need local file uploads and public share links.

* **The deny-list always wins,** even inside an allow-listed directory:
  `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/gh`, `~/.kube`, `~/.netrc`,
  `~/.artifacta`, `/etc`, `~/Library/Keychains`, and any `credentials.json` or
  `.env*` file are refused. Symlinks are resolved with `realpath()` before the
  check, so a symlink pointing outside the allow-list (or into the deny-list)
  is refused after resolution. Sockets, FIFOs, and device files are refused.

`--allow-path` only accepts **absolute** paths; a relative value exits at
startup with code 2. The CLI flag is not inferred from a config-file field,
but the server **also** appends roots from the
**`ARTIFACTA_MCP_ALLOW_PATH`** environment variable (colon-separated absolute
paths) when present in the launched server's `env` block — that env var
widens the allow-list at startup the same way `--allow-path` does, so audit
it whenever you review who can read local files through `store_artifact.path`.

### Ambiguous completion: when a write may or may not have happened

`request_upload_url` and `create_download_link` are **non-idempotent** — the API
does not honor an `Idempotency-Key` on them. So when one returns a `5xx` or the
network drops, the MCP server makes **exactly one** attempt and returns
guidance instead of silently retrying:

> The request may or may not have completed. Do not retry blindly — verify
> current state first (e.g. list links / list artifacts) or escalate to a human.

This is deliberate: a blind retry could mint a **second** public download link
or a duplicate upload slot. The correct agent behavior is to **verify or
escalate**, not retry. (`store_artifact` and `complete_upload`, which *are*
idempotent, do retry `5xx` automatically — a replay is safe there.)

### `create_download_link` consent

`create_download_link` produces a **public** URL, so it is classified
*destructive* for consent purposes:

* Clients that advertise `experimental.confirmations` receive
  `requiresConfirmation: true` and prompt the human before the call runs.
* **Claude Desktop and Cursor** do not advertise confirmations. Without
  `--allow-destructive` in the launch `args`, `create_download_link` is
  omitted from `tools/list` entirely — see [Claude Desktop & Cursor: public
  share links](#claude-desktop--cursor-public-share-links) for the recommended
  install config.
* Other clients that do **not** advertise confirmations behave the same:
  the tool stays hidden unless the server was launched with
  `--allow-destructive` (and each such call then emits a one-line stderr
  audit). This prevents an agent from silently leaking a shareable URL.

## Destructive tools

v1.0 adds two destructive tools. They share the same consent surface as
`create_download_link`: filtered from `tools/list` for non-compliant
clients (unless `--allow-destructive` is set), and carrying
`requiresConfirmation` for compliant ones. See [Autonomy
boundary](#autonomy-boundary) for the full matrix.

| Tool              | What it does                                                                                            | Replay behavior                                                                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `delete_artifact` | Soft-delete an artifact by id (`DELETE /v1/artifacts/{artifact_id}`).                                   | A second call on an already-deleted artifact returns `{ artifact_id, deleted: true, already_deleted: true }` rather than an error — safe to retry. |
| `seal_session`    | Mark a session **irreversible** (no `unseal`) so further uploads against that `session_id` are refused. | A re-seal returns the existing `sealed_at` — passthrough at the API.                                                                               |

Both are classified `destructive` and follow the autonomy boundary below.
Neither injects `Idempotency-Key` (the API gates injection to
`POST /v1/artifacts` only); both use the `idempotentWrite` retry policy
(429 once, 5xx up to 3× with jitter) because each operation is naturally
idempotent.

### Example agent prompts

> "Delete artifact `art_a1b2c3d4e5f6g7h8` — it has the wrong session id and
> I am going to re-upload it under `build_2118`."

The agent calls `delete_artifact` with `artifact_id: "art_a1b2c3d4e5f6g7h8"`,
then re-runs `store_artifact` with the right `session_id`. On a compliant
client the host prompts before the delete fires; on Claude Desktop / Cursor
the agent prints its plan and waits for chat confirmation, then dispatches
the call (`--allow-destructive` must be in the launch `args`).

> "Seal session `nightly_eval_2026_05_25` — the run is finished and I don't
> want any further uploads against that id."

The agent calls `seal_session` with `session_id: "nightly_eval_2026_05_25"`.
After the call, `list_sessions` reports `is_sealed: true` and any further
`store_artifact` against that session id receives `session_sealed`.

## Autonomy boundary

The MCP server filters and prompts for tools based on the **client's
declared capabilities** in `initialize`. Three tools are classified
*destructive*: `create_download_link`, `delete_artifact`, `seal_session`.
The matrix:

| Client                                                                                             | Default tool list                                                           | `requiresConfirmation`                                                              | With `--allow-destructive`                                                                                                                                          |
| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Compliant (advertises `experimental.confirmations` — e.g. Claude Code)                             | Destructive tools **present**.                                              | Set to `true` on each destructive tool; the host prompts the human before the call. | No change in surface; the flag is unused (the confirmation UI is already intact).                                                                                   |
| Non-compliant (no `experimental.confirmations` — Claude Desktop, Cursor, Codex, most others today) | Destructive tools **absent** from `tools/list`. The agent cannot call them. | Not applicable.                                                                     | Destructive tools **appear** in `tools/list`. Each call emits a one-line stderr audit `[artifacta-mcp] destructive call: <tool>(<args>)` — no host UI confirmation. |

Other rules that bind the flag:

* `--allow-destructive` is **never read from the environment or
  `mcp.toml`** — it must be in the per-launch CLI args. This prevents a
  leftover env export from silently unlocking destructive tools.
* The `ARTIFACTA_MCP_REQUIRE_WRITE_CONFIRM=1` env override promotes
  `requiresConfirmation` on the four **write** tools (`store_artifact`,
  `request_upload_url`, `complete_upload`, `create_download_link`) for
  compliant clients that want a stricter surface. It does **not** affect
  destructive tools — those always require confirmation on compliant
  clients.
* On compliant clients, `create_download_link` is destructive too — the
  host prompts before each share-link mint. The same one-line stderr
  audit still emits when `--allow-destructive` is the only reason a tool
  was exposed.

## Security model

A consolidated reference for the four security mechanisms the MCP server
relies on.

### 1. Path confinement

Applies to: `store_artifact.path` (the only tool that reads local files).

* Default allow-list = server CWD. Extend with `--allow-path=<absolute-dir>`
  (relative paths exit at startup with code 2; the flag is never read from
  the environment).
* Deny-list always wins: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/gh`,
  `~/.kube`, `~/.netrc`, `~/.artifacta`, `/etc`, `~/Library/Keychains`, any
  `credentials.json`, any `.env*`.
* Symlinks are resolved via `realpath()` before allow-list and deny-list
  checks. Special files (sockets, FIFOs, devices) are refused.
* 500 MB ceiling per file, regardless of plan tier. >500 MB → use
  `request_upload_url` → `complete_upload`.

See [Path confinement and `--allow-path`](#path-confinement-and-allow-path)
for configuration examples.

### 2. `Idempotency-Key` auto-injection

* `POST /v1/artifacts` (the `store_artifact` endpoint) — **the only
  endpoint that auto-injects an Idempotency-Key**. The MCP server attaches
  `mcp_<uuid4>` unless the caller passes their own `idempotency_key`. The
  effective key is surfaced on success as `_meta.idempotency_key`.
* Every other write endpoint — `request_upload_url`, `complete_upload`,
  `create_download_link`, `delete_artifact`, `seal_session` — does **not**
  inject. `complete_upload`, `delete_artifact`, and `seal_session` are
  naturally idempotent at the API layer (replays return the existing
  record). `request_upload_url` and `create_download_link` are
  non-idempotent; see ambiguous-completion below.

### 3. Retry policy

Per plan §6.1, retries are tied to backend idempotency:

| Tool                                                                                     | Retry policy         | 5xx auto-retry?                                      | Idempotency-Key injected? |
| ---------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------- | ------------------------- |
| `whoami`, `list_artifacts`, `get_artifact`, `get_artifact_download_url`, `list_sessions` | `safe`               | Yes (3× w/ jitter)                                   | n/a (GET)                 |
| `store_artifact`                                                                         | `idempotentWrite`    | Yes (3× w/ jitter)                                   | Yes                       |
| `request_upload_url`                                                                     | `nonIdempotentWrite` | **No** (1 call, then §6.1 ambiguous-completion text) | No                        |
| `complete_upload`                                                                        | `idempotentWrite`    | Yes (3× w/ jitter)                                   | No (natural idempotency)  |
| `create_download_link`                                                                   | `nonIdempotentWrite` | **No** (1 call, then §6.1 ambiguous-completion text) | No                        |
| `delete_artifact`, `seal_session`                                                        | `idempotentWrite`    | Yes (3× w/ jitter)                                   | No (natural idempotency)  |
| `publish_artifact`, `unpublish_artifact`                                                 | `idempotentWrite`    | Yes (3× w/ jitter)                                   | No (natural idempotency)  |

429 across the board: retried once with the `Retry-After` header value if
present, otherwise jittered backoff. The 3-consecutive-failure outage
notifier writes a single `Artifacta API unreachable…` line to stderr after
the third sequential transport-level failure and resets on the next
success.

### 4. Destructive-tool gating

See [Autonomy boundary](#autonomy-boundary). One-paragraph summary: tools
classified `destructive` (`create_download_link`, `delete_artifact`,
`seal_session`) are hidden from non-compliant clients unless the
per-launch `--allow-destructive` flag is set, and carry
`requiresConfirmation: true` for compliant clients. Each call emits a
one-line stderr audit when the flag is the reason the tool was exposed
without a confirmation surface.

## Resources

In addition to tools, the MCP server exposes four resource URIs. Hosts that
prefer the resource model — or want to surface Artifacta data in a side panel
without firing a tool call — can read these directly.

| URI                                        | Returns                                               | Notes                                                                                         |
| ------------------------------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `artifacta://whoami`                       | Same payload as the `whoami` tool.                    | Cheap; safe to poll.                                                                          |
| `artifacta://artifact/{artifact_id}`       | Same metadata body as `get_artifact`.                 | Tenant-internal fields (`tenant_id`, `deleted_at`) are stripped at the MCP boundary.          |
| `artifacta://artifact/{artifact_id}/bytes` | Inline bytes (text or blob, routed by content type).  | Hard-capped at **100 MB** — see [size cap](#bytes-size-cap) below.                            |
| `artifacta://session/{session_id}`         | Aggregate view of a session's artifacts + seal state. | Read-only; sessions cannot be sealed in v0.2 (see [A note on sessions](#a-note-on-sessions)). |

### `…/bytes` size cap

The bytes resource is hard-capped at **100 MB**. Exactly 100 MB inline is
allowed; 100 MB + 1 byte is refused. Oversize requests fail at the MCP
`resources/read` layer with an **`InvalidRequest`** error — *not* a tool error
envelope — whose message steers the agent to `get_artifact_download_url`
(which mints a private 1-hour presigned URL for the authenticated caller; use
`create_download_link` only if a *public, shareable* URL is required).

Two refusal variants exist, both returning the same `InvalidRequest` class:

* **Metadata gate** — caught from the artifact's recorded size with a single
  `get_artifact` call, so an over-cap fetch never touches R2.
* **R2 oversize gate** — backstop in case the metadata size under-reports the
  actual blob size on disk.

Agents should branch on the `InvalidRequest` error class and reissue the read
as a `get_artifact_download_url` tool call; the resource cannot be widened with
a flag.

## Troubleshooting

### `unauthorized` on every call

The MCP server surfaces an `unauthorized` error when the API rejects the
configured key. Common causes:

1. **`ARTIFACTA_API_KEY` is not set in the MCP host's environment.** Editing
   your shell's `.zshrc` does not propagate to Claude Desktop or Cursor —
   they read the `env` block from your `mcpServers` config. Put the key
   directly in `claude_desktop_config.json` or `~/.cursor/mcp.json` and
   restart the host.
2. **The key was rotated.** The remediation message includes the
   last-known key suffix (`Last-known key suffix: ****abcd`). If that suffix
   does not match the key you currently have in the dashboard, regenerate or
   paste the new key into the host config.
3. **The key is malformed.** Keys must match `ak_live_` plus 32
   alphanumeric characters. The server exits at startup with code 2 if the
   shape check fails — check the host's stderr log for the rejection message.
4. **The tenant is in deletion grace period.** The server replaces the
   generic remediation with `"Account is scheduled for deletion — see
   https://app.artifacta.io/dashboard/account."` Restore the account from
   the dashboard before the grace period ends.

If none of those apply, capture the failing response — every error result
carries a `request_id` in `_meta.request_id` — and contact support with that
id.

### Destructive tools missing from `tools/list`

If `create_download_link`, `delete_artifact`, or `seal_session` is missing
while every other Artifacta tool appears, you are likely on **Claude
Desktop, Cursor, or another non-compliant client** without
`--allow-destructive` in the launch `args`. Those hosts do not implement
MCP write confirmations, so the server hides destructive-classified tools
by default. See [Autonomy boundary](#autonomy-boundary) for the matrix.

For `create_download_link` specifically: `get_artifact_download_url` is
**not** a substitute — it returns a time-limited presigned URL for the
authenticated caller, not a public share link. Add `"--allow-destructive"`
to the `args` array (alongside `--allow-path` if you use local file
uploads), restart the host, and confirm the tool appears. See [Claude
Desktop & Cursor: destructive
tools](#claude-desktop--cursor-destructive-tools).

### `tools/list` shows nothing

Make sure the host launched the server. Common causes:

* `npx` could not resolve `@artifacta-mcp/mcp` (no network, expired npm cache).
  Run `npx -y @artifacta-mcp/mcp --version` from a shell to reproduce.
* The host is running on Node 18 or older. The package's `engines.node` is
  `>=20.0.0`; the npm install step refuses to run on older Node.

### Path arguments are refused even though the file exists

The local path-confinement engine refuses any path outside the server's
allow-list (default: the host's working directory) and refuses paths inside
the built-in deny-list (`~/.ssh`, `~/.aws`, `~/.config/gh`, `/etc`, …) even
when the allow-list is widened. Pass `--allow-path=/your/dir` at launch to
extend the allow-list, but the deny-list always wins.

This guards the `store_artifact` `path` argument (v0.2). See [Path confinement
and `--allow-path`](#path-confinement-and-allow-path) for the full deny-list and
configuration examples.

## What's next

* **[Anthropic MCP introduction](https://modelcontextprotocol.io/introduction)**
  — protocol fundamentals if you are new to MCP.
* **[MCP Server Leaderboard](https://artifacta.io/mcp-leaderboard)** — see
  how Artifacta ranks among other MCP servers agents connect to.
