---
title: MCP
description: A stdio MCP server any client can launch, with eleven tools over one API key.
---

`@raster/mcp` is `raster` as a Model Context Protocol server. An MCP client launches it over
stdio with an API key in its environment, and every tool is one call into the public SDK, which
is one call into the public API.

That thinness is the whole security story: there is **no second credential in this process** and
no path to the substrate from it. An MCP client operating a machine can do exactly what a
customer holding that API key can do, and nothing else.

## Configuration

```json
{
  "mcpServers": {
    "raster": {
      "command": "npx",
      "args": ["@raster/mcp"],
      "env": {
        "RASTER_API_KEY": "sk_...",
        "RASTER_BASE_URL": "https://api.example.com/v1"
      }
    }
  }
}
```

The server **refuses to start** without `RASTER_API_KEY` rather than coming up and failing
every tool call with `unauthenticated` — which, to a model, looks like the machine is broken.

Stdout is the protocol. Nothing in the process writes to it; every diagnostic goes to stderr.

## The tools

| Tool                 | What it does                                                  |
| -------------------- | ------------------------------------------------------------- |
| `machine_create`     | create a machine and **wait for its desktop**; returns the id |
| `machine_get`        | current state, size and creation time                         |
| `machine_screenshot` | the screen, plus the geometry it was captured at              |
| `machine_click`      | click at a coordinate                                         |
| `machine_type`       | type text                                                     |
| `machine_scroll`     | scroll, optionally after moving the pointer                   |
| `machine_exec`       | run a command as argv                                         |
| `machine_read_file`  | read a file                                                   |
| `machine_write_file` | write a file                                                  |
| `machine_snapshot`   | capture the machine's disk                                    |
| `machine_fork`       | a new machine from a copy of the disk                         |

`machine_create` waits for the desktop before returning, so the id it hands back is one the very
next tool call can use. That matters more here than in an SDK: a model has no good way to poll.

A screenshot comes back as two content parts — the geometry as text, then the image — so the
model reads the coordinate space it is about to click in from the same result.

## What the server tells the model

The server ships instructions with it, and they are the three things that go wrong most:

> Operate a cloud Linux machine with a desktop, a shell and a browser. Create one machine per
> task and reuse it: a machine keeps its filesystem. Take a screenshot before clicking or
> scrolling — every coordinate is in the pixel space of the most recent screenshot, and nothing
> is scaled for you.

## Failures are content, not transport errors

A refused call comes back as a tool result with `isError` and a sentence the model can act on:

```
quota_exceeded: your plan allows 3 running machines (running_machines: 3/3)
```

Throwing instead would give the model a protocol error with nothing in it to try differently. A
`quota_exceeded` carries the limit, the current value and the maximum inline for the same reason.

## Embedding it

```ts
import { createMcpServer, clientFromEnv } from "@raster/mcp";

const server = createMcpServer({ client: clientFromEnv() });
await server.connect(transport);
```

`createMcpServer` takes a `client`, an optional `tools` array to override the shipped set, and a
`version`. `TOOLS` and `toToolError` are exported if you are assembling your own server around
them.
