> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/ai/mcp-server.md).

# MCP Server

Drive your xprem server from any MCP client, authenticated with your dashboard account and bounded by its permissions.

xprem ships with a built-in MCP (Model Context Protocol) server, so AI agents such as Claude Code, Cursor or Codex can connect to your deployment and work with it directly: listing apps, branches, channels and updates, following a progressive rollout, publishing a rollback, or digging into device telemetry. An agent always acts as the dashboard account of the person who connected it, and it can never do more than that account could do in the dashboard.

The endpoint lives at `https://<your-server>/mcp` and uses the streamable HTTP transport. There is nothing to enable: the endpoint is present on every deployment that runs the control plane.

{% hint style="info" %}
The MCP server authenticates dashboard accounts, so it requires the control plane. A stateless deployment (no [database](/xprem/installation-guide/database-configuration.md)) does not expose the `/mcp` endpoint.
{% endhint %}

## How authentication works

The server implements the OAuth 2.1 flow. From your side the whole thing is a browser round-trip: the first time an agent connects, it opens your server's consent page, you log in with your dashboard account (single sign-on included, when configured), you approve the client, and the agent is connected.

## What a connected agent may do

Agents inherit the same authorization rules as the dashboard, applied at two levels. When an agent connects, its tool list is filtered: a tool the account cannot use on any visible app is simply absent from the session. Each tool call is then checked again against the specific app it targets, so seeing a tool never grants it on every app. The `whoami` tool returns the exact picture: the account's role and, for every app it can see, each permission granted or denied.

Who can use what depends on whether Enterprise RBAC feature is enabled:

* Without RBAC (the community default), every member can use the read tools on the apps they see, while the certificate download and every write tool (creating or deleting branches and channels, rolling back, republishing) are reserved to admins.
* With Enterprise roles, the per-app permissions listed in the tables below decide, and members can be granted write access app by app. The audit log tool remains admin only in both cases.

The destructive tools (`delete_branch`, `delete_channel`, `rollback_branch`) declare the MCP destructive annotation, so well-behaved clients ask you for an explicit confirmation before calling them. Write tools go through the same services as the dashboard, which means that on Enterprise deployments every action lands in the audit log with the account as actor.

### Core tools

These tools are part of the MIT core and are present on every deployment that runs the control plane.

| Tool                   | What it does                                                                                                                                                                  | Permission            |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `whoami`               | Returns the account you are connected as, its role, and for every app it can see the permissions granted and denied.                                                          | Any signed-in account |
| `get_apps`             | Lists the id and name of every app the account is allowed to see.                                                                                                             | Any signed-in account |
| `get_branches`         | Lists an app's branches with their release channel, protection flag and current update.                                                                                       | Visibility on the app |
| `get_runtime_versions` | Lists the runtime versions published on one branch, with update counts, last publication date and active rollout state.                                                       | Visibility on the app |
| `get_channels`         | Lists an app's release channels with the branch each one points at, its current updates and any rollout in progress.                                                          | Visibility on the app |
| `get_channel_rollouts` | Lists only the channels currently running a progressive rollout from one branch to another.                                                                                   | Visibility on the app |
| `get_updates`          | Lists published updates newest first, paged, with filters on branch, runtime version, platform, publish group, commit hash and date range.                                    | Visibility on the app |
| `get_update_rollout`   | Reports whether the latest update of a branch and runtime version is being rolled out, and to what percentage on each platform.                                               | Visibility on the app |
| `get_update_patches`   | Lists the bsdiff patches computed toward one update: the earlier update each one starts from, its status, and the patch size against the full download. Needs bundle diffing. | Visibility on the app |
| `get_server_config`    | Returns the deployment's settings as the dashboard settings page shows them, with key-like values masked.                                                                     | Any signed-in account |
| `get_certificate`      | Returns the app's public code-signing certificate.                                                                                                                            | `certificate:read`    |
| `create_branch`        | Creates a branch on an app.                                                                                                                                                   | `branch:create`       |
| `delete_branch`        | Deletes a branch and the update files it holds, refusing when it is protected, mapped to a channel, or serving an active rollout.                                             | `branch:delete`       |
| `create_channel`       | Creates a release channel, optionally mapped to a branch straight away.                                                                                                       | `channel:create`      |
| `delete_channel`       | Deletes a release channel, after which devices still pointing at it stop receiving updates.                                                                                   | `channel:delete`      |
| `rollback_branch`      | Publishes a rollback to the embedded bundle for one branch and runtime version, recording the reason you pass with it.                                                        | `update:publish`      |
| `republish_update`     | Republishes a past update, or every platform of a publish group, as the newest update of its branch and runtime version.                                                      | `update:publish`      |

When roles are not enforced, every tool gated by a permission in that table falls back to admin only, `get_certificate` included.

### Device and telemetry tools

These tools read the device registry and the Observe telemetry. They are listed on every deployment that runs the control plane. Their code lives under `ee/` and is covered by the [Enterprise license](/xprem/references/open-core-and-licensing.md), not MIT. On a deployment that records no devices (device telemetry disabled), the `identity:read` and `observe:read` tools answer with an explanatory error instead of data.

`query_errors` and `get_error_details` need an active Enterprise license. The other tools need no license key at runtime. `query_audit_logs` returns the events recorded while a license was active.

