The Viktor MCP server lets any MCP-compatible agent delegate work to Viktor. It exposes the same threads, runs, results, and files as the [Viktor Public API](/docs/public-api), with two agent-friendly tools that can start a task and wait for its result in one call.

- **Server URL:** `https://api.viktor.com/mcp`
- **Transport:** Streamable HTTP
- **Authentication:** Viktor API key on every request
- **Session model:** Stateless

## Connect

### 1. Create a scoped API key

Open [Settings → API Keys](https://app.viktor.com/settings/api-keys) and generate a personal or team key. For the simplest `ask_viktor` workflow, grant:

- `threads:create`
- `runs:create`
- `runs:read`

Add other scopes only when the MCP client needs the corresponding tools. The server advertises only tools allowed by the key's current scopes.

### 2. Add the remote server to your MCP client

Every client needs the same two pieces of information: the server URL and an authentication header. Either header style works:

- `Authorization: Bearer <VIKTOR_API_KEY>`
- `x-api-key: <VIKTOR_API_KEY>`

**Claude Code**

```bash
claude mcp add --transport http viktor https://api.viktor.com/mcp \
  --header "Authorization: Bearer $VIKTOR_API_KEY"
```

**Cursor**

Add the server to `~/.cursor/mcp.json` (or the project's `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "viktor": {
      "url": "https://api.viktor.com/mcp",
      "headers": {
        "Authorization": "Bearer <VIKTOR_API_KEY>"
      }
    }
  }
}
```

**Other clients**

Most MCP clients accept the same `mcpServers` JSON shape as the Cursor example, or offer a settings screen where you enter the URL and header. If your client asks for a transport, choose **Streamable HTTP**, not the legacy SSE transport.

Use your client's secret or environment-variable support instead of saving a live key directly in a shared configuration file.

### 3. Verify the connection

Call `whoami`. It requires no scope and returns the key type, granted scopes, and active rate limits. If other tools are missing, update the key's scopes in the Viktor dashboard and refresh the client's tool list.

## Recommended workflow

For a one-off task, call `ask_viktor` with a message:

```json
{
  "message": "Analyze this month's revenue and summarize the biggest changes.",
  "speed": "smarter",
  "timeout_seconds": 120,
  "idempotency_key": "monthly-revenue-2026-07"
}
```

`ask_viktor` creates a thread, starts a run, waits up to `timeout_seconds`, and returns the result when it is ready. Results can contain Markdown, structured JSON, and file artifacts.

If the wait ends first, the response includes `wait_timed_out: true` and the `run_id`. Call `wait_for_run` with that ID. Do not call `ask_viktor` again, because that can start duplicate work unless the same idempotency key is reused.

For long-running or interactive workflows:

1. Call `create_thread` to queue work without waiting.
2. Call `wait_for_run` or `get_run` with the returned run ID.
3. Call `send_message` on the same thread to continue the conversation with its history.
4. Use `get_file_download_url` for any artifact IDs returned in the result.

## Tool reference

| Tool | What it does | Required scopes |
| --- | --- | --- |
| `ask_viktor` | Start a new thread and wait for its result | `threads:create`, `runs:create`, `runs:read` |
| `create_thread` | Start a new thread and queue a run without waiting | `threads:create`, `runs:create` |
| `send_message` | Continue an existing thread and queue a run | `messages:create`, `runs:create` |
| `wait_for_run` | Wait for a run to finish and return its result | `runs:read` |
| `get_run` | Read a run's current status | `runs:read` |
| `get_run_result` | Fetch a terminal run's result | `runs:read` |
| `cancel_run` | Request cancellation of an in-flight run | `runs:create` |
| `list_threads` | List the key's threads, newest first | `threads:read` |
| `get_thread` | Get a thread's status | `threads:read` |
| `list_messages` | List user-visible messages in a thread | `messages:read` |
| `list_runs` | List a thread's runs, newest first | `runs:read` |
| `get_file_download_url` | Exchange an artifact token for a short-lived URL | `files:read` |
| `whoami` | Identify the key, scopes, and rate limits | None |

## Structured output

`ask_viktor`, `create_thread`, and `send_message` accept the same `response_format` JSON Schema as the REST API. When supplied, the completed result includes a validated `json` value. See the [Public API guide](/docs/public-api).

## Timeouts, progress, and retries

`ask_viktor` and `wait_for_run` perform bounded server-side polling. Clients that send an MCP progress token receive best-effort progress notifications while waiting.

Every tool call is rate limited against the same policy as its REST equivalent. If a wait times out, call `wait_for_run` again with the same run ID. Use `idempotency_key` whenever a tool starts a run so reconnects and retries cannot duplicate work.

## Errors

Authentication failures are plain HTTP `401` or `403` responses so MCP clients can recognize credential problems. Tool failures are machine-readable JSON strings with an `error` and `message`, and may include `http_status`, `scope`, or rate-limit details.

Common fixes:

- **No tools appear:** verify the key with `whoami`, then grant the required scopes.
- **`insufficient_scope`:** add the named scope to the key or choose a different tool.
- **`wait_timed_out`:** call `wait_for_run` again with the returned run ID.
- **`thread_busy`:** wait for the active run before sending another message to that thread.
- **`rate_limit_exceeded`:** wait for the retry interval before calling again.