> ## Documentation Index
> Fetch the complete documentation index at: https://help.exy.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK and MCP server

> Use the API from TypeScript with @exysh/sdk, or let an AI agent manage your servers with @exysh/mcp.

Two packages on npm build on the [exy.sh API](https://help.exy.sh/api/use-the-exy-api.md). Both are published under the MIT license and need Node.js 22 or newer.

* **[@exysh/sdk](https://www.npmjs.com/package/@exysh/sdk):** a typed client for the API v1, plus a verifier for webhook signatures.
* **[@exysh/mcp](https://www.npmjs.com/package/@exysh/mcp):** an MCP server that gives AI agents (Claude Code, Claude Desktop, Cursor and any other MCP client) tools to manage your servers, orders, invoices and support tickets.

Both work with an API token you create in the dashboard under **Settings → API**. See [Get a token](https://help.exy.sh/api/use-the-exy-api.md#get-a-token).

## TypeScript SDK

```bash theme={null}
npm install @exysh/sdk
```

Use it from your server, never in a browser: it holds your API token. Keep the token out of your source code, for example in the `EXY_API_TOKEN` environment variable.

```ts theme={null}
import { createClient, retryAfterSeconds } from "@exysh/sdk"

const exy = createClient(process.env.EXY_API_TOKEN!)

const { data, error, response } = await exy.GET("/v1/servers", {
  params: { query: { limit: 20 } },
})
if (error) {
  if (response.status === 429) console.log(`Rate limited, retry in ${retryAfterSeconds(response)}s`)
  throw new Error(`exy.sh API ${response.status}: ${JSON.stringify(error)}`)
}
for (const server of data.data) console.log(server.id, server.name, server.status)
```

* **Typed:** every path, parameter, request body and response is typed. The `paths`, `operations` and `webhooks` types are exported too.
* **No exceptions on HTTP errors:** a call resolves to `{ data, error, response }` and never throws on an error status.
* **Options:** the client is built on [openapi-fetch](https://openapi-ts.dev/openapi-fetch/). The second argument of `createClient` accepts its options, for example `baseUrl` or a custom `fetch`.
* **Paid calls:** `POST /v1/orders`, `POST /v1/servers/{id}/renew` and `POST /v1/servers/{id}/upgrades` take an `Idempotency-Key` header. Generate one key per purchase and reuse it when you retry; see [Order and renew servers](https://help.exy.sh/api/order-and-renew-servers.md).
* **Webhooks:** `verifyWebhook` checks the signature and the timestamp of a delivery. Pass the raw body before parsing it; see [Webhooks](https://help.exy.sh/api/webhooks.md).

## MCP server

The MCP server runs locally on your machine over stdio. It talks only to `https://api.exy.sh/v1`, through `@exysh/sdk`, with your own token. Nothing else has to be installed.

**1. Create a token**

Create a token under **Settings → API** with only the scopes the agent needs. `vms.read`, `billing.read` and `tickets.read` are enough to try it out.

**2. Add the server to your client**

The token is read from the `EXY_API_TOKEN` environment variable, never from an argument.

**Claude Code:**

```bash theme={null}
claude mcp add exy --env EXY_API_TOKEN=exy_… -- npx -y @exysh/mcp
```

**Claude Desktop, Cursor and other clients** (JSON config):

```json theme={null}
{
  "mcpServers": {
    "exy": {
      "command": "npx",
      "args": ["-y", "@exysh/mcp"],
      "env": { "EXY_API_TOKEN": "exy_…" }
    }
  }
}
```

Without `EXY_API_TOKEN` the server exits immediately with a message on stderr.

### Scopes and tools

The agent only gets the tools its token's scopes allow.

| Scope | Tools |
| - | - |
| any token | `get_account` |
| `vms.read` | `list_servers`, `get_server`, `get_server_action`, `list_images` |
| `vms.operate` | `power_server` |
| `vms.manage` | `rename_server`, `delete_server`, `reinstall_server`, `set_server_password` |
| `billing.read` | `list_locations`, `list_plans`, `get_quote`, `get_balance`, `list_orders`, `get_order`, `list_invoices`, `get_invoice` |
| `billing.manage` | `order_server`, `renew_server` |
| `tickets.read` | `list_tickets`, `get_ticket` |
| `tickets.manage` | `create_ticket`, `reply_to_ticket` |

> [!WARNING]
>
> `billing.manage` spends real money: with it, the agent can order and renew servers, paid from your balance. `vms.manage` can delete and reinstall servers, which destroys their data. Grant these scopes only to an agent you trust to ask you first, and prefer a separate, short-lived token for them.

### What the server guarantees

* **Confirmation:** `delete_server` and `reinstall_server` require `confirmName`, like the API. The server never fills in the name itself. Whether your client asks you before running a destructive tool is up to the client.
* **Paid calls:** `order_server` and `renew_server` require an `idempotencyKey` from the caller. The server never retries a call and never makes up a key. After a timeout, call again with the same key: the API returns the first result and charges once.
* **Errors:** an error comes back with the HTTP status and the API's `code` and `message`; a `429` also carries `retryAfterSeconds`.
* **Token:** the server logs nothing and never returns the token in a result.


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