`query_logs`, `query_errors`, `get_error_details` and `get_observe_events` return an error when called with a `thermalState`, `lowPowerMode`, `networkType`, `frozenFrames` or `networkBytes` filter. Only `get_observe_overview` and `get_metric_breakdown` accept these filters.

| Tool                    | What it does                                                                                                                                                                             | Permission            |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `query_audit_logs`      | Returns the deployment's audit events newest first, filterable by actor, action, app, outcome and date range.                                                                            | Admin only            |
| `get_update_health`     | Returns the adoption, success and failure counts of one or more updates, or of a whole publish group, with their history over time.                                                      | Visibility on the app |
| `search_devices`        | Returns a page of the app's registered installs, most recently seen first, filtered on platform, release, location or custom attributes.                                                 | `identity:read`       |
| `get_device`            | Returns one install by its EAS client id, with its platform, release, hardware, location and attributes.                                                                                 | `identity:read`       |
| `count_online_devices`  | Counts the devices that contacted the server within a recent window, using the same filters as `search_devices`.                                                                         | `identity:read`       |
| `get_device_attributes` | Lists the attribute keys declared on the app with their type, or the values seen for one key with a device count each.                                                                   | `identity:read`       |
| `query_logs`            | Returns the app's log and crash lines newest first, paged, with filters on severity, free text, event name, device and release. Error lines sent by the SDK carry an `errorFingerprint`. | `observe:read`        |
| `query_errors`          | Lists the app's error groups over a window of up to 31 days, with occurrences, impacted installs and fatal occurrences per group, paged.                                                 | `observe:read`        |
| `get_error_details`     | Returns one error group from `query_errors` over a window of up to 31 days: counts, breakdowns by update, device model, OS version and runtime version, and its occurrences, paged.      | `observe:read`        |
| `get_observe_overview`  | Summarises a time window: users, sessions, events and releases, the busiest metrics with their percentiles, and the top cities.                                                          | `observe:read`        |
| `get_metric_breakdown`  | Splits one timing metric by a dimension and returns devices, samples, p50 and p90 per segment, against the overall baseline.                                                             | `observe:read`        |
| `get_observe_events`    | Lists the event names emitted over a window, busiest first, with line, install and session counts.                                                                                       | `observe:read`        |

When roles are not enforced, every member holds `identity:read` and `observe:read` on every app.

## Connect a client

The MCP endpoint of your deployment is `https://<your-server>/mcp`. **Server → MCP** in the dashboard shows your deployment's own URL and the instructions below.

{% tabs %}
{% tab title="Claude Code" %}
Run the command, then type `/mcp` in Claude Code to sign in.

```bash
claude mcp add --transport http xprem https://updates.example.com/mcp
```

{% endtab %}

{% tab title="Claude (desktop and web)" %}
Open **Customize › Connectors**, click **+** then **Add custom connector**, paste the URL and click **Add**. On a Team or Enterprise plan, an owner adds it from **Organization settings › Connectors** and each member clicks **Connect**.

```
https://updates.example.com/mcp
```

{% endtab %}

{% tab title="Cursor" %}
Add the server to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` for a single project. Cursor opens the sign-in page on first use.

```json
{
  "mcpServers": {
    "xprem": {
      "url": "https://updates.example.com/mcp"
    }
  }
}
```

{% endtab %}

{% tab title="VS Code" %}
Add the server to `.vscode/mcp.json`, or run **MCP: Add Server** from the command palette and pick HTTP.

```json
{
  "servers": {
    "xprem": {
      "type": "http",
      "url": "https://updates.example.com/mcp"
    }
  }
}
```

{% endtab %}

{% tab title="Codex" %}
Add the server to `~/.codex/config.toml`, then sign in. The CLI, the IDE extension and the desktop app share this file.

```toml
[mcp_servers.xprem]
url = "https://updates.example.com/mcp"
```

```bash
codex mcp login xprem
```

{% endtab %}

{% tab title="Gemini CLI" %}
Run the command. Gemini CLI detects the sign-in step on its own.

```bash
gemini mcp add --transport http xprem https://updates.example.com/mcp
```

{% endtab %}

{% tab title="Other" %}
Any client that supports remote MCP servers over streamable HTTP with OAuth can connect. Give it the URL; most clients accept this block in their MCP configuration file.

```
https://updates.example.com/mcp
```

```json
{
  "mcpServers": {
    "xprem": {
      "url": "https://updates.example.com/mcp"
    }
  }
}
```

{% endtab %}
{% endtabs %}

After sign-in, the client stores and refreshes its tokens on its own.

## Server configuration

There is no dedicated switch for the MCP server; it relies on settings you have already configured:

* A [database](/xprem/installation-guide/database-configuration.md), because accounts, OAuth clients and refresh tokens live in Postgres.
* `BASE_URL` must be the exact public URL your clients reach the server on: it is the OAuth issuer and the audience of every access token, so a mismatch (an internal hostname, a missing path) breaks client discovery. See [server configuration](/xprem/installation-guide/server-configuration.md).
* `JWT_SECRET` signs the MCP access tokens, the same way it signs dashboard sessions. Rotating it invalidates every issued token.

If the server sits behind a reverse proxy, make sure `/mcp`, `/oauth/` and `/.well-known/` are all forwarded on that same public host, since clients resolve every endpoint from `BASE_URL`.
