# Connect MCP

> Let an agent call the tools of products you are entitled to. Give your client the server URL and sign in with your Gujo account.

## Endpoints and authentication

| Server | Endpoint | Authentication |
|---|---|---|
| Customer MCP | `https://api.gujo.ai/api/mcp` | Sign in (OAuth). For CI and scripts, an account API key with the `mcp.read` ability |
| Design MCP | `https://design.gujo.ai/mcp` | Design agent token with the `read` role |

Both servers take JSON-RPC 2.0 over HTTP POST. Always use `api.gujo.ai` and `design.gujo.ai`. Calls to `gujo.ai/api/...` lose the MCP headers, and tokens you get by signing in only work at the `api.gujo.ai` URL.

This page covers the customer MCP. The design MCP is described in [Design Gallery](../design.md).

## Connect by signing in

Give your client just the server URL. You don't need to create a key or set an environment variable.

1. Pick your client below and press the install button, or add the command or config.
2. The first time the client calls the server, it opens your browser at the account.gujo.ai sign-in page. If you are already signed in, you go straight to the consent screen.
3. On the consent screen, check the app's name, its address, the server it connects to and the permissions it gets, then approve.
4. The client receives and keeps the token, and from then on refreshes it on its own when it expires.

The client finds where to sign in by itself, from the standard metadata the server publishes (`/.well-known/oauth-protected-resource/api/mcp`). It also registers itself as an app.

### Permissions

Permissions on the consent screen have the same names as [account API key abilities](../auth.md) and are checked exactly the same way.

- The default permissions are `mcp.read` (connect over MCP) and `products.read` (read your library and products). They are always included when you approve.
- If the app asks for more, such as `orders.read`, `subscriptions.read` or `device.install`, those appear unchecked. Only the ones you check are granted.
- The tool list shows only the tools the granted permissions allow. If the app calls a tool it lacks permission for, the server answers 403 and names the missing permission, and the app can ask for consent again with it.
- Operator abilities cannot be requested.

### Token lifetimes and disconnecting

