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

# MCP Server

> Connect AI clients (Claude, Cursor, VS Code) to Zuko CRM data via the OAuth 2.1 MCP server.

Zuko exposes a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server so AI clients can read and write CRM data on behalf of a logged-in user. Authentication uses **OAuth 2.1** with RFC 9728 protected-resource metadata — clients discover auth endpoints automatically.

## Endpoint

```
POST {BACKEND_URL}/api/mcp
```

Only `POST` is accepted. `GET` and `DELETE` return `405` — the server is stateless with no sessions to manage.

## OAuth flow

1. Client hits `/api/mcp` without a token → receives `401` with `WWW-Authenticate` pointing to `/.well-known/oauth-protected-resource/api/mcp`
2. Client fetches protected-resource metadata → discovers the authorization server at `{BACKEND_URL}/auth`
3. Client fetches `/.well-known/oauth-authorization-server/auth` → gets authorize URL, token URL, JWKS URI, and supported scopes
4. User completes the authorization code flow and approves scopes on the consent screen
5. Client exchanges the code for a JWT access token (audience = `{BACKEND_URL}/api/mcp`)
6. Client sends `Authorization: Bearer <token>` on every MCP request

Tokens are JWTs verified locally against `{BACKEND_URL}/auth/jwks` — no round-trip to the auth server per request.

## Scopes

| Scope                | Grants                                 |
| -------------------- | -------------------------------------- |
| `organizations:read` | List organizations the user belongs to |
| `tasks:read`         | List and retrieve tasks                |
| `tasks:write`        | Create and update tasks                |

## Tools

### `list_organizations`

**Scope:** `organizations:read`

Returns the organizations the authorized user belongs to. Call this first when the user belongs to multiple organizations to get the correct `organizationId` for task tools.

**Input:** *(none)*

**Output:**

```json theme={null}
[{ "id": 1, "name": "Acme", "slug": "acme", "role": "owner" }]
```

***

### `list_tasks`

**Scope:** `tasks:read`

Returns up to 50 tasks (most recently updated) across all organizations the user belongs to.

| Input            | Type                                               | Description                             |
| ---------------- | -------------------------------------------------- | --------------------------------------- |
| `status`         | `"TODO" \| "IN_PROGRESS" \| "DONE" \| "CANCELLED"` | Filter by status (optional)             |
| `organizationId` | `integer`                                          | Restrict to one organization (optional) |

***

### `get_task`

**Scope:** `tasks:read`

Returns full task detail including owners, subtasks, and parent task.

| Input    | Type      | Description                |
| -------- | --------- | -------------------------- |
| `taskId` | `integer` | ID of the task to retrieve |

***

### `create_task`

**Scope:** `tasks:write`

Creates a task. If the user belongs to exactly one organization, `organizationId` can be omitted — call `list_organizations` first if they belong to multiple.

| Input            | Type      | Description                                            |
| ---------------- | --------- | ------------------------------------------------------ |
| `title`          | `string`  | Task title (required)                                  |
| `organizationId` | `integer` | Target organization (optional if user has exactly one) |
| `description`    | `any`     | Description in JSON format (optional)                  |
| `status`         | enum      | Initial status — defaults to `"TODO"` (optional)       |
| `assignee`       | `string`  | Assignee identifier (optional)                         |

***

### `update_task`

**Scope:** `tasks:write`

Updates one or more fields on an existing task. Only supplied fields are changed.

| Input         | Type      | Description                                        |
| ------------- | --------- | -------------------------------------------------- |
| `taskId`      | `integer` | ID of the task to update (required)                |
| `title`       | `string`  | New title (optional)                               |
| `description` | `any`     | New description in JSON format (optional)          |
| `status`      | enum      | New status (optional)                              |
| `assignee`    | `string`  | New assignee (optional)                            |
| `completedAt` | `string`  | Completion timestamp in ISO 8601 format (optional) |

## Connecting a client

### Claude Desktop

Add to `claude_desktop_config.json`:

```json theme={null}
{
  "mcpServers": {
    "zuko": {
      "type": "http",
      "url": "https://your-backend.example.com/api/mcp"
    }
  }
}
```

### Claude Code

```bash theme={null}
claude mcp add --transport http zuko https://your-backend.example.com/api/mcp
```

### Cursor / VS Code

Add a remote MCP server pointing to `{BACKEND_URL}/api/mcp`. The client handles OAuth discovery and the login flow on first use.

## Environment variables

| Variable          | Default                 | Description                                                    |
| ----------------- | ----------------------- | -------------------------------------------------------------- |
| `BACKEND_URL`     | `http://localhost:3001` | Public backend URL — used in OAuth metadata responses          |
| `BETTER_AUTH_URL` | `http://localhost:8000` | Auth server base URL — used by the bearer guard to verify JWTs |
