# MCP

Emits exposes its verbs as [Model Context Protocol](https://modelcontextprotocol.io)
tools, so an agent client publishes by making tool calls instead of driving
the CLI. There is one set of tools and two transports: a **local** server the
client launches as a subprocess, and a **hosted** endpoint for clients that
cannot.

## The tools

| Tool | What it does | Scope |
| --- | --- | --- |
| `publish` | Create a page from HTML passed **as a string**; returns the live URL | `pages:write` |
| `update` | Replace a page's contents; the URL does not change | `pages:write` |
| `list` | List the account's pages | `pages:read` |
| `unpublish` | Take a page offline | `pages:write` |

Both transports serve exactly these four, identically described: the tool
definitions are shared, so a tool cannot exist locally and be missing
remotely.

## The local server

```bash
emit mcp
```

runs the server on stdio, which every subprocess-capable client can launch
with no port, no TLS, and no inbound network. A typical client configuration:

```json
{
  "mcpServers": {
    "emits": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@mzedstudio/emits-cli", "mcp"]
    }
  }
}
```

Auth is the CLI's: the server reads the credential
[`emit login`](/docs/quickstart#1-log-in) stored in `~/.emits/`, re-reading it
on each call, so logging in from another terminal mid-session is picked up
and credentials never leave the machine or appear on a socket.

## The remote server

Clients that cannot launch a subprocess (browser-hosted connectors, hosted
agents) connect to:

```
https://api.emits.app/mcp
```

There is no key to mint and no client to register by hand; the endpoint
bootstraps a conforming client from a single unauthenticated request:

1. The client `POST`s without a token and receives a `401` whose
   `WWW-Authenticate` header carries a `resource_metadata` URL:

   ```
   WWW-Authenticate: Bearer resource_metadata="https://api.emits.app/.well-known/oauth-protected-resource/mcp"
   ```

2. It fetches that RFC 9728 document and learns which authorization server
   can issue it a token.
3. It registers itself with
   [dynamic client registration](/docs/auth#dynamic-client-registration).
4. It runs the [authorization-code grant with PKCE](/docs/auth#the-authorization-code-grant),
   sending its human to a browser consent screen that names the client and
   the scopes.
5. It retries with the bearer token it was issued.

The token rides the `Authorization` header only: the MCP spec forbids tokens
in the query string, and so does the endpoint.

## Which transport to choose

Prefer the local server whenever the client can launch one: the credential
stays on the machine and there is no OAuth handshake to perform. The hosted
endpoint deliberately makes the opposite trade: it is reachable from
anywhere, and pays for that with the browser consent flow above. Approved
clients appear in the console under **Settings → API access**, one entry per
client, where access can be [revoked](/docs/auth#revocation).
