# How to Set Up the MCP Server: Claude Code, Cursor, Codex

> How to set up the MAS MCP server in Claude Code, Cursor or Codex: connect Android, send your API key as a header, and run a macro from chat.

Source: https://automationmacro.com/docs/mcp (Integrate, updated 2026-09-09)

This page shows how to set up the Macro Automation Studio (MAS) MCP server in Claude Code, Cursor, Codex, Claude Desktop and any other MCP client. The server turns your Android devices, your macro library and the MAS authoring agent into tools an AI client can call. It has two surfaces: the hosted server, with 41 tools on your cloud devices, and the local server the desktop app runs for the emulator or phone on your desk, with 38 tools. The local server exists only while the desktop app is open; any public listing of MAS (an MCP registry or a client's server directory) points at the hosted server. The two share tool names, resources and prompts, so a client that learned one works on the other.

## Before you start

- For the hosted server: a MAS account with at least one cloud device, and an API key created in the app. See [API keys](/docs/api-keys).
- For the local server: the MAS desktop app open and signed in, a running emulator with ADB switched on (or a phone with USB debugging), and the local server switched on under **Integrations (MCP)**. Nothing else to install: the app is the server, and its macro catalogue, Image Library and scheduler are what the tools read and write.
- An MCP client that reaches a server over streamable HTTP by URL, with or without a header. Claude Desktop reaches local servers by command instead; the app provides that command too.

## The two surfaces: cloud devices and a local Android adb MCP server

| | Hosted server | Local server |
|---|---|---|
| How you reach it | `POST https://api.automationmacro.com/mcp` | `POST http://127.0.0.1:<port>/mcp` while the app is open; the port is shown in the app |
| Tools | 41 tools in nine areas: status, device control, screen understanding, macros and runs, the image library, agent authoring, the developer loop, app maps and scheduling | 38 tools: the same areas on the emulators the app sees, without device leases, `get_run` and `get_map` |
| Devices | Your cloud devices | Whatever the app sees over adb |
| Device reference | `lease_id` from `lease_device` on every device tool; macro, validation and schedule tools take the `device_id` from `list_devices` | `device`: the id from `list_devices` (`emulator-21513`), an adb serial (`127.0.0.1:21513`) or the bare port (`21513`); omit it when one emulator is running |
| Auth | Your API key as a request header, or a browser sign-in | None; the server only answers your own machine |
| Resources and prompts | `mas://status` (read it first), `mas://docs/sdk-reference`, `mas://docs/house-library`; prompts `write-macro` and `debug-run` | The same three resources and two prompts; `mas://status` also lists the clients connected to the app |
| Cost | Cloud device time and AI credits are billed like the app; device tools carry no per-call charge | Free; needs the app signed in with an active subscription. `author_macro` uses the provider selected in the app, and only the MAS cloud model spends credits |

Both surfaces speak JSON-RPC 2.0 at protocol version `2025-06-18` on one stateless endpoint: one JSON reply per POST, no batch requests. Three tools are hosted only: `lease_device` and `release_device`, because a cloud device is opened and closed like a session, and `get_map`. `get_run` is the hosted name of the local `get_run_status`. Everything else, macro runs, agent authoring, the image library, the developer loop and scheduling included, exists on both. The full list is on the [tools reference](/docs/mcp/tools).

## Authenticate the hosted server with your API key

The hosted server takes the API key itself, on every request, in either of two headers:

```text
X-API-Key: mas_your_key
Authorization: Bearer mas_your_key
```

That is the shape every client already supports for fixed credentials: Claude Code's `--header`, Cursor's `headers`, Codex's bearer environment variable, the Anthropic API's `authorization_token`, and the `headers` dictionary of the OpenAI Agents SDK. The key carries its scopes, its expiry and its revocation, so nothing expires hourly and there is nothing to refresh.

Create the key in MAS under **Integrations (MCP)** or **API Keys**: enter a name, tick the scopes you want (none ticked means full access) and copy the key; it starts with `mas_` and is shown once.

Clients that implement the MCP client-credentials extension can instead exchange the key for a one-hour token at `POST https://api.automationmacro.com/api/mcp/oauth/token` with `grant_type=client_credentials` and the key as `client_secret`, then send the token as a bearer header. The token dies with the key: revoke the key and every token minted from it stops at once. Discovery documents sit under `/.well-known` on the same host.

Clients that sign in interactively work too. Claude.ai, Claude Desktop and Claude Code's `/mcp` login open a browser tab on the hosted server's sign-in page; you sign in with your MAS account (email and password, or Google), approve the scopes, and the client receives its own tokens. Nothing to paste, and tokens renew themselves. Connected apps are listed under **Integrations (MCP)** in the desktop app, where you can disconnect one at any time.

## How to set up the MCP server in Claude Code, Cursor and Codex

Each tab shows the hosted server first and the local server second. Replace `<your key>` with your API key, and `<port>` with the port the **Integrations (MCP)** page shows for the local server.

<div class="doc-tabs" data-tabs="client">
<section data-tab="Claude Code">

Add the hosted server, then sign in from inside Claude Code:

```bash
claude mcp add --transport http mas-cloud https://api.automationmacro.com/mcp
```

Run `/mcp`, pick `mas-cloud` and choose **Authenticate**: a browser tab opens on the MAS sign-in page, you approve the scopes, and Claude Code keeps its own tokens from then on. If you prefer a fixed key (scripts, CI), add the header instead and skip the sign-in:

```bash
claude mcp add --transport http mas-cloud https://api.automationmacro.com/mcp --header "X-API-Key: <your key>"
```

Add the local server by URL while the app is open:

```bash
claude mcp add --transport http mas-local http://127.0.0.1:<port>/mcp
```

Check the result with `/mcp` inside Claude Code, then ask it to list your devices and take a screenshot.

</section>
<section data-tab="Cursor">

Nothing to create or edit: click a link and Cursor installs the server itself.

- **[Add mas-cloud to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=mas-cloud&config=eyJ1cmwiOiJodHRwczovL2FwaS5hdXRvbWF0aW9ubWFjcm8uY29tL21jcCJ9)**, the hosted server. It appears under **Customize → MCP** marked *Needs login*; click that and a browser tab opens on the MAS sign-in page. Sign in with your account (email and password, or Google), approve the scopes, and Cursor keeps its own tokens from then on. No key to paste.
- **[Add mas-local to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=mas-local&config=eyJ1cmwiOiJodHRwOi8vMTI3LjAuMC4xOjM4NDYvbWNwIn0=)**, the local server on its default port, 3846. The app must be open with the local server switched on (**Integrations (MCP)**). If you changed the port there, adjust the URL in Cursor after installing.

The links are the same on macOS and Windows. Enable, disable or remove either server from **Customize** in Cursor's sidebar; both list their tools there once connected.

Prefer a fixed key instead of signing in (scripts, shared machines)? Cursor also reads `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` for every project:

```json
{
  "mcpServers": {
    "mas-cloud": {
      "url": "https://api.automationmacro.com/mcp",
      "headers": { "X-API-Key": "<your key>" }
    },
    "mas-local": {
      "url": "http://127.0.0.1:<port>/mcp"
    }
  }
}
```

VS Code takes the same two entries through **MCP: Add Server → HTTP**.

</section>
<section data-tab="Codex">

Codex CLI adds a streamable HTTP server with `--url` and reads a bearer value from an environment variable. Put your key in `MAS_API_KEY`; the hosted server accepts the key as a bearer:

```bash
codex mcp add mas-cloud --url https://api.automationmacro.com/mcp --bearer-token-env-var MAS_API_KEY
codex mcp add mas-local --url http://127.0.0.1:<port>/mcp
```

Both land in `~/.codex/config.toml`:

```toml
[mcp_servers.mas-cloud]
url = "https://api.automationmacro.com/mcp"
bearer_token_env_var = "MAS_API_KEY"

[mcp_servers.mas-local]
url = "http://127.0.0.1:<port>/mcp"
```

Export `MAS_API_KEY` before you start Codex.

</section>
<section data-tab="Hermes Agent">

Hermes Agent reads `~/.hermes/config.yaml`. Add the hosted server with the key as a header, and the local server by URL:

```yaml
mcp_servers:
  mas-cloud:
    url: "https://api.automationmacro.com/mcp"
    headers:
      X-API-Key: "<your key>"
  mas-local:
    url: "http://127.0.0.1:<port>/mcp"
```

Prefer signing in instead of a key? Replace the `headers` block with `auth: oauth` and run `hermes mcp login mas-cloud`; the browser opens the MAS sign-in page. After editing the file, type `/reload-mcp` in Hermes and ask it to list your devices.

</section>
<section data-tab="Claude Desktop">

Claude Desktop launches local servers by command, and only from its config file; there is no in-app form for local servers yet. You don't have to find the file yourself: open **Settings → Developer → Edit Config** and Claude Desktop opens `claude_desktop_config.json` for you, creating it if it doesn't exist. The **Integrations (MCP)** page in the app gives you the exact block to paste, with the app's own path; it looks like this on Windows:

```json
{
  "mcpServers": {
    "mas-local": {
      "command": "C:\\Program Files\\MacroAutomationStudio\\MacroAutomationStudio.exe",
      "args": ["--mcp"]
    }
  }
}
```

The `--mcp` command is a thin bridge to the app's local server, so the app must be open and the local server switched on. Restart Claude Desktop after editing the file.

For cloud devices, add the hosted server as a custom connector. In Claude Desktop or Claude.ai open **Settings → Connectors → Add custom connector**, enter a name and the URL `https://api.automationmacro.com/mcp`, click **Add**, then **Connect**. A browser tab opens on the MAS sign-in page: sign in with your account (email and password, or Google), approve the scopes, and the connector is ready. On a Team or Enterprise plan an owner adds the connector under **Organization settings → Connectors** and each member clicks **Connect** to sign in as themselves.

</section>
<section data-tab="Your own agent">

Any agent framework that speaks MCP over HTTP works with the key in a header. With the Anthropic API's MCP connector the key goes in `authorization_token`:

```python
import anthropic

client = anthropic.Anthropic()
response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Take a screenshot of my first cloud device."}],
    mcp_servers=[{
        "type": "url",
        "url": "https://api.automationmacro.com/mcp",
        "name": "mas-cloud",
        "authorization_token": "<your key>",
    }],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "mas-cloud"}],
    betas=["mcp-client-2025-11-20"],
)
```

With the OpenAI Agents SDK:

```python
from agents.mcp import MCPServerStreamableHttp

async with MCPServerStreamableHttp(params={
    "url": "https://api.automationmacro.com/mcp",
    "headers": {"X-API-Key": "<your key>"},
}) as server:
    ...
```

The raw protocol is plain HTTP. Send JSON-RPC 2.0 requests as `POST` with the key header and `Content-Type: application/json`; each POST gets one JSON body back. Call `initialize`, then `tools/list`, then `tools/call`; resources come from `resources/list` and `resources/read`, prompts from `prompts/list` and `prompts/get`.

```bash
curl -s -X POST https://api.automationmacro.com/mcp \
  -H "X-API-Key: $MAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

</section>
</div>

## Try it

Ask your client for the status first: `get_status` (or the `mas://status` resource) says whether the account can run anything, which devices the server reaches, and, on the hosted server, that emulators on your desk are only reachable through the desktop app's local server. Then ask it to list devices: `list_devices` returns your cloud devices with their state on the hosted server, or what the app sees over adb on the local server. Then ask for a screenshot; the image comes back as MCP image content the model looks at directly. Pass `max_width` (200 to 2000) for a smaller image; the reply then carries the scale, and tap coordinates stay in device pixels. When the screen has real widgets, `get_ui_tree` and `find_element` read buttons, fields and labels from the accessibility tree with exact tap bounds, cheaper than a screenshot; games and canvas views expose little there, so use `screenshot`, `read_text` and `find_image` on them.

On the hosted server every device tool needs a lease. `lease_device` opens a control session on one cloud device and returns a `lease_id`; `auto_start` boots a stopped device first. A lease renews on every call and expires after 10 idle minutes; one lease per device at a time, and a device with a macro run in progress cannot be leased until the run ends. `release_device` ends it and the device keeps running. The typical loop is `list_devices`, `lease_device`, `screenshot`, `tap` or `type_text`, then `release_device`.

On the local server there is no lease. Every device-bound tool takes `device`: the id `list_devices` returned (`emulator-21513`), an adb serial (`127.0.0.1:21513`) or the bare port (`21513`), and you can leave it out when exactly one emulator is running. An emulator the scanner misses can still be addressed by its adb serial. A device with a macro run in progress is busy for the other device tools until the run ends.

Running a saved macro through `run_macro` costs zero AI credits. `author_macro` starts a MAS Agent session: on the hosted server it spends credits as it works; on the local server it uses the provider selected in the app, so a bring-your-own model runs free. See [MAS Agent](/docs/agent).

## Write and run macros from your client: the developer loop

Both servers carry the tools a client needs to write a macro itself, in the same house layout MAS Agent builds with, and prove it on a device. The loop is scaffold, install, validate, then run or schedule.

1. **Read the house docs.** `mas://docs/sdk-reference` is the complete reference of the pre-installed `mas` SDK: device control, vision, elements, app management, storage, types and exceptions. `mas://docs/house-library` holds the scheduler skeleton, the activity runner and the runtime-UI template the scaffold ships verbatim. Clients that support MCP prompts can load `write-macro` (arguments `goal`, and `app_package` when known): it briefs the model on the layout, the activity contract and the rules that fail device runs before it writes a line.
2. **Scaffold.** `scaffold_macro {name, activities, description, app_package}` returns the project skeleton as `{files: {path: content}}`: `src/app.py` wiring, one `src/activities/<name>.py` stub per activity, `src/utils/common_logic.py`, `src/utils/images.py`, the scheduler library, `task.yaml`, `requirements.txt` and `README.md`. Nothing is saved anywhere; the client fills in the activities.
3. **Harvest what the activities will find on.** Explore the app with `screenshot`, `get_ui_tree`, `find_element` and `read_text`. Crop a control with `crop_image {region, name, folder}`: it saves the rectangle from the current screen into your Image Library and returns an `image_id`. `find_image {image_id}` and `wait_for {image_id}` locate it from then on, and the macro references the same id as an uppercase constant in `src/utils/images.py`. `list_images` and `get_image` browse the library.
4. **Install.** `install_macro {name, files}` creates a code macro through the same create and code-sync path the app uses (2 MB of text files, syntax-checked on intake; a rejected install names the file and line). `install_macro {macro_id, files}` replaces the code of one of yours. The local server also takes `project_dir`, a folder on your PC that holds `src/app.py` and `task.yaml`.
5. **Validate.** `validate_macro` runs the macro and waits for the verdict: `{passed, exit_code, log_tail}`. Hosted: `{macro_id, device_id, timeout_s, args}`, one run on a cloud device of yours, 300 seconds by default; a run still going at the timeout is stopped and reported as timed out. Local: `{macro_id | project_dir, device, runs, timeout_s}`, the app's own validation job, which also checks `task.yaml`; `runs` goes from 1 to 5. Read `log_tail`, or `get_run_log` for more, fix the activity, re-install and validate again.
6. **Run or schedule.** `run_macro` starts it now and returns a `run_id` to poll (`get_run` hosted, `get_run_status` local) and to read with `get_run_log`. `schedule_macro {macro_id, device_id | device, at, every, timezone, weekdays}` runs it without you: once at `at` (RFC 3339), or daily, weekly or monthly in an IANA timezone. There is no hourly recurrence; a macro that repeats within the day recurs itself. Missed occurrences are skipped and a job pauses after three consecutive failures. Hosted jobs run on a cloud device with your PC off; local jobs fire only while the app is open. `list_schedules` and `cancel_schedule` manage both kinds.

When a run fails, the `debug-run` prompt (argument `run_id`) walks the model through the log with `get_run_log`, classifies the failure, and asks it to prove the fix with `validate_macro` rather than guess.

> [!NOTE]
> On the hosted server, `get_ui_tree`, `find_element` and `wait_for` with an element target need a device on the current device image; an older box answers with an error that says so, and a screenshot still works. `crop_image`, `validate_macro` and `schedule_macro` need a cloud device of yours. On the local server every tool works on any emulator the app sees.

### Run a project from a terminal

The `mas` SDK ships a command for the same loop outside MCP. `mas run <project folder> --device 21513` runs `src/app.py` the way the Run button does: the project is the working directory and is on `PYTHONPATH`, and the script is attached to the Studio app that is open on this PC. The app publishes its engine while it runs in `engine.json` under `%APPDATA%\MacroAutomationStudio` on Windows or `~/Library/Application Support/MacroAutomationStudio` on Mac, so nothing is configured by hand. `--device` takes an emulator port, an adb serial or `desktop`, and falls back to `MAS_DEVICE`; everything after `--args` (or a bare `--`) goes to the script. `mas status` shows the engine the SDK would attach to. The app must be open and signed in; a stale `engine.json` is reported with the fix (restart the app).

The command arrives with the SDK, and the SDK is already installed in the Python environment the app keeps for macros. That environment is where `mas` lives:

```text
Windows   C:\ProgramData\MacroAutomationStudio\python_venv\Scripts\mas.exe
Mac       ~/Library/Application Support/MacroAutomationStudio/python_venv/bin/mas
```

Call it by that full path, or put the folder on your `PATH` and type `mas`. The interpreter beside it runs the same entry point, so `C:\ProgramData\MacroAutomationStudio\python_venv\Scripts\python.exe -m mas run <project folder>` does exactly what `mas run` does; use that form if the folder holds `python.exe` but no `mas.exe`, which means the SDK installed with your copy of the app predates the command and the app is due an update. Any other Python works too once you install `mas-sdk` into it.

## Scopes

A key carries the scopes you tick when you create it; none ticked means full access. The hosted server checks the scope before every tool call and returns an error result naming the missing scope. The [REST API](/docs/rest-api) enforces the same scopes on its routes. The local server has no scopes.

| Scope | Tools it gates |
|---|---|
| `devices:control` | Device control and screen understanding: leases, screenshots, taps, swipes, long presses, drags, typing, keys, apps, OCR, template matching, the UI tree and `wait_for`; the image library (`list_images`, `get_image`, `crop_image`) |
| `macros:run` | Macros and runs: list and read macros, start, check and stop runs, `validate_macro`, `get_run_log`; scheduling (`schedule_macro`, `list_schedules`, `cancel_schedule`) |
| `macros:author` | Agent authoring: start, follow, answer and stop a session; the developer loop's `scaffold_macro` and `install_macro` |
| `maps:read` | App maps: `get_map` |
| `runs:read` | REST only: list and read run history and logs |

## Troubleshooting

### 401 or invalid token

The endpoint answered `{"error":"invalid_token"}`: no `X-API-Key` header and no `Authorization` header reached the server, the key is unknown, revoked or expired, or a token from the exchange has expired or belongs to a revoked key. Check the header name and value, or create a new key in the app. For a signed-in client (Claude.ai, Claude Desktop, Claude Code), disconnect and connect again; if the app was disconnected under **Integrations (MCP) → Connected apps**, that is why.

### The sign-in page rejects the password

The sign-in page uses the same account as the desktop app. If you signed up with Google, use **Continue with Google**. Too many failed attempts pause sign-in for a minute.

### 402 or 429

`402` with `subscription_required` means the key's owner has no active subscription; `429` with `rate_limit_exceeded` means the key or the account is over its per-minute limit and carries `Retry-After`. Both are the [API key](/docs/api-keys) rules; the MCP door applies the same ones.

### No devices listed

On the hosted server, `list_devices` returns your cloud devices; an empty list means the account has none. Create one on the **Cloud Devices** page; see [Cloud devices](/docs/cloud-devices). A stopped device can still be leased with `auto_start`. "Cloud devices are not enabled on this deployment" means the server you are talking to has no cloud fleet; use the local server. On the local server, the tool returns what the app sees: switch ADB on in the emulator and confirm the device shows in the app's device list, or pass the adb serial (`127.0.0.1:<port>`) as `device`.

### Local server refuses to connect

The app must be open, signed in, and the local server switched on under **Integrations (MCP)**. The server answers only your own machine: a request from another host, or a browser page on another origin, is refused.

### Lease expired

A device tool answered "lease not found or expired". More than 10 idle minutes passed since the last call, the lease belongs to a different key owner, or the server restarted and dropped its in-memory leases. Call `lease_device` again and use the new `lease_id`; nothing on the device is lost.

### This device's box agent predates dump_hierarchy

`get_ui_tree`, `find_element` or an element `wait_for` on the hosted server hit a cloud device whose box has not received the current device image yet. Screenshots, OCR and template matching still work on it; the tree tools work once the box is cycled or the fleet update reaches it.

### Device busy or lease refused

On the hosted server a device with a macro run in progress, or one already leased, cannot be leased until the run ends or the lease is released. On the local server `run_macro` and `validate_macro` keep the emulator busy for the other device tools until the run ends; `stop_run` frees it.

## Next

The [tools reference](/docs/mcp/tools) lists every tool with its parameters, surface and scope. [API keys](/docs/api-keys) covers key expiry, revocation and limits. The [MCP page](/mcp) has the overview and the FAQ.
