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

# MCP server

> Point Claude Desktop, Claude Code or Cursor at your workspace: nineteen tools, each behind one token permission.

There are two MCP servers you will meet around UTMKit, and they do different jobs. **`https://docs.utmkit.co/mcp`** belongs to this documentation site: it searches and reads these pages, and nothing else — point an assistant at it to ask how the product works. **`https://app.utmkit.co/mcp`** belongs to the product: it operates your workspace — lists, creates, edits and withdraws links, manages campaigns and rules, reads the report — and it needs a token. Same protocol, same path, two hosts. This page is about the second one.

## What it is

An MCP (Model Context Protocol) server, spoken over the protocol's Streamable HTTP
transport, inside the product itself. Every tool calls the same operation the console and
the API call, so the workspace's rules apply unchanged: a link the convention would refuse
in the console is refused here, with the same sentence, as a tool result your assistant can
read and relay or repair.

Every request carries the same header as the API:

```text theme={null}
Authorization: Bearer utmk_…
```

Mint the token on the console's Tokens page with the permissions the assistant should
have, as described in [Authentication & tokens](/en/developers/authentication). The token
fixes the workspace; no tool takes a workspace, organization or user argument, and nothing
an assistant says can move a call to another one.

## Connecting a client

<Tabs>
  <Tab title="Claude Code">
    One command, run in any terminal:

    ```bash theme={null}
    claude mcp add --transport http utmkit https://app.utmkit.co/mcp \
      --header "Authorization: Bearer utmk_…"
    ```

    Then `/mcp` in a session lists the server and the tools your token permits. To keep the
    token out of the command line, put it in an environment variable and reference it from
    `.mcp.json` as `"Authorization": "Bearer ${UTMKIT_TOKEN}"`.
  </Tab>

  <Tab title="Cursor">
    In `~/.cursor/mcp.json` (every project) or `.cursor/mcp.json` (one project):

    ```json theme={null}
    {
      "mcpServers": {
        "utmkit": {
          "url": "https://app.utmkit.co/mcp",
          "headers": {
            "Authorization": "Bearer ${env:UTMKIT_TOKEN}"
          }
        }
      }
    }
    ```

    Set `UTMKIT_TOKEN` in your environment; Cursor resolves it when it connects.
  </Tab>

  <Tab title="Claude Desktop">
    Claude Desktop's own connectors authenticate with OAuth, which this server does not
    offer today, so the connection goes through the `mcp-remote` bridge in
    `claude_desktop_config.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "utmkit": {
          "command": "npx",
          "args": [
            "mcp-remote",
            "https://app.utmkit.co/mcp",
            "--header",
            "Authorization:${AUTH_HEADER}"
          ],
          "env": {
            "AUTH_HEADER": "Bearer utmk_…"
          }
        }
      }
    }
    ```

    The header is split between `args` and `env` on purpose: Claude Desktop mangles spaces
    inside an argument, and the environment variable keeps the space after `Bearer` intact.
    Restart Claude Desktop after saving.
  </Tab>
</Tabs>

Any other MCP client that speaks Streamable HTTP and can send a header works the same way.

## The tools

Nineteen tools, each behind exactly one token permission. Reads need only the permission;
writes also need your role in the workspace to allow them, checked live on every call.
Every tool under `rules:write` also needs an admin or owner role.

| Tool                     | Permission        | What it does                                                                                                                          |
| ------------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `list_links`             | `links:read`      | The workspace's links, newest first, with the same search string the console's box takes                                              |
| `get_link`               | `links:read`      | One link by id: destination, UTMs, state, short URL, QR URL                                                                           |
| `create_link`            | `links:write`     | A governed short link, with or without UTMs; returns the short URL and the QR URL, plus a warning when the convention is in warn mode |
| `update_link`            | `links:write`     | Change destination, UTMs, campaign, expiry, tags or note; send only what changes                                                      |
| `withdraw_link`          | `links:write`     | Take a link down; it stops resolving at the edge                                                                                      |
| `list_tags`              | `links:read`      | The workspace's tags with how many links carry each                                                                                   |
| `create_tag`             | `links:write`     | Reserve a tag name, optionally with a colour, before any link uses it                                                                 |
| `list_campaigns`         | `campaigns:read`  | Campaigns with their link counts                                                                                                      |
| `get_campaign`           | `campaigns:read`  | One campaign by id                                                                                                                    |
| `create_campaign`        | `campaigns:write` | A campaign; a name that already exists returns the existing one                                                                       |
| `get_report`             | `analytics:read`  | The click report, scans reported apart, with the console's filters and grouping                                                       |
| `get_convention`         | `rules:read`      | Casing, separator, required parameters and mode                                                                                       |
| `list_approved_values`   | `rules:read`      | The approved values per parameter; an empty list means the parameter is open                                                          |
| `list_dependency_rules`  | `rules:read`      | The "when source is X, medium may be…" rules                                                                                          |
| `set_convention`         | `rules:write`     | Change the convention, partially                                                                                                      |
| `approve_values`         | `rules:write`     | Approve values for one parameter; repeats are no-ops                                                                                  |
| `retire_value`           | `rules:write`     | Retire one approved value; retiring a parameter's last value opens it                                                                 |
| `add_dependency_rule`    | `rules:write`     | Allow values for a target parameter under a driving pair                                                                              |
| `remove_dependency_rule` | `rules:write`     | Remove one dependency rule                                                                                                            |

**A tool the token lacks is hidden, and refused by name if called anyway.** The tool list a
client receives is filtered to what the token's permissions and your role allow, and it is
recomputed on every list request. A hidden tool is still there: an assistant that calls
`create_campaign` with a links-only token gets a tool result that says
`This token does not carry campaigns:write.` rather than "unknown tool", so it can tell you
exactly which box to tick on the next token.

There is no conversions tool. A conversion is a result **your server** reports against a
link — a sale, a sign-up — and a server does that through the API with the
`conversions:write` permission, not through an assistant. See [Conversions](/en/analytics/conversions).

## What to expect

* **Every write is attributed** to you and to the token, and appears in the
  [activity log](/en/workspaces/activity-log) like a change made in the console.
* **Refusals are tool results**, flagged as errors, one sentence each: the convention's
  refusal, an unapproved value, a destination the scanner rejected, an allowance used up.
  A protocol error, by contrast, means the client spoke wrongly.
* **The door is the API's door.** A revoked or expired token fails the connection with
  401; an organization with no live subscription with 402; the per-token
  [rate limit](/en/developers/rate-limits) applies, with `Retry-After`. No session outlives
  its token.
* **Demotion takes effect on the next call.** If your role changes, the write tools
  disappear from the list on the next tool listing, and a call made before that is refused
  for the role.

## The other server, once more

If your assistant answers a request to create a link with a search result, it is talking
to `docs.utmkit.co/mcp`, the documentation's server. Reconnect it to
`app.utmkit.co/mcp` with a token.
