The [`@air/mcp`](https://www.npmjs.com/package/@air/mcp) package connects MCP-compatible AI clients to an Air workspace. It exposes tools for finding and previewing assets, organizing boards, applying tags and custom fields, importing files from URLs, and uploading local files.

The server supports:

- **STDIO** for clients that launch the package as a local child process. This is the recommended setup for desktop and coding clients.
- **Streamable HTTP** for local HTTP-capable clients and development.

## Prerequisites

- Node.js 18 or newer
- An Air API key and workspace ID. See [Getting Started](https://developer.air.inc/docs/getting-started) for credential setup.

## Configuration

The server reads these environment variables:

| Variable | Required | Description |
| --- | --- | --- |
| `AIR_API_KEY` | Yes | API key used to authenticate with Air |
| `AIR_WORKSPACE_ID` | Yes | Workspace that all MCP tools operate on |
| `AIR_API_URL` | No | API base URL override for non-production environments |

Credentials stay in the MCP server process. Do not place them in prompts or tool arguments.

### Command-line options

| Option | Description |
| --- | --- |
| `--http` | Use Streamable HTTP instead of STDIO |
| `--port <number>` | Set the initial HTTP port. The default is `3000`; if occupied, the server tries up to 10 subsequent ports. |
| `--debug` | In HTTP mode, log parsed MCP requests and responses to stderr |

HTTP mode binds to `127.0.0.1` and serves MCP requests at `/mcp`.

## Set up a client

### Claude Desktop

Add the server to your Claude Desktop config. On macOS, the file is `~/Library/Application Support/Claude/claude_desktop_config.json`.

JSON

```json
{
  "mcpServers": {
    "air": {
      "command": "npx",
      "args": ["-y", "@air/mcp"],
      "env": {
        "AIR_API_KEY": "your-api-key",
        "AIR_WORKSPACE_ID": "your-workspace-id"
      }
    }
  }
}
```

If Claude Desktop does not load the Node.js version from your shell profile, set `command` to the full path of a Node 18+ `npx` executable. See the [Claude Desktop MCP guide](https://modelcontextprotocol.io/docs/develop/connect-local-servers).

### Claude Code

Bash

```shell
claude mcp add \
  --env AIR_API_KEY=your-api-key \
  --env AIR_WORKSPACE_ID=your-workspace-id \
  --transport stdio \
  air \
  -- npx -y @air/mcp
```

See the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp).

### Codex

Add the server to `~/.codex/config.toml`:

TOML

```toml
[mcp_servers.air]
command = "npx"
args = ["-y", "@air/mcp"]

[mcp_servers.air.env]
AIR_API_KEY = "your-api-key"
AIR_WORKSPACE_ID = "your-workspace-id"
```

Or use the CLI:

Bash

```shell
codex mcp add air \
  --env AIR_API_KEY=your-api-key \
  --env AIR_WORKSPACE_ID=your-workspace-id \
  -- npx -y @air/mcp
```

See the [Codex MCP documentation](https://developers.openai.com/codex/mcp).

### Cursor

Add the server to `.cursor/mcp.json` in a project or `~/.cursor/mcp.json` globally:

JSON

See the [Cursor MCP documentation](https://cursor.com/docs/context/mcp).

### Windsurf

Add the server to `~/.codeium/windsurf/mcp_config.json`:

JSON

See the [Windsurf MCP documentation](https://docs.windsurf.com/windsurf/cascade/mcp).

### VS Code with GitHub Copilot

Add the server to `.vscode/mcp.json`, or open the user-level file with **MCP: Open User Configuration**:

JSON

```json
{
  "servers": {
    "air": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@air/mcp"],
      "env": {
        "AIR_API_KEY": "your-api-key",
        "AIR_WORKSPACE_ID": "your-workspace-id"
      }
    }
  }
}
```

Use Copilot in Agent mode to call MCP tools. See the [VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers).

### Zed

Add the server to `~/.config/zed/settings.json`:

JSON

```json
{
  "context_servers": {
    "air": {
      "command": "npx",
      "args": ["-y", "@air/mcp"],
      "env": {
        "AIR_API_KEY": "your-api-key",
        "AIR_WORKSPACE_ID": "your-workspace-id"
      }
    }
  }
}
```

See the [Zed context server documentation](https://zed.dev/docs/ai/mcp).

## Run with Streamable HTTP

Start a local HTTP server with credentials in the process environment:

Bash

```shell
AIR_API_KEY=your-api-key \
AIR_WORKSPACE_ID=your-workspace-id \
npx -y @air/mcp --http
```

Connect a local HTTP-capable MCP client to:

Text

```text
http://localhost:3000/mcp
```

For example, add `--port 4000` to choose another initial port or `--debug` to inspect MCP traffic.

## Tool reference

The server registers 23 tools in six categories. A parameter followed by `?` is optional.

List tools return a JSON object with `data`, `pagination`, and `total`. To request another page, pass the returned pagination `cursor` into the next call.

### Assets

| Tool | Inputs | Description |
| --- | --- | --- |
| `list_assets` | `parentBoardId?`, `includeNestedAssets?`, `tag?`, `customField?`, `search?`, `limit?`, `cursor?`, `includeThumbnails?` | List assets with board, tag, custom-field, and search filters. `tag` and `customField` accept one string or an array. `includeThumbnails` adds inline images for up to the first 10 results. |
| `list_nested_assets` | `boardId`, `limit?`, `cursor?`, `includeThumbnails?` | List assets in a board and all nested sub-boards. Can include up to 10 inline thumbnails. |
| `get_asset` | `assetId` | Get asset details, cover version, metadata, custom fields, and a canonical Air web app `url`. |
| `get_asset_download_url` | `assetId`, `versionId?` | Get a temporary download URL. If `versionId` is omitted, the cover version is used. |
| `get_asset_boards` | `assetId`, `limit?`, `cursor?` | List every board that contains an asset. |
| `get_asset_image` | `assetId`, `size?` | Return an inline base64 image content block. `size` is `thumbnail` (default) or `preview`. |
| `rename_asset` | `assetId`, `title`, `versionId?` | Update only the display title. Defaults to the cover version and does not change the underlying file name. |
| `update_asset` | `assetId`, `title?`, `description?`, `versionId?` | Update the display title and/or description. Defaults to the cover version. |
| `add_tag_to_asset` | `assetId`, `tagId`, `versionId?` | Add a tag to an asset version. Defaults to the cover version. |
| `remove_tag_from_asset` | `assetId`, `tagId`, `versionId?` | Remove a tag from an asset version. Defaults to the cover version. |
| `update_asset_custom_field` | `assetId`, `customFieldId`, `value?`, `values?` | Set or clear a custom field value. Field-specific rules are described below. |

### Boards

| Tool | Inputs | Description |
| --- | --- | --- |
| `list_boards` | `name?`, `parentBoardId?`, `limit?`, `cursor?` | List boards, optionally filtered by name or parent. |
| `get_board` | `boardId` | Get board details and a canonical Air web app `url`. |
| `create_board` | `title`, `description?`, `parentBoardId?` | Create a root board or a board nested under another board. Returns a canonical `url`. |
| `update_board` | `boardId`, `title?`, `description?`, `parentBoardId?` | Update a board. Pass `parentBoardId: null` to move it to the workspace root. |
| `add_asset_to_board` | `boardId`, `assetIds` | Add one or more asset IDs to a board. |

### Tags

| Tool | Inputs | Description |
| --- | --- | --- |
| `list_tags` | `name?`, `limit?`, `cursor?` | List tags, optionally filtered by name. |
| `create_tag` | `name` | Create a tag in the workspace. |

### Custom fields

| Tool | Inputs | Description |
| --- | --- | --- |
| `list_custom_fields` | `limit?`, `cursor?` | List custom fields defined in the workspace. |
| `get_custom_field` | `customFieldId` | Get a field's type and its possible select options. |

### Imports

| Tool | Inputs | Description |
| --- | --- | --- |
| `create_import` | `sourceUrl`, `fileName?`, `ext?`, `recordedAt?`, `parentBoardId?`, `assetId?`, `description?`, `title?`, `customFields?`, `tags?` | Import an asset from a URL. Optionally place it in a board, add it as a new version, and set metadata. Returns an import task ID, asset ID, version ID, and canonical `url`. |
| `get_import_status` | `importId` | Poll an import task. Status is `pending`, `inProgress`, `succeeded`, or `failed`; failures include an error object. |

### Uploads

| Tool | Inputs | Description |
| --- | --- | --- |
| `upload_asset` | `filePath`, `parentBoardId?`, `assetId?`, `customFields?`, `tags?` | Upload a local file. The SDK detects its type and upload strategy. Optionally place it in a board or add it as a new asset version. Returns the asset ID, version ID, and canonical `url`. |

`filePath` must be an absolute path that the MCP server process can read. For STDIO, this is normally the same machine as the client. For HTTP, the path refers to the server machine, not the machine running the remote client.

## Tool behavior

### Canonical Air URLs

`get_asset`, `get_board`, `create_board`, `create_import`, and `upload_asset` return a canonical `url` when applicable. Agents should use this field instead of constructing Air URLs from asset, version, or board IDs.

### Asset images and thumbnails

`get_asset_image` returns an MCP image content block that supported clients can render directly. `list_assets` and `list_nested_assets` can include image content blocks by setting `includeThumbnails: true`.

For list calls, thumbnail fetching is limited to the first 10 returned assets. Images are fetched only from trusted Air or Imgix HTTPS hosts.

### Custom-field values

`update_asset_custom_field` fetches the custom-field definition and validates values before updating the asset:

- For `plain-text` and `date` fields, pass `value`. Pass `null` to clear it.
- For `single-select` and `multi-select` fields, pass `values` as an array of `{ "id": "option-uuid" }` objects. Pass `null` to clear the selection.
- A select field also accepts one option ID through `value`; the server validates the ID and converts it to `values`.
- Do not pass both `value` and `values`.
- Select option IDs are validated against the field's available options.

The `customFields` parameter accepted by `create_import` and `upload_asset` uses an array of objects with this shape:

JSON

```json
[\
  {\
    "id": "text-or-date-field-id",\
    "value": "text-or-date-value"\\
  },\
  {\
    "id": "select-field-id",\
    "values": [{ "id": "select-option-id" }]\
  }\
]
```

Use the member appropriate for the field type. The `tags` parameter for imports and uploads is an array of `{ "id": "tag-id" }` objects.

### Imports and uploads

URL imports are asynchronous. After `create_import`, call `get_import_status` with the returned import task ID until the status is `succeeded` or `failed`.

Pass `assetId` to `create_import` or `upload_asset` to add the new file as a version of an existing asset. Pass `parentBoardId` to place a newly created asset in a board.

For `create_import`, `fileName` excludes the extension, `ext` excludes the leading dot, and `recordedAt` is an ISO 8601 date string.

### Errors

Air API failures are returned as MCP tool errors containing the HTTP status and API error message. Invalid custom-field input is returned as a tool error with guidance about the expected value shape or valid select options.
