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

# MCP

> Serve a workspace's tools to any MCP client over stdio, the daemon's HTTP API, or SSH.

## What It Does

Mirage serves seven tools to any [MCP](https://modelcontextprotocol.io) client: `shell`, `read`, `write`, `edit`, `ls`, `grep` and `glob`, the set coding-agent harnesses expect. Each parameter carries a description, and `read`, `ls`, `grep` and `glob` are marked read-only, which lets Claude run them in parallel. Every call runs in a mirage session, so commands go through the shell, file calls go through the workspace's mounts, and the session's profile and policies apply. `edit` refuses to overwrite a file that changed since the agent last read it.

There are three ways to reach them, and all three end at the daemon's HTTP endpoint: stdio and SSH only carry the messages, so auth, sessions and history are decided in one place. Every `shell` call is a daemon job, listed by `GET /v1/jobs` as a `POST /shell` is.

| Door | Reach | Session |
| - | - | - |
| `mirage mcp` | stdio, relayed to the daemon | the default session of the workspace it serves, or the one `-s` names |
| `/v1/workspaces/{id}/mcp` | the daemon's HTTP API, streamable HTTP | the default session, or the one the URL names |
| `ssh -s <id>@host mcp` | the daemon's [SSH door](/home/ssh) | a fresh session under the login key's profile |

## stdio

`mirage mcp` relays stdin and stdout to the daemon's endpoint, starting the daemon when it is not running. Given a config, it creates a workspace from it. With no `workspace_id` in the config, that workspace is deleted when the client closes stdin, as a stdio server's state ends with its process. A config that names a `workspace_id` keeps its workspace: the first run creates it, and a later run of the same config reaches it again, because the daemon answers a create with the live workspace made from an identical config. A different config naming the same id is refused rather than attached. It looks for `workspace.yaml` walking up from the working directory; `MIRAGE_MCP_CONFIG` or a path argument picks another.

```json theme={null}
{
  "mcpServers": {
    "mirage": { "command": "mirage", "args": ["mcp", "/abs/path/workspace.yaml"] }
  }
}
```

`-w <id>` serves a workspace the daemon already holds, and leaves it as it was when the client goes:

```json theme={null}
{
  "mcpServers": {
    "mirage": { "command": "mirage", "args": ["mcp", "-w", "demo"] }
  }
}
```

`-s <session>` serves the tools as a session the workspace already holds, under that session's profile, as `mirage shell -s` runs a line. A session the workspace does not hold is refused before anything is relayed:

```json theme={null}
{
  "mcpServers": {
    "mirage": { "command": "mirage", "args": ["mcp", "-w", "demo", "-s", "agent"] }
  }
}
```

`${VAR}` in the config is read from the environment `mirage mcp` runs in, so a host's `env` block for the server reaches it. A `{from: env, key: VAR}` secret is fetched by the daemon from its own environment, as it is for `mirage workspace create`, so a credential stays where the workspace runs.

## HTTP

The daemon serves every workspace at `/v1/workspaces/{id}/mcp`, behind the same host check and [auth](/home/auth) as the rest of its API. The endpoint is stateless: each request runs in the workspace's default session, so a `cd` in one call holds for the next, and a session created with `POST /v1/workspaces/{id}/sessions` is named in the URL to give an agent its own cwd and environment:

```json theme={null}
{
  "mcpServers": {
    "mirage": {
      "type": "http",
      "url": "http://127.0.0.1:8765/v1/workspaces/demo/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}
```

The Python daemon names the session with `?session_id=agent`, the TypeScript daemon with `?sessionId=agent`, the casing each daemon's API uses throughout. An unknown workspace or session answers 404.

## SSH

With the daemon's [SSH door](/home/ssh) on, the `mcp` subsystem serves the workspace the login names over MCP's stdio framing, so any client that launches a command can use it:

```json theme={null}
{
  "mcpServers": {
    "mirage": { "command": "ssh", "args": ["-T", "mirage-demo", "-s", "mcp"] }
  }
}
```

Each channel is a fresh session under the login key's profile ([Bind a key to a profile](/home/ssh#bind-a-key-to-a-profile)), with the environment an `ssh` login gets, and it closes when the channel ends. A key whose profile seals a path gets the same refusal from `read` as from `cat`.

## The same tools over HTTP and the CLI

The tools are not only MCP's. The daemon serves each one but `shell` at
`POST /v1/workspaces/{id}/{read,write,edit,ls,grep,glob}`, with the tool's
input as the JSON body and the session named in the query as on the MCP
endpoint; `shell` is `POST /v1/workspaces/{id}/shell`. The CLI has one
verb per tool (`mirage read -w ID PATH`, `mirage grep -w ID -i PATTERN PATH`,
and so on). Every door calls the same per-session tool table, so a `read`
through one stamps the file for a `write` or `edit` through another.

## Errors

A tool the server does not serve is a protocol error (`-32602 Tool <name> not found`). Arguments outside a tool's input schema, and a failing call, come back as the tool's answer with `isError` set, so the agent reads them and can retry.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.