- An access token lasts one hour, and the client gets a new one with its refresh token. Both tokens change on every refresh.
- A refresh token expires after 30 days without use. A connection ends 90 days after you approved it, and you sign in once more then.
- Apps you connected by signing in are listed under **Connected apps** in your [account security settings](https://account.gujo.ai/settings/security). You can see each app's name, permissions and last use, and **Disconnect** revokes all of that app's tokens.
- An account can have up to 20 active connections. When it is full, disconnect the ones you no longer use.
- Tokens you get by signing in work only for MCP. Calling other account API endpoints (such as `/api/products`) with them returns 401.

## Connect with a key (CI, scripts)

Where nobody can sign in through a browser (CI, scripts on a server, headless agents), use an account API key. Expand **Connect with a key** under each client below for the config.

No config contains a key. They only point at an environment variable (`GUJO_API_KEY`, `GUJO_DESIGN_TOKEN`) or the client's password prompt. Create a key with the `mcp.read` ability as described in [Account API keys](../auth.md), and put the value shown once at creation into a shell environment variable.

```bash
export GUJO_API_KEY="your key"
```

## Connect a client

### Customer MCP

The endpoint is `https://api.gujo.ai/api/mcp`. Give your client just this URL: the first time it connects, it opens your browser so you can sign in to your Gujo account and approve its access.

#### Cursor

[Install in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=gujo&config=eyJ1cmwiOiJodHRwczovL2FwaS5ndWpvLmFpL2FwaS9tY3AifQ%3D%3D)

The install button adds the server to Cursor. The first time it connects, Cursor opens your browser to sign in and approve access. Without the button, put the following in the config file.

File: `~/.cursor/mcp.json`

```json
{
    "mcpServers": {
        "gujo": {
            "url": "https://api.gujo.ai/api/mcp"
        }
    }
}
```

**Connect with a key (CI, scripts)**

Cursor reads the key from the environment variable `GUJO_API_KEY`, so start Cursor from a shell that has it. Put the following in the config file.

File: `~/.cursor/mcp.json`

```json
{
    "mcpServers": {
        "gujo": {
            "url": "https://api.gujo.ai/api/mcp",
            "headers": {
                "Authorization": "Bearer ${env:GUJO_API_KEY}"
            }
        }
    }
}
```

#### VS Code

[Install in VS Code](vscode:mcp/install?%7B%22name%22%3A%22gujo%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.gujo.ai%2Fapi%2Fmcp%22%7D)

The install button adds the server to VS Code. The first time the server starts, VS Code opens your browser to sign in and approve access. To keep the setup in a repository, put the following in the config file.

File: `.vscode/mcp.json`

```json
{
    "servers": {
        "gujo": {
            "type": "http",
            "url": "https://api.gujo.ai/api/mcp"
        }
    }
}
```

**Connect with a key (CI, scripts)**

VS Code asks for the key in a password prompt the first time it uses the server and stores it separately. Put the following in the config file.

File: `.vscode/mcp.json`

```json
{
    "inputs": [
        {
            "type": "promptString",
            "id": "gujo-token",
            "password": true,
            "description": "Gujo account API key (mcp.read)"
        }
    ],
    "servers": {
        "gujo": {
            "type": "http",
            "url": "https://api.gujo.ai/api/mcp",
            "headers": {
                "Authorization": "Bearer ${input:gujo-token}"
            }
        }
    }
}
```

#### Claude Code

Add the server with the command below, then open `/mcp` in Claude Code, choose `gujo` and sign in. Once you approve in the browser, Claude Code keeps the token and refreshes it on its own. To share the setup with a project, put it in `.mcp.json` instead of running the command.

```bash
claude mcp add --transport http gujo https://api.gujo.ai/api/mcp
```

File: `.mcp.json`

```json
{
    "mcpServers": {
        "gujo": {
            "type": "http",
            "url": "https://api.gujo.ai/api/mcp"
        }
    }
}
```

**Connect with a key (CI, scripts)**

Claude Code expands `${GUJO_API_KEY}` in `.mcp.json` from the environment when it starts. `claude mcp add --header` does not expand environment variables and would leave the key itself in the config, so add the server with this file.

File: `.mcp.json`

```json
{
    "mcpServers": {
        "gujo": {
            "type": "http",
            "url": "https://api.gujo.ai/api/mcp",
            "headers": {
                "Authorization": "Bearer ${GUJO_API_KEY}"
            }
        }
    }
}
```

#### Codex

Add the server with the command below and sign in through the browser with `codex mcp login gujo`. Once you approve, Codex keeps the token and refreshes it on its own.

```bash
codex mcp add gujo --url https://api.gujo.ai/api/mcp
codex mcp login gujo
```

**Connect with a key (CI, scripts)**

Codex reads the key from the environment variable `GUJO_API_KEY` named in `bearer_token_env_var` and sends it as an `Authorization: Bearer` header.

File: `~/.codex/config.toml`

```toml
[mcp_servers.gujo]
url = "https://api.gujo.ai/api/mcp"
bearer_token_env_var = "GUJO_API_KEY"
```

#### Claude.ai

In Claude.ai or Claude Desktop, go to Settings > Connectors > Add custom connector, enter only the URL below and connect. After you sign in to Gujo and approve in the browser, the connector receives a token. Do not enter a key.

```text
https://api.gujo.ai/api/mcp
```

**Connect with a key (CI, scripts)**

In Settings > Connectors > Add custom connector, enter the URL below and add an `Authorization` request header with `Bearer` and your key. The key is then stored by Anthropic. Create a separate key for this connector only and revoke it as soon as you stop using it.

```text
URL: https://api.gujo.ai/api/mcp
Authorization: Bearer <GUJO_API_KEY>
```

#### Windsurf

Windsurf (Devin Desktop) adds the server from a config that has only the URL, and opens your browser to sign in and approve access the first time it connects. Put the following in the config file.

File: `~/.codeium/windsurf/mcp_config.json`

```json
{
    "mcpServers": {
        "gujo": {
            "serverUrl": "https://api.gujo.ai/api/mcp"
        }
    }
}
```

**Connect with a key (CI, scripts)**

Windsurf (Devin Desktop) reads the key from the environment variable `GUJO_API_KEY`. Put the following in the config file.

File: `~/.codeium/windsurf/mcp_config.json`

```json
{
    "mcpServers": {
        "gujo": {
            "serverUrl": "https://api.gujo.ai/api/mcp",
            "headers": {
                "Authorization": "Bearer ${env:GUJO_API_KEY}"
            }
        }
    }
}
```

### Design MCP

The endpoint is `https://design.gujo.ai/mcp`. Keep the key in the environment variable `GUJO_DESIGN_TOKEN`.

#### Cursor

[Install in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=gujo-design&config=eyJ1cmwiOiJodHRwczovL2Rlc2lnbi5ndWpvLmFpL21jcCIsImhlYWRlcnMiOnsiQXV0aG9yaXphdGlvbiI6IkJlYXJlciAke2VudjpHVUpPX0RFU0lHTl9UT0tFTn0ifX0%3D)

Cursor reads the key from the environment variable `GUJO_DESIGN_TOKEN`, so start Cursor from a shell that has it. Put the following in the config file.

File: `~/.cursor/mcp.json`

```json
{
    "mcpServers": {
        "gujo-design": {
            "url": "https://design.gujo.ai/mcp",
            "headers": {
                "Authorization": "Bearer ${env:GUJO_DESIGN_TOKEN}"
            }
        }
    }
}
```

#### VS Code

[Install in VS Code](vscode:mcp/install?%7B%22name%22%3A%22gujo-design%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fdesign.gujo.ai%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24%7Binput%3Agujo-design-token%7D%22%7D%2C%22inputs%22%3A%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22gujo-design-token%22%2C%22password%22%3Atrue%2C%22description%22%3A%22Gujo%20design%20agent%20token%22%7D%5D%7D)

VS Code asks for the key in a password prompt the first time it uses the server and stores it separately. Put the following in the config file.

File: `.vscode/mcp.json`

```json
{
    "inputs": [
        {
            "type": "promptString",
            "id": "gujo-design-token",
            "password": true,
            "description": "Gujo design agent token"
        }
    ],
    "servers": {
        "gujo-design": {
            "type": "http",
            "url": "https://design.gujo.ai/mcp",
            "headers": {
                "Authorization": "Bearer ${input:gujo-design-token}"
            }
        }
    }
}
```

#### Claude Code

Claude Code expands `${GUJO_DESIGN_TOKEN}` in `.mcp.json` from the environment when it starts. `claude mcp add --header` does not expand environment variables and would leave the key itself in the config, so add the server with this file.

File: `.mcp.json`

```json
{
    "mcpServers": {
        "gujo-design": {
            "type": "http",
            "url": "https://design.gujo.ai/mcp",
            "headers": {
                "Authorization": "Bearer ${GUJO_DESIGN_TOKEN}"
            }
        }
    }
}
```

#### Codex

Codex reads the key from the environment variable `GUJO_DESIGN_TOKEN` named in `bearer_token_env_var` and sends it as an `Authorization: Bearer` header.

File: `~/.codex/config.toml`

```toml
[mcp_servers.gujo-design]
url = "https://design.gujo.ai/mcp"
bearer_token_env_var = "GUJO_DESIGN_TOKEN"
```

#### Windsurf

Windsurf (Devin Desktop) reads the key from the environment variable `GUJO_DESIGN_TOKEN`. Put the following in the config file.

File: `~/.codeium/windsurf/mcp_config.json`

```json
{
    "mcpServers": {
        "gujo-design": {
            "serverUrl": "https://design.gujo.ai/mcp",
            "headers": {
                "Authorization": "Bearer ${env:GUJO_DESIGN_TOKEN}"
            }
        }
    }
}
```

## Check the connection

1. Ask the agent for its tool list. The tools the granted permissions (or the key's abilities) allow appear.
2. If no sign-in window opens, check that the URL is `https://api.gujo.ai/api/mcp`. Start signing in again with `/mcp` in Claude Code or `codex mcp login gujo` in Codex.
3. With a key, on 401 check the key, and on 403 `missing_api_key_ability` check that the key has `mcp.read`.
4. With a key, if no tools appear, the client may not see the environment variable. Start the client again from a shell that has it.

## Toolsets

Agents pick tools less accurately when they see all of them at once, so tools are grouped into toolsets. The first `tools/list` shows three meta tools and the tools of the `library` toolset.

| Meta tool | Input | What it does |
|---|---|---|
| `search_tools` | `query?`, `toolset?` | Finds tools this key can call. Each result is `{name, toolset, method, path, read_only, description}` |
| `enable_toolset` | `toolset` | Turns a toolset on and adds its tools to the session's tool list. The result includes the input schemas of those tools |
| `disable_toolset` | `toolset` | Turns a toolset off |

Turning a toolset on or off makes the server send `notifications/tools/list_changed`. If the client sent `Accept: text/event-stream`, the same response comes as SSE with that notification before the result. An unknown toolset name is an `unknown_toolset` tool error.

| Toolset | Tools |
|---|---|
| `library` (on from the start) | `library`, `products_content_assets_list` |
| `products` | `products_list`, `products_show`, `products_detail`, `products_releases`, `products_updates` |
| `install` | `products_install` |
| `license` | `license_check`, `products_license`, `entitlements_pass_check` |
| `setup` | `products_setup_progress_show`, `products_setup_progress_update` |
| `device` | `bundles_list`, `device_entitlements`, `device_installs_create`, `device_usage_create` |
| `orders` | `invoices_list`, `orders_list`, `orders_show`, `payment_methods_list` |
| `subscriptions` | `subscriptions_list` |
| `client_config` | `client_config` |

One tool is one account API endpoint. Tool names, required abilities and input fields match the `tool` column of the [API reference](../api-reference.md). Read the detail and manual of a product you bought with `products_detail`.

- The tool list only shows tools whose ability the key has. A key without `orders.read`, for example, sees no `orders` tools.
- Entitlement and product-scoped key checks need the arguments, so they come back at call time as tool errors such as 403 `product_entitlement_required`.
- The three write tools (`products_setup_progress_update`, `device_installs_create`, `device_usage_create`) run right away without approval.
- Endpoints that revoke the calling key or return a new key in plain text, endpoints that take file uploads, and non-JSON responses (such as bundle files) are not tools.

## Sessions

1. Take the `Mcp-Session-Id` response header from `initialize`. The protocol version is `2025-11-25`.
2. Send the same `Mcp-Session-Id` on every later request. Each session remembers its enabled toolsets.
3. Each use extends a session by 12 hours. An unknown session, or one created with another key, gets HTTP 404 with JSON-RPC `-32600`. Start again from `initialize`.

A request without the session header is treated as having only the `library` toolset on. Batch requests are rejected (400). Supported methods are `initialize`, `ping`, `tools/list`, `tools/call` and notifications (202 with an empty body).

## Results and errors

- Success: `structuredContent = {status, body}`, the HTTP status and JSON body you would get calling the endpoint over REST.
- Failure: `isError: true`, `structuredContent = {error, message, status?, body?, field?}`. `error` is the endpoint's error code (such as `product_entitlement_required`), or `invalid_input` when an argument does not match the input fields (`field` names it).
- Calling a tool name you cannot see is the JSON-RPC error `-32602 Unknown tool`. The old `mcp_tool_{id}` names now get this error too.
- An unknown method is `-32601`.

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| Every request starts a new session, or enabled toolsets disappear | You are calling `gujo.ai/api/mcp`, which does not forward the `Mcp-Session-Id` header. Use `https://api.gujo.ai/api/mcp` |
| 401 `missing_api_key` | The client sends no `Authorization` header. If you connected by signing in, you have not signed in yet, so start signing in from the client (`/mcp` in Claude Code, `codex mcp login gujo` in Codex). With a key, check that the environment variable exists in the shell that started the client |
| 401 `invalid_api_key` | The token or key expired or was revoked. If you connected by signing in, sign in again from the client. With a key, create a new key |
| 403 `missing_api_key_ability` | The permission that work needs is missing. If you connected by signing in, check that permission when the app asks for consent again. With a key, check that it has `mcp.read`. Device sign-in keys (gujoctl) never have it, so create a separate key in Developer settings |
| 404 with `-32600` | The session expired or belongs to another key. Run `initialize` again |
| `-32602 Unknown tool` | This key cannot see that tool. Check the name with `search_tools` and add the ability the tool needs to the key |
| Tool result is 403 `product_entitlement_required` | You have no entitlement to that product. Check the product ids you can use with `library` first |
