# MCP Servers

Offer chosen tools, skills, and resources to agents running outside Twisp, with every request recorded and every call bounded by policies.

An **MCP server** is an [action](https://www.twisp.com/docs/actions.md) of kind `mcp` that offers some of your tools to agents running outside Twisp, such as a coding assistant, an operations bot, or an agent on your own platform, over the [Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28). You choose exactly what it offers: [tools](https://www.twisp.com/docs/actions/tools.md), [callable agents](https://www.twisp.com/docs/actions/agents.md), [skills](https://www.twisp.com/docs/actions/skills.md), and resources. Every request an external agent makes is one [execution](https://www.twisp.com/docs/actions/triggers-and-executions.md) of the server, so what it asked for, what it was shown, and everything its calls did are recorded and can be investigated like the work of an agent Twisp runs.

> **Note:**
>
> An MCP server grants nothing. Every operation performed through it must be allowed by the principal the request signed in as, by the server's `policies`, and by each tool's own policies. A principal cannot do through a server what it could not do itself, and the server's policies bound what anyone can do through it. See [Authority and audit](https://www.twisp.com/docs/actions/authority-and-audit.md).

## What a server is

A server is defined with `createAction`, like any action. Its `mcp` block names it and says how requests are grouped into sessions; `tools` and `skills` list what it offers; `policies` bound everything done through it.

```graphql
createAction(
  id: "3c1e4d1a-7a1b-4bde-9b7e-6b8f3f0c5a21"
  input: {
    name: "fraud-ops"
    kind: "mcp"
    config: {}
    mcp: {
      name: "fraud-ops"
      instructions: "Tools for reviewing account activity."
      session: { start: { input: { principal: "execution.input.principal", subject: "execution.input.subject" } } }
    }
    session: {
      inputSchema: {
        type: "object"
        properties: { principal: { type: "string" }, subject: { type: "string" } }
        required: ["principal", "subject"]
      }
      id: "string(size(session.input.principal)) + ':' + session.input.principal + ':' + session.input.subject"
    }
    tools: [
      { name: "get_account_context" }
      { name: "notify_human_ops", input: { requested_by: "execution.input.subject" } }
    ]
    policies: [{ effect: ALLOW, actions: [SELECT], resources: ["financial.*"] }]
  }
)
```

| Part            | What it does                                                                                                                                                                                                                                                                                  |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tools`         | Ordinary [tool references](https://www.twisp.com/docs/reference/actions/tools.md#tool-references). A server offers exactly the tools it lists, each at a pinned `version` or its latest. Fixed `input` replaces what the external agent sends, which is how you stamp who asked into a call, as `requested_by` does above. |
| `skills`        | Skills the server offers to the external agent as skills and prompts, each rendered as one `SKILL.md`. Loading one makes none of its tools available: the external agent calls only the server's own tools.                                                                                   |
| `mcp.resources` | URI templates, such as `twisp://accounts/{account_id}`, each read by one of the server's read-only tools, so a host can attach an account or an execution to a conversation.                                                                                                                  |
| `policies`      | The server's layer of the policy chain. Every call through the server must be allowed by them.                                                                                                                                                                                                |

A server can also list native tools that only a server can offer: [`program`](https://www.twisp.com/docs/actions/mcp-servers.md#atomic-work-with-program), [`invoke`](https://www.twisp.com/docs/actions/mcp-servers.md#running-what-you-built-with-invoke), and [`get_task`](https://www.twisp.com/docs/actions/mcp-servers.md#long-running-agents-as-tasks) with `cancel_task`. Every field and limit is in the [MCP Servers reference](https://www.twisp.com/docs/reference/actions/mcp.md#defining-a-server).

## The endpoint and sign-in

Each server has its own endpoint, named by your account and the server's registry `name`:

```
https://api.<region>.<environment>.twisp.com/mcp/v1/<account_id>/<name>
```

A person connects a host, such as Claude Code, Claude, or VS Code, by adding that URL and signing in to Twisp as the user they are, on the same page and with the same MFA as the console. There are no tokens to copy. Each request is then made as that user, with the permissions they have in Twisp. The token a host receives works only at the region's MCP endpoints, not at the GraphQL API.

To use a server, a principal needs permission to read the server's action and `INVOKE` on it. It needs no `INVOKE` on the server's tools: the server's references authorize those calls. [Connecting MCP hosts](https://www.twisp.com/docs/guides/mcp-hosts.md) has the steps for each host. If your agents run on your own platform, [Twisp in Your Agent Platform](https://www.twisp.com/docs/guides/your-agents.md) shows how to give each one its own principal.

## Requests are executions

Each request that passes sign-in is one execution of the server, whose input is the request. `ActionExecution.mcpRequest` reads it: the method, the tool it named, the arguments the external agent sent, its stated intent, the principal and signed-in user, the host application, the client's self-reported name and version, and the request's `_meta`, such as your trace context.

- **One call per request.** A `tools/call` makes exactly one call, a child execution of the request. Its effective input, with fixed input applied, and its output or error are recorded, and a successful output is recorded as a use of the request.
- **Nothing leaves before it is recorded.** Twisp returns a result only after the execution that recorded it commits. If the commit fails, the client gets an error and no content.
- **Errors the agent can act on.** Arguments that fail a tool's schema, a policy denial, and other call errors are tool results with `isError`, so the external agent can correct itself. A denial names the operation, the resource, and the layer that denied it: the principal, the server, or the tool.
- **Listings are recorded too.** A `tools/list` records the exact version of every tool it offered, so you can tell which contracts an agent saw.

Requests refused after sign-in, such as for a missing `INVOKE` or the rate limit, are recorded as `FAILED` executions with the reason; under a flood, the first refusal of each user in a minute is recorded, with a count of the rest. See [Requests](https://www.twisp.com/docs/reference/actions/mcp.md#requests) and [Refusals](https://www.twisp.com/docs/reference/actions/mcp.md#refusals).

## Stated intent

Twisp never sees why an external model makes a call, so a server asks. By default, `intent: REQUIRED`, every tool gains a required `intent` argument: a sentence saying why the agent is making the call. The server records it on the request and removes it before the call, so the tool never receives it. `OPTIONAL` offers the argument without requiring it, and `NONE` adds nothing.

The stated intent is the external model's own account. It is evidence for whoever investigates later, read side by side with what the call did, and is never used for authorization. See [Stated intent](https://www.twisp.com/docs/reference/actions/mcp.md#stated-intent).

## Atomic work with `program`

Agents often need several steps that must all happen or none, such as a fee, a transfer, and a settlement. A server that lists `program` lets the external agent send one Starlark program that calls the server's tools, and Twisp runs it in one transaction: every call commits together, or none does.

`commit` is required. With `commit: false`, the program runs every call and commits nothing, and returns its value together with every row it would write that the principal may read. The agent, or a review step on your platform, checks the effect, then sends the same program with `commit: true`. Both runs are recorded, so an investigator sees the preview and the commit that followed it.

A program can do nothing its tools could not do one call at a time: it reaches only the tools the server lists, under the same policies. See [Programs](https://www.twisp.com/docs/reference/actions/mcp.md#programs).

## Running what you built with `invoke`

A server that lists `invoke` lets the external agent run one of your tools or callable agents on an input it chooses. It is how an agent that builds tools and agents through the server tests its work:

- With `commit: false`, the default, it previews: a stored version, a disabled one, or an unpublished `definition` runs against your real data, and none of its writes commit. The result shows the output or error, each call, and the rows it would have written.
- With `commit: true`, it runs a published, enabled version for real.

A new tool can be created disabled, previewed as drafts until it works, published, and then invoked with `commit: true` where a real run is safe. `invoke` can run any tool or agent the principal and the server may `INVOKE`, not only those the server lists, so list it only on a server agents build with. See [Invoking an action](https://www.twisp.com/docs/reference/actions/mcp.md#invoke) and [Preview and replay](https://www.twisp.com/docs/actions/preview-and-replay.md).

## Long-running agents as tasks

A [callable agent](https://www.twisp.com/docs/actions/agents.md) runs its whole loop, often for minutes: longer than a request should be held open. So calling one through a server, by naming it in `tools/call` or running it with `invoke`, answers at once with a **task**: the agent runs on its own as a started execution, and the host polls for its result.

- Hosts that support the MCP Tasks extension poll with `tasks/get` and cancel with `tasks/cancel`.
- Hosts without it can still run agents when the server lists `get_task` and `cancel_task`: the call returns a `task_id`, which the agent reads with `get_task` until the task completes.

A task runs under the requesting principal, then the server's policies, then the agent's own, checked again at each invocation, so revoking the principal or its credential expiring stops work that has not run. A task is reachable only from the session that created it, and a session, like a user, has at most 32 unfinished tasks. See [Tasks](https://www.twisp.com/docs/reference/actions/mcp.md#tasks).

## Sessions and rate limits

Every request is recorded in a [session](https://www.twisp.com/docs/actions/sessions.md) of the server. The `mcp.session.start.input` mapping and the session's `id` decide how requests are grouped: per user, per user of each host, per principal, or per conversation or run ID your platform sends in `_meta`.

Each session is its own unit of control:

- **History.** A session's inputs are the ordered log of its requests, each naming its execution.
- **Rate limit.** `mcp.limits.callsPerMinute` bounds each session, 300 calls a minute by default. A request counts as one while it runs, and once recorded as the number of tool calls it made, including those beneath them, when that is more. Over the limit, requests get `429` with `Retry-After`.
- **Kill switch.** `closeSession` stops one user, host, or run at once, without touching the others. Disabling the server refuses every request.

Group by the unit you would want to stop on its own. [Twisp in Your Agent Platform](https://www.twisp.com/docs/guides/your-agents.md#one-session-per-run) shows sessions keyed by your own run IDs. See [Sessions](https://www.twisp.com/docs/reference/actions/mcp.md#sessions) and [Rate limits](https://www.twisp.com/docs/reference/actions/mcp.md#rate-limits).

## Read-only annotations come from policies

Hosts use tool annotations to decide when to ask a person before a call. A server does not let you declare them; it derives them from policies:

| Annotation               | When                                                                                                                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `readOnlyHint: true`     | The server's policies, or the tool's own, allow only `SELECT`.                                                                                                                                         |
| `destructiveHint: false` | The server's or the tool's policies allow only `SELECT` and `INSERT`.                                                                                                                                  |
| `openWorldHint: true`    | The tool is a [model](https://www.twisp.com/docs/actions/models.md), whose input goes to the model's provider, or it declares one, directly or through its tools. `program` is open world when the server lists an open-world tool. |

So the way to let hosts call a tool without asking is to give it read-only policies. `program` and `invoke` can call everything, so their annotations come from the server's policies alone. Annotations are derived for each listing from the versions it offers, and change when a tool's policies do. See [Annotations](https://www.twisp.com/docs/reference/actions/mcp.md#annotations).

## Preview mode, then LIVE

Every server starts in **preview mode**. A preview server runs every call exactly as it would live, against your real data, and returns real results, but commits none of the calls' writes: each call records the writes it would have made. The request's execution, its session history, and its uses still commit, and each result carries `"com.twisp/preview": true` in `_meta`, so an informed host can say so. Tasks a preview request creates are previews too, and a `program` or `invoke` with `commit: true` commits nothing on a preview server.

Connect a new external agent to a preview server, review what it would have written, then promote the server by updating its `mcp` block with `mode: LIVE`. Before changing a live server or a tool it uses, [replay](https://www.twisp.com/docs/actions/preview-and-replay.md#replaying-against-another-version) its recorded requests against the change. See [Preview mode](https://www.twisp.com/docs/reference/actions/mcp.md#preview-mode).

## The `default` server in the local environment

The [local environment](https://www.twisp.com/docs/infrastructure/local-environment.md) has a server named `default`, which offers an agent the whole of Twisp: `graphql`, `cel`, `program`, `invoke`, `get_task` and `cancel_task`, and skills for designing, verifying, and automating a ledger. It needs no sign-in and runs `LIVE`, so its writes commit. Like any server, it is made of ordinary actions you can change. [Twisp for AI Agents](https://www.twisp.com/docs/introduction/agents.md) shows how to connect to it.

## Where to go next

- [Connecting MCP hosts](https://www.twisp.com/docs/guides/mcp-hosts.md): Add a server to Claude Code, Claude, VS Code, and other hosts, and sign in.
- [Twisp in your agent platform](https://www.twisp.com/docs/guides/your-agents.md): Give your own agents their own authority, sessions per run, and trace correlation.
- [Building actions with a coding agent](https://www.twisp.com/docs/guides/mcp-action-builder.md): Install the action-builder server so a coding agent can build and replay against your data.
- [MCP Servers reference](https://www.twisp.com/docs/reference/actions/mcp.md): Every field, method, limit, and error of an MCP server.
