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 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. You choose exactly what it offers: tools, callable agents, skills, and resources. Every request an external agent makes is one execution 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.
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.
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.
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. 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, invoke, and get_task with cancel_task. Every field and limit is in the MCP Servers reference.
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 has the steps for each host. If your agents run on your own platform, Twisp in Your Agent Platform 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/callmakes 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/listrecords 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 and 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.
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.
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 unpublisheddefinitionruns 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 and Preview and replay.
Long-running agents as tasks
A callable agent 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/getand cancel withtasks/cancel. - Hosts without it can still run agents when the server lists
get_taskandcancel_task: the call returns atask_id, which the agent reads withget_taskuntil 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.
Sessions and rate limits
Every request is recorded in a session 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.callsPerMinutebounds 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 get429withRetry-After. - Kill switch.
closeSessionstops 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 shows sessions keyed by your own run IDs. See Sessions and 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, 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.
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 its recorded requests against the change. See Preview mode.
The default server in the local environment
The local environment 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 shows how to connect to it.
Where to go next
Connecting MCP hosts
Add a server to Claude Code, Claude, VS Code, and other hosts, and sign in.
Twisp in your agent platform
Give your own agents their own authority, sessions per run, and trace correlation.
Building actions with a coding agent
Install the action-builder server so a coding agent can build and replay against your data.
MCP Servers reference
Every field, method, limit, and error of an MCP server.