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

# Integrations

> MCP server integrations at workspace, crew, and agent levels, plus OAuth flow.

Integrations connect agents to external tools through MCP (Model Context Protocol) servers. Crewship uses a three-tier model: workspace-level MCP servers cascade down to crews, which cascade down to agent bindings, and each level can override or extend the configuration. Servers can be defined directly, discovered from the MCP registry, and authenticated with stored credentials or a full OAuth 2.0 flow.

<Note>
  All integration endpoints require authentication and workspace context (except where a step explicitly states otherwise, such as the OAuth callback).
</Note>

## Endpoints

| Method   | Endpoint                                                                                                | Purpose                                        |
| -------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `GET`    | [`/api/v1/integrations`](#list-workspace-integrations)                                                  | List workspace MCP servers                     |
| `POST`   | [`/api/v1/integrations`](#create-workspace-integration)                                                 | Create a workspace MCP server                  |
| `GET`    | [`/api/v1/integrations/{integrationId}`](#get-workspace-integration)                                    | Get a workspace integration                    |
| `PATCH`  | [`/api/v1/integrations/{integrationId}`](#update-workspace-integration)                                 | Update a workspace integration                 |
| `DELETE` | [`/api/v1/integrations/{integrationId}`](#delete-workspace-integration)                                 | Delete a workspace integration                 |
| `POST`   | [`/api/v1/integrations/{integrationId}/test`](#test-workspace-integration)                              | Test a workspace MCP connection                |
| `GET`    | [`/api/v1/integrations/crews`](#all-crew-integrations)                                                  | Cross-crew integration overview                |
| `GET`    | [`/api/v1/crews/{crewId}/integrations`](#list-crew-integrations)                                        | List crew MCP bindings                         |
| `POST`   | [`/api/v1/crews/{crewId}/integrations`](#create-crew-integration)                                       | Create a crew MCP binding                      |
| `PATCH`  | [`/api/v1/crews/{crewId}/integrations/{integrationId}`](#update-crew-integration)                       | Update a crew binding                          |
| `DELETE` | [`/api/v1/crews/{crewId}/integrations/{integrationId}`](#delete-crew-integration)                       | Delete a crew binding                          |
| `POST`   | [`/api/v1/crews/{crewId}/integrations/{integrationId}/test`](#test-crew-integration)                    | Test a crew MCP connection                     |
| `GET`    | [`/api/v1/crews/{crewId}/integrations/{integrationId}/tools`](#list-crew-integration-tools)             | List per-tool bindings                         |
| `PATCH`  | [`/api/v1/crews/{crewId}/integrations/{integrationId}/tools/{toolName}`](#update-crew-integration-tool) | Toggle a single tool                           |
| `POST`   | [`/api/v1/crews/{crewId}/integrations/{integrationId}/tools/refresh`](#refresh-crew-integration-tools)  | Reconcile tool bindings                        |
| `GET`    | [`/api/v1/agents/{agentId}/integrations`](#list-agent-bindings)                                         | List agent MCP bindings                        |
| `POST`   | [`/api/v1/agents/{agentId}/integrations`](#create-agent-binding)                                        | Create an agent binding                        |
| `PATCH`  | [`/api/v1/agents/{agentId}/integrations/{integrationId}`](#update-agent-binding)                        | Update an agent binding                        |
| `DELETE` | [`/api/v1/agents/{agentId}/integrations/{integrationId}`](#delete-agent-binding)                        | Delete an agent binding                        |
| `GET`    | [`/api/v1/agents/{agentId}/integrations/resolved`](#resolve-agent-integrations)                         | Resolve effective integrations                 |
| `GET`    | [`/api/v1/oauth/providers`](#list-oauth-providers)                                                      | List OAuth providers                           |
| `POST`   | [`/api/v1/oauth/initiate`](#initiate-oauth-flow)                                                        | Start an OAuth flow                            |
| `GET`    | [`/api/v1/oauth/callback`](#oauth-callback)                                                             | Handle the OAuth callback                      |
| `POST`   | [`/api/v1/oauth/exchange`](#exchange-token)                                                             | Exchange code for tokens                       |
| `POST`   | [`/api/v1/oauth/loopback`](#loopback)                                                                   | Handle loopback redirect                       |
| `POST`   | [`/api/v1/oauth/discover`](#discover-oauth-endpoints)                                                   | Discover OAuth endpoints                       |
| `POST`   | [`/api/v1/oauth/auto-connect`](#auto-connect-integration)                                               | Create integration from OAuth                  |
| `GET`    | [`/api/v1/mcp-registry`](#list-registry-entries)                                                        | List MCP registry entries                      |
| `GET`    | [`/api/v1/mcp-registry/search`](#search-registry)                                                       | Search the MCP registry                        |
| `POST`   | [`/api/v1/mcp-registry/sync`](#sync-registry)                                                           | Sync the MCP registry                          |
| `GET`    | [`/api/v1/integrations/composio/inventory`](#composio-inventory)                                        | Composio catalog + connected accounts per user |
| `GET`    | [`/api/v1/integrations/composio/toolkits`](#composio-toolkits)                                          | Browse the Composio app catalog (1000+ apps)   |
| `GET`    | [`/api/v1/integrations/composio/tools`](#composio-tools)                                                | List the tools a toolkit exposes               |
| `GET`    | [`/api/v1/integrations/composio/triggers`](#composio-triggers)                                          | List available trigger types                   |
| `GET`    | [`/api/v1/integrations/composio/triggers/active`](#composio-active-triggers)                            | List active trigger instances                  |
| `POST`   | [`/api/v1/integrations/composio/triggers`](#composio-create-trigger)                                    | Create/enable a trigger instance for a user    |
| `POST`   | [`/api/v1/integrations/composio/connect`](#composio-connect)                                            | Start an OAuth Connect Link for an app/user    |
| `GET`    | [`/api/v1/integrations/composio/settings`](#composio-settings)                                          | Get the workspace Composio config status       |
| `PUT`    | [`/api/v1/integrations/composio/settings`](#composio-settings)                                          | Set & validate the workspace Composio API key  |
| `DELETE` | [`/api/v1/integrations/composio/settings`](#composio-settings)                                          | Remove the workspace API key (revert to env)   |
| `GET`    | [`/api/v1/integrations/composio/default`](#composio-default-connector)                                  | Inspect the default-connector state            |
| `PUT`    | [`/api/v1/integrations/composio/default`](#composio-default-connector)                                  | Provision/re-point the default connector       |
| `GET`    | [`/api/v1/integrations/composio/agents/{agentId}/bind`](#composio-agent-binding)                        | List an agent's Composio user binding(s)       |
| `POST`   | [`/api/v1/integrations/composio/agents/{agentId}/bind`](#composio-agent-binding)                        | Bind a Composio user to an agent               |
| `DELETE` | [`/api/v1/integrations/composio/agents/{agentId}/bind`](#composio-agent-binding)                        | Unbind a Composio user from an agent           |
| `POST`   | [`/api/v1/integrations/composio/accounts/{accountId}/revoke`](#composio-connected-account-management)   | Revoke (de-authorize) a connected account      |
| `POST`   | [`/api/v1/integrations/composio/accounts/{accountId}/refresh`](#composio-connected-account-management)  | Refresh a connected account's credentials      |
| `DELETE` | [`/api/v1/integrations/composio/accounts/{accountId}`](#composio-connected-account-management)          | Delete a connected account                     |

***

## Managed Integrations (Composio)

Read-only inventory of the Composio managed-integration provider. See the
[Integrations guide](/guides/integrations#managed-integrations-composio) for the
object model. Requires `COMPOSIO_API_KEY` on the server; when unset the endpoint
returns `enabled: false` with empty lists rather than an error.

### Composio Inventory

```
GET /api/v1/integrations/composio/inventory?workspace_id={workspaceId}
```

Returns the connector catalog (auth configs) and every connected account
grouped by Composio `user_id`. Gated on workspace `read`. This is an operator
view; agents are scoped to a single `user_id` and never receive the full list.

**Response:** `200 OK`

```json theme={null}
{
  "enabled": true,
  "auth_configs": [
    {
      "id": "ac_JE6J7fDSsneA",
      "name": "gmail-i6p1sb",
      "status": "ENABLED",
      "toolkit": { "slug": "gmail", "logo": "https://logos.composio.dev/api/gmail" }
    }
  ],
  "users": [
    {
      "user_id": "pg-test-8acca167",
      "connected_accounts": [
        {
          "id": "ca_2pjydr0oHqiI",
          "user_id": "pg-test-8acca167",
          "status": "ACTIVE",
          "toolkit": { "slug": "gmail" },
          "auth_config": { "id": "ac_JE6J7fDSsneA", "auth_scheme": "OAUTH2", "is_composio_managed": true, "is_disabled": false }
        }
      ]
    }
  ]
}
```

| Field          | Type    | Description                                                           |
| -------------- | ------- | --------------------------------------------------------------------- |
| `enabled`      | boolean | Whether the provider is configured (`COMPOSIO_API_KEY` set)           |
| `auth_configs` | array   | Connector catalog — one entry per configured app                      |
| `users`        | array   | Connected accounts grouped by Composio `user_id`, sorted by `user_id` |

CLI: `crewship integration composio inventory`.

### Composio Toolkits

```
GET /api/v1/integrations/composio/toolkits?workspace_id={workspaceId}&search={q}&category={cat}&limit={n}
```

Proxies the Composio app catalog (1000+ connectable apps). `search`, `category`,
and `limit` (max 100, default 40) are optional server-side filters. Read-gated;
returns `enabled: false` when the provider is unconfigured.

**Response:** `200 OK`

```json theme={null}
{
  "enabled": true,
  "total": 1047,
  "toolkits": [
    {
      "slug": "github",
      "name": "GitHub",
      "no_auth": false,
      "meta": {
        "description": "Code hosting platform…",
        "logo": "https://logos.composio.dev/api/github",
        "tools_count": 846,
        "categories": [{ "id": "developer-tools", "name": "developer tools" }]
      }
    }
  ]
}
```

| Field      | Type    | Description                                            |
| ---------- | ------- | ------------------------------------------------------ |
| `enabled`  | boolean | Whether the provider is configured                     |
| `total`    | integer | Total apps matching the filter in the Composio catalog |
| `toolkits` | array   | The current page of apps (slug, name, meta)            |

CLI: `crewship integration composio toolkits [--search] [--category] [--limit]`.

### Composio Tools

```
GET /api/v1/integrations/composio/tools?workspace_id={workspaceId}&toolkit={slug}&search={q}&limit={n}
```

Lists the tools a single toolkit exposes (e.g. `github` has 846, `gmail` 61).
`toolkit` is **required** (`400` otherwise). `search` and `limit` (max 100,
default 40) are optional server-side filters. Read-gated; returns
`enabled: false` when the provider is unconfigured.

**Response:** `200 OK`

```json theme={null}
{
  "enabled": true,
  "total": 846,
  "tools": [
    {
      "slug": "GITHUB_CREATE_AN_ISSUE",
      "name": "Create an issue",
      "description": "Create a new issue in a repository",
      "toolkit": { "slug": "github" }
    }
  ]
}
```

| Field     | Type    | Description                                                  |
| --------- | ------- | ------------------------------------------------------------ |
| `enabled` | boolean | Whether the provider is configured                           |
| `total`   | integer | Total tools matching the filter for the toolkit              |
| `tools`   | array   | The current page of tools (slug, name, description, toolkit) |

CLI: `crewship integration composio tools <toolkit> [--search] [--limit]`.

### Composio Triggers

```
GET /api/v1/integrations/composio/triggers?workspace_id={workspaceId}&toolkit={slug}&search={q}&limit={n}
```

Lists the available **trigger types** — event subscriptions a toolkit exposes
(e.g. `GMAIL_NEW_MESSAGE`, `GITHUB_PR_OPENED`). `toolkit`, `search`, and
`limit` (max 100, default 40) are optional server-side filters. Read-gated;
returns `enabled: false` when the provider is unconfigured.

**Response:** `200 OK`

```json theme={null}
{
  "enabled": true,
  "total": 12,
  "triggers": [
    {
      "slug": "GMAIL_NEW_GMAIL_MESSAGE",
      "name": "New Gmail message",
      "description": "Triggers when a new email arrives",
      "type": "poll",
      "toolkit": { "slug": "gmail" }
    }
  ]
}
```

| Field      | Type    | Description                                                                |
| ---------- | ------- | -------------------------------------------------------------------------- |
| `enabled`  | boolean | Whether the provider is configured                                         |
| `total`    | integer | Total trigger types matching the filter                                    |
| `triggers` | array   | The current page of trigger types (slug, name, description, type, toolkit) |

CLI: `crewship integration composio triggers types [--toolkit] [--search] [--limit]`.

### Composio Active Triggers

```
GET /api/v1/integrations/composio/triggers/active?workspace_id={workspaceId}
```

Lists the **live trigger instances** in the project across all users. Read-gated;
returns `enabled: false` when the provider is unconfigured.

**Response:** `200 OK`

```json theme={null}
{
  "enabled": true,
  "triggers": [
    {
      "id": "ti_abc123",
      "trigger_name": "GMAIL_NEW_GMAIL_MESSAGE",
      "user_id": "user-1",
      "connected_account_id": "ca_1",
      "trigger_config": { "interval": 60 }
    }
  ]
}
```

| Field      | Type    | Description                                                                                                       |
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `enabled`  | boolean | Whether the provider is configured                                                                                |
| `triggers` | array   | The active trigger instances (id, trigger\_name, user\_id, connected\_account\_id, trigger\_config, disabled\_at) |

CLI: `crewship integration composio triggers active`.

### Composio Create Trigger

```
POST /api/v1/integrations/composio/triggers?workspace_id={workspaceId}
```

Creates (or re-enables) a trigger instance for a Composio user. **OWNER/ADMIN
only.** Returns `400` if `slug` or `user_id` is empty, or if the provider is
unconfigured.

**Request body:**

```json theme={null}
{
  "slug": "GMAIL_NEW_GMAIL_MESSAGE",
  "user_id": "user-1",
  "config": { "interval": 60 }
}
```

| Field     | Type   | Description                                        |
| --------- | ------ | -------------------------------------------------- |
| `slug`    | string | Trigger-type slug to enable (**required**)         |
| `user_id` | string | Composio user the trigger fires for (**required**) |
| `config`  | object | Trigger-type-specific configuration (optional)     |

**Response:** `200 OK`

```json theme={null}
{
  "enabled": true,
  "trigger": {
    "id": "ti_new",
    "trigger_name": "GMAIL_NEW_GMAIL_MESSAGE",
    "user_id": "user-1",
    "trigger_config": { "interval": 60 }
  }
}
```

CLI: `crewship integration composio triggers enable <slug> --user <id>`.

### Composio Connect

```
POST /api/v1/integrations/composio/connect?workspace_id={workspaceId}
```

Starts an OAuth Connect Link for an app and user. Requires workspace **manage**
(OWNER/ADMIN). Finds the toolkit's auth config (creating a managed one on demand
if none exists), then creates a Composio Connect Link for the given user.

**Request body:**

```json theme={null}
{ "toolkit": "gmail", "user_id": "user-42" }
```

| Field     | Type   | Description                                               |
| --------- | ------ | --------------------------------------------------------- |
| `toolkit` | string | App slug to authorize (**required**)                      |
| `user_id` | string | Composio user to connect the account under (**required**) |

Returns `400` if either field is empty, or if Composio is unconfigured.

**Response:** `200 OK`

```json theme={null}
{
  "redirect_url": "https://…",
  "connected_account_id": "ca_…",
  "user_id": "user-42"
}
```

The end-user opens `redirect_url` to authorize. Returns `502` on an upstream
Composio failure. CLI: `crewship integration composio connect <toolkit> --user <id>`.

### Composio Settings

Manage the **per-workspace** Composio API key from the app instead of the
server env. The key is **validated against Composio** before being stored
**encrypted** (AES-GCM). The effective key is resolved per request: the
workspace key first, then the server `COMPOSIO_API_KEY` env fallback. The key
is never returned by any endpoint.

```
GET    /api/v1/integrations/composio/settings?workspace_id={workspaceId}
PUT    /api/v1/integrations/composio/settings?workspace_id={workspaceId}
DELETE /api/v1/integrations/composio/settings?workspace_id={workspaceId}
```

`GET` (read) returns the status; `PUT`/`DELETE` require workspace **manage**
(OWNER/ADMIN). `PUT` body: `{ "api_key": "ak_…", "label": "" }`.

The Composio host is server-controlled via the `COMPOSIO_BASE_URL` env var, not
via this API.

**Response (GET / PUT / DELETE):** `200 OK`

```json theme={null}
{ "configured": true, "source": "workspace", "label": "Crewship_dev_1" }
```

| Field        | Type    | Description                                    |
| ------------ | ------- | ---------------------------------------------- |
| `configured` | boolean | Whether a usable key exists (workspace or env) |
| `source`     | string  | `workspace`, `env`, or `none`                  |
| `label`      | string  | Optional project label (workspace source only) |

A `PUT` with an invalid key returns `400` (Composio rejected it). CLI:
`crewship integration composio key {show,set,remove}`.

### Composio Default Connector

Inspect / provision the **workspace-wide default connector**. When the server
flag `COMPOSIO_DEFAULT_CONNECTOR` is **ON**, every agent **without** an explicit
per-agent binding inherits this connector (full access to all the workspace's
connected apps), and legacy non-Composio MCP servers are turned **off** at
resolve time (not deleted). See the
[Integrations guide](/guides/integrations#default-connector-composio_default_connector).

```
GET /api/v1/integrations/composio/default?workspace_id={workspaceId}
PUT /api/v1/integrations/composio/default?workspace_id={workspaceId}
```

`GET` (read) reports state; `PUT` requires workspace **manage** (OWNER/ADMIN).
`PUT` body is optional: `{ "user_id": "<id>" }` pins the default Composio user;
omit it to **auto-derive** the user when exactly one is connected. `PUT` errors
`400` when zero users are connected ("connect an account first") or when
multiple users exist (pin one). Re-running is idempotent and refreshes the
connector's app set.

**Response (GET / PUT):** `200 OK`

```json theme={null}
{ "enabled_flag": true, "default_user_id": "user-a", "default_mcp_server_id": "mcp_srv_1", "connected_user_count": 1 }
```

| Field                   | Type    | Description                                                      |
| ----------------------- | ------- | ---------------------------------------------------------------- |
| `enabled_flag`          | boolean | Whether `COMPOSIO_DEFAULT_CONNECTOR` is armed on the server      |
| `default_user_id`       | string  | The Composio user the default is scoped to (`""` if unset)       |
| `default_mcp_server_id` | string  | The provisioned Composio MCP server id (`""` if not provisioned) |
| `connected_user_count`  | integer | Live count of Composio users with connected accounts             |

CLI: `crewship integration composio default {show,set,enable}`.

### Composio Agent Binding

Grant an agent **per-app, tool-scoped** access to a Composio **user**'s
connected apps. For **each** granted app the binding provisions one
tool-scoped Composio MCP server and persists the rows the runtime resolver
reads — a per-(agent, app) workspace MCP server pointing at the per-user
Composio MCP URL, a workspace credential holding the Composio API key, and an
agent MCP binding joining them (`cred_type: api_key`, `cred_header: x-api-key`).
No resolver change is needed: the sidecar injects the `x-api-key` header on the
streamable-http request.

Each app carries a **mode** that maps to the MCP server's `allowed_tools`:

| Mode     | `allowed_tools`          | Effect                                                                                                                                                     |
| -------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `full`   | *(empty)*                | Every tool the app exposes (no enumeration).                                                                                                               |
| `read`   | the app's read-ish tools | Server-resolved: tools whose slug carries a read verb (`GET`, `LIST`, `FETCH`, `SEARCH`, `READ`, …). Conservative — unknown verbs are treated as not-read. |
| `custom` | the picked tool slugs    | The request's `tools`, intersected with the app's real tools (bogus slugs are dropped).                                                                    |

```
GET    /api/v1/integrations/composio/agents/{agentId}/bind?workspace_id={workspaceId}
POST   /api/v1/integrations/composio/agents/{agentId}/bind?workspace_id={workspaceId}
DELETE /api/v1/integrations/composio/agents/{agentId}/bind?workspace_id={workspaceId}[&toolkit={slug}]
```

`GET` (read) lists the agent's per-app Composio bindings; `POST`/`DELETE`
require workspace **manage** (OWNER/ADMIN). `POST` body:

```json theme={null}
{
  "user_id": "user-42",
  "apps": [
    { "toolkit": "gmail", "mode": "read" },
    { "toolkit": "github", "mode": "full" },
    { "toolkit": "gmail", "mode": "custom", "tools": ["GMAIL_FETCH_EMAILS", "GMAIL_LIST_THREADS"] }
  ]
}
```

`user_id` is required (the Composio user the agent is scoped to). The legacy
shape `{ "user_id": "…", "toolkits": ["gmail"] }` is still accepted — each
toolkit becomes an app at `full`. When neither `apps` nor `toolkits` is given,
every connected app is bound at `full`. Re-binding **replaces** the agent's app
set: apps no longer present are removed (their server row + binding deleted).
`read`/`custom` with an empty resolved tool set returns `400`. `DELETE` with
`?toolkit=<slug>` removes one app; without it, removes all the agent's Composio
apps.

**Response (POST):** `200 OK`

```json theme={null}
{
  "agent_id": "clx…",
  "user_id": "user-42",
  "apps": [
    { "toolkit": "gmail", "mode": "read", "endpoint": "https://mcp.composio.dev/server/…?user_id=user-42" },
    { "toolkit": "github", "mode": "full", "endpoint": "https://mcp.composio.dev/server/…?user_id=user-42" }
  ]
}
```

**Response (GET):** `200 OK`

```json theme={null}
{ "agent_id": "clx…", "bindings": [ { "toolkit": "gmail", "mode": "read", "user_id": "user-42", "endpoint": "https://mcp.composio.dev/server/…?user_id=user-42" } ] }
```

`POST` returns `404` for an unknown/foreign agent and `400` when Composio is
unconfigured or no apps resolve. CLI:
`crewship integration composio {bind,unbind,bindings} <agent> --user <id> [--app toolkit[:mode[:t1,t2]]]…`.

### Composio Connected-Account Management

Lifecycle operations on an existing connected account. The `accountId` is the
Composio account id surfaced by the [inventory](#composio-inventory) endpoint.
All three require workspace **manage** (OWNER/ADMIN) and return `400` when
Composio is unconfigured.

```
POST   /api/v1/integrations/composio/accounts/{accountId}/revoke?workspace_id={workspaceId}
POST   /api/v1/integrations/composio/accounts/{accountId}/refresh?workspace_id={workspaceId}
DELETE /api/v1/integrations/composio/accounts/{accountId}?workspace_id={workspaceId}
```

* **revoke** — de-authorizes the account at the provider. Its credentials are
  invalidated upstream; the user must re-connect before it can be used again.
* **refresh** — refreshes the account's credentials (e.g. exchanging a refresh
  token for a new access token).
* **delete** — permanently removes the connected account at the provider.

**Response:** `204 No Content` on success; `502` when Composio returns an error.

CLI: `crewship integration composio account {revoke,refresh,remove} <account-id>`.

***

## Workspace Integrations

Workspace-level MCP server definitions available to all crews and agents.

### List Workspace Integrations

```
GET /api/v1/integrations?workspace_id={workspaceId}
```

**Response:** `200 OK`

```json theme={null}
[
  {
    "id": "int_abc",
    "workspace_id": "ws_123",
    "name": "github-mcp",
    "display_name": "GitHub MCP Server",
    "transport": "stdio",
    "endpoint": null,
    "command": "npx",
    "args_json": "[\"-y\", \"@modelcontextprotocol/server-github\"]",
    "env_json": "{\"GITHUB_TOKEN\": \"{{credential:github-token}}\"}",
    "config_json": null,
    "icon": "github",
    "enabled": true,
    "created_at": "2024-01-15T10:00:00Z",
    "updated_at": "2024-01-15T10:00:00Z",
    "agent_binding_count": 5,
    "crew_server_count": 2
  }
]
```

### Response Fields

| Field                 | Type    | Description                                                        |
| --------------------- | ------- | ------------------------------------------------------------------ |
| `id`                  | string  | Integration ID                                                     |
| `workspace_id`        | string  | Workspace ID                                                       |
| `name`                | string  | Internal name                                                      |
| `display_name`        | string  | Human-readable name                                                |
| `transport`           | string  | Transport type (validated set: `streamable-http` or `stdio`)       |
| `endpoint`            | string? | Server endpoint URL (required when transport is `streamable-http`) |
| `command`             | string? | Command to execute (required when transport is `stdio`)            |
| `args_json`           | string? | JSON array of command arguments                                    |
| `env_json`            | string? | JSON object of environment variables                               |
| `config_json`         | string? | Additional configuration JSON                                      |
| `icon`                | string? | Display icon name                                                  |
| `enabled`             | boolean | Whether the integration is active                                  |
| `agent_binding_count` | integer | Number of agent bindings                                           |
| `crew_server_count`   | integer | Number of crew-level servers                                       |

### Create Workspace Integration

```
POST /api/v1/integrations?workspace_id={workspaceId}
```

**Auth:** `OWNER` or `ADMIN` role

**Request Body:**

| Field          | Type   | Required    | Default             | Description                                    |
| -------------- | ------ | ----------- | ------------------- | ---------------------------------------------- |
| `name`         | string | Yes         | --                  | Internal name                                  |
| `display_name` | string | No          | `name`              | Display name (defaults to `name` when omitted) |
| `transport`    | string | No          | `"streamable-http"` | `streamable-http` or `stdio`                   |
| `endpoint`     | string | Conditional | `null`              | Required when `transport` is `streamable-http` |
| `command`      | string | Conditional | `null`              | Required when `transport` is `stdio`           |
| `args_json`    | string | No          | `null`              | JSON array of arguments                        |
| `env_json`     | string | No          | `null`              | JSON object of env vars                        |
| `config_json`  | string | No          | `null`              | Additional config JSON                         |
| `icon`         | string | No          | `null`              | Display icon                                   |

```json theme={null}
{
  "name": "github-mcp",
  "display_name": "GitHub MCP Server",
  "transport": "stdio",
  "command": "npx",
  "args_json": "[\"-y\", \"@modelcontextprotocol/server-github\"]",
  "env_json": "{\"GITHUB_TOKEN\": \"{{credential:github-token}}\"}"
}
```

**Response:** `201 Created` -- integration object (enabled defaults to `true`).

| Status | Condition                                                                                     |
| ------ | --------------------------------------------------------------------------------------------- |
| `400`  | Missing `name`, invalid `transport`, or `endpoint`/`command` missing for the chosen transport |
| `403`  | Insufficient role                                                                             |
| `409`  | Integration with this name already exists                                                     |

### Get Workspace Integration

```
GET /api/v1/integrations/{integrationId}?workspace_id={workspaceId}
```

**Response:** `200 OK`

### Update Workspace Integration

```
PATCH /api/v1/integrations/{integrationId}?workspace_id={workspaceId}
```

**Auth:** `OWNER` or `ADMIN` role

All fields optional: `display_name`, `transport`, `endpoint`, `command`, `args_json`, `env_json`, `config_json`, `icon`, `enabled`.

**Response:** `200 OK`

### Delete Workspace Integration

```
DELETE /api/v1/integrations/{integrationId}?workspace_id={workspaceId}
```

Cascade-deletes agent bindings, crew-level overrides, and finally the workspace server, all in one transaction.

<Warning>
  This cascades: every agent binding and crew-level override of the integration is removed alongside the workspace server, in a single transaction.
</Warning>

**Auth:** `OWNER` or `ADMIN` role

**Response:** `200 OK`

```json theme={null}
{ "status": "deleted" }
```

| Status | Condition             |
| ------ | --------------------- |
| `404`  | Integration not found |

### Test Workspace Integration

```
POST /api/v1/integrations/{integrationId}/test?workspace_id={workspaceId}
```

Tests the MCP server connection. For `stdio` transport the server is not actually launched (that only happens inside a container at runtime) — instead the config is statically validated (command must be a bare executable with no embedded whitespace, `args_json` must be a valid JSON string array), returning `status: "ok"` when well-formed or `status: "error"` otherwise. For `streamable-http` (also `http`/`sse`) an MCP `initialize` handshake is attempted via an SSRF-safe HTTP client (private/loopback IPs blocked).

**Response:** `200 OK`

```json theme={null}
{
  "status": "ok",
  "message": "Connected to MCP server",
  "server_info": { "name": "github-mcp", "version": "1.2.3" }
}
```

| Field         | Description                                                                                                   |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| `status`      | `ok`, `error`, or `auth_required` (the struct also defines `skipped`, but the current handlers never emit it) |
| `message`     | Optional human-readable description (omitted when empty)                                                      |
| `server_info` | Optional raw `initialize` `result` from the server (omitted when empty)                                       |

| Status | Condition             |
| ------ | --------------------- |
| `404`  | Integration not found |

***

## Crew Integrations

Crew-level MCP bindings — either standalone servers or overrides linked to a workspace integration. This tier also carries per-tool enable/disable state.

### All Crew Integrations

```
GET /api/v1/integrations/crews?workspace_id={workspaceId}
```

Cross-crew overview of all crew-level integration bindings.

**Response:** `200 OK` -- array of crew integration objects.

### List Crew Integrations

```
GET /api/v1/crews/{crewId}/integrations?workspace_id={workspaceId}
```

**Response:** `200 OK` -- array of crew MCP server bindings.

### Create Crew Integration

```
POST /api/v1/crews/{crewId}/integrations?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

**Request Body:**

| Field                     | Type   | Required    | Default             | Description                                                           |
| ------------------------- | ------ | ----------- | ------------------- | --------------------------------------------------------------------- |
| `workspace_mcp_server_id` | string | No          | `null`              | Link to an existing workspace integration (must be in same workspace) |
| `name`                    | string | Yes         | --                  | Internal name                                                         |
| `display_name`            | string | No          | `name`              | Display name                                                          |
| `transport`               | string | No          | `"streamable-http"` | `streamable-http` or `stdio`                                          |
| `endpoint`                | string | Conditional | `null`              | Required when `transport` is `streamable-http`                        |
| `command`                 | string | Conditional | `null`              | Required when `transport` is `stdio`                                  |
| `args_json`               | string | No          | `null`              | JSON array of arguments                                               |
| `env_json`                | string | No          | `null`              | JSON object of env vars                                               |
| `config_json`             | string | No          | `null`              | Additional config JSON                                                |
| `icon`                    | string | No          | `null`              | Display icon                                                          |

**Response:** `201 Created` -- crew MCP server object.

| Status | Condition                                                                                                                              |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Missing `name`, invalid `transport`, missing endpoint/command for transport, or referenced workspace integration not in same workspace |
| `404`  | Crew not found                                                                                                                         |
| `409`  | Integration with this name already exists in this crew                                                                                 |

### Update Crew Integration

```
PATCH /api/v1/crews/{crewId}/integrations/{integrationId}?workspace_id={workspaceId}
```

**Auth:** `OWNER` or `ADMIN` role

All fields optional: `display_name`, `transport`, `endpoint`, `command`, `args_json`, `env_json`, `config_json`, `icon`, `enabled`. Changing `transport` re-validates against the merged final endpoint/command.

**Response:** `200 OK` -- updated crew integration object.

| Status | Condition                                                          |
| ------ | ------------------------------------------------------------------ |
| `400`  | Invalid `transport`, or endpoint/command missing for new transport |
| `404`  | Crew integration not found                                         |

### Delete Crew Integration

```
DELETE /api/v1/crews/{crewId}/integrations/{integrationId}?workspace_id={workspaceId}
```

Cascade-deletes agent bindings and any OAuth credentials that were auto-created for this integration (only when no other binding still references them).

<Warning>
  This cascades: agent bindings are removed, and OAuth credentials auto-created for this integration are deleted too — but only when no other binding still references them.
</Warning>

**Auth:** `OWNER` or `ADMIN` role

**Response:** `200 OK`

```json theme={null}
{ "status": "deleted" }
```

| Status | Condition                  |
| ------ | -------------------------- |
| `404`  | Crew integration not found |

### Test Crew Integration

```
POST /api/v1/crews/{crewId}/integrations/{integrationId}/test?workspace_id={workspaceId}
```

Same probe semantics as the workspace test endpoint.

**Response:** `200 OK` -- `{ status, message?, server_info? }` (see Test Workspace Integration).

| Status | Condition                     |
| ------ | ----------------------------- |
| `404`  | Crew or integration not found |

### List Crew Integration Tools

```
GET /api/v1/crews/{crewId}/integrations/{integrationId}/tools?workspace_id={workspaceId}
```

Returns the recorded per-tool enable/disable bindings for the crew's
MCP server (`mcp_tool_bindings`). Only tools that have been toggled or
seen via [Refresh](#refresh-crew-integration-tools) have a row; a tool
with no row is treated as **enabled** by default. This endpoint does
**not** contact the MCP server.

**Response:** `200 OK` — a JSON **array** of tool-binding objects (no wrapper).

```json theme={null}
[
  { "id": "tb_abc", "tool_name": "search", "description": "...", "enabled": true, "created_at": "2026-05-14T09:12:44Z", "updated_at": "2026-05-14T09:12:44Z" },
  { "id": "tb_def", "tool_name": "create_issue", "description": null, "enabled": false, "created_at": "2026-05-14T09:12:44Z", "updated_at": "2026-05-14T09:12:44Z" }
]
```

| Field         | Type    | Description                 |
| ------------- | ------- | --------------------------- |
| `id`          | string  | Tool-binding row ID         |
| `tool_name`   | string  | MCP tool name               |
| `description` | string? | Tool description (nullable) |
| `enabled`     | boolean | Per-crew enabled state      |
| `created_at`  | string  | ISO 8601 timestamp          |
| `updated_at`  | string  | ISO 8601 timestamp          |

| Status | Condition                                                  |
| ------ | ---------------------------------------------------------- |
| `404`  | Crew or integration not found                              |
| `500`  | Database error (a real DB failure is not collapsed to 404) |

### Update Crew Integration Tool

```
PATCH /api/v1/crews/{crewId}/integrations/{integrationId}/tools/{toolName}?workspace_id={workspaceId}
Content-Type: application/json
```

Upsert a single tool's binding for this crew. If no row exists for the
tool it is materialised (a fresh row defaults to `enabled = true`).
Wraps the `crewship integration tool enable/disable` CLI subcommands.

**Auth:** `OWNER` or `ADMIN` role (`canRole(role, "manage")`)

**Request Body:** provide at least one of the following fields.

| Field         | Type    | Description                                         |
| ------------- | ------- | --------------------------------------------------- |
| `enabled`     | boolean | New enabled state (omit to leave unchanged)         |
| `description` | string  | Tool description to store (omit to leave unchanged) |

```json theme={null}
{ "enabled": false }
```

**Response:** `200 OK` — the upserted tool-binding object (same shape as the [List](#list-crew-integration-tools) entries).

| Status | Condition                                                                       |
| ------ | ------------------------------------------------------------------------------- |
| `400`  | Empty `toolName`, invalid JSON, or neither `enabled` nor `description` supplied |
| `403`  | Insufficient role                                                               |
| `404`  | Crew or integration not found                                                   |

### Refresh Crew Integration Tools

```
POST /api/v1/crews/{crewId}/integrations/{integrationId}/tools/refresh?workspace_id={workspaceId}
```

Reconcile the `mcp_tool_bindings` rows for this crew server against a
tool list supplied **in the request body** (typically posted by the
frontend after a successful test-connection round-trip). The server
does **not** contact the MCP server itself: new tools are inserted
`enabled = true`, existing tools have their description refreshed but
their `enabled` state left untouched, and tools absent from the payload
are left in place (never auto-revoked). An empty list is a no-op.

**Auth:** `OWNER` or `ADMIN` role (`canRole(role, "manage")`)

**Request Body:**

```json theme={null}
{
  "tools": [
    { "name": "search", "description": "Search issues" },
    { "name": "create_issue", "description": null }
  ]
}
```

**Response:** `200 OK`

```json theme={null}
{ "created": 1, "updated": 1, "total": 2 }
```

`total` is the number of entries in the request `tools` array.

| Status | Condition                     |
| ------ | ----------------------------- |
| `400`  | Invalid JSON body             |
| `403`  | Insufficient role             |
| `404`  | Crew or integration not found |

***

## Agent MCP Bindings

Agent bindings link MCP servers (workspace or crew level) to specific agents, optionally with credential overrides.

### List Agent Bindings

```
GET /api/v1/agents/{agentId}/integrations?workspace_id={workspaceId}
```

**Response:** `200 OK` -- array of agent MCP binding objects.

### Create Agent Binding

```
POST /api/v1/agents/{agentId}/integrations?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

**Request Body:**

| Field                  | Type    | Required | Default    | Description                                             |
| ---------------------- | ------- | -------- | ---------- | ------------------------------------------------------- |
| `mcp_server_id`        | string  | Yes      | --         | ID of the MCP server to bind                            |
| `mcp_server_scope`     | string  | Yes      | --         | `workspace` or `crew`                                   |
| `credential_id`        | string  | No       | `null`     | Workspace credential to attach for auth                 |
| `cred_type`            | string  | No       | `"bearer"` | `bearer`, `api_key`, or `basic`                         |
| `cred_header`          | string  | No       | `null`     | Custom header name (used when `cred_type` is `api_key`) |
| `env_var_name`         | string  | No       | `null`     | Env var name for stdio credential injection             |
| `enabled`              | boolean | No       | `true`     | Whether the binding is active                           |
| `config_override_json` | string  | No       | `null`     | Per-binding config override JSON                        |

**Response:** `201 Created` -- binding object.

| Status | Condition                                                                                                                    |
| ------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Missing/invalid `mcp_server_id`, `mcp_server_scope`, `cred_type`, or referenced integration/credential not in this workspace |
| `404`  | Agent not found                                                                                                              |
| `409`  | Agent already has a binding for this integration                                                                             |

### Update Agent Binding

```
PATCH /api/v1/agents/{agentId}/integrations/{integrationId}?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

All fields optional: `credential_id` (empty string clears), `cred_type`, `cred_header`, `env_var_name` (empty string clears), `enabled`, `config_override_json`.

**Response:** `200 OK`

```json theme={null}
{ "status": "updated" }
```

| Status | Condition                                                                     |
| ------ | ----------------------------------------------------------------------------- |
| `400`  | Invalid `cred_type`, credential not in this workspace, or no fields to update |
| `404`  | Agent binding not found                                                       |

### Delete Agent Binding

```
DELETE /api/v1/agents/{agentId}/integrations/{integrationId}?workspace_id={workspaceId}
```

**Auth:** `OWNER`, `ADMIN`, or `MANAGER` role

**Response:** `200 OK`

```json theme={null}
{ "status": "deleted" }
```

| Status | Condition               |
| ------ | ----------------------- |
| `404`  | Agent binding not found |

### Resolve Agent Integrations

```
GET /api/v1/agents/{agentId}/integrations/resolved?workspace_id={workspaceId}
```

Returns the effective set of MCP servers for an agent after cascading workspace, crew, and agent-level configurations. Includes resolved credentials.

**Response:** `200 OK` -- array of resolved integration objects.

***

## OAuth Flow

OAuth 2.0 flow for connecting external services (GitHub, Slack, Google, etc.) and storing tokens as credentials.

### List OAuth Providers

```
GET /api/v1/oauth/providers
```

Returns available OAuth provider configurations.

**Auth:** Session or CLI token (no workspace context needed)

**Response:** `200 OK`

### Initiate OAuth Flow

```
POST /api/v1/oauth/initiate?workspace_id={workspaceId}
```

Starts the OAuth authorization flow by generating an authorization URL.

**Auth:** Session or CLI token + workspace membership

**Response:** `200 OK` -- includes the authorization URL to redirect the user to.

### OAuth Callback

```
GET /api/v1/oauth/callback?code={code}&state={state}
```

Handles the OAuth callback from the provider.

<Note>
  No authentication required — this endpoint uses the state token for validation instead.
</Note>

### Exchange Token

```
POST /api/v1/oauth/exchange?workspace_id={workspaceId}
```

Exchanges an authorization code for access and refresh tokens.

**Response:** `200 OK`

### Loopback

```
POST /api/v1/oauth/loopback?workspace_id={workspaceId}
```

Handles loopback redirect for local OAuth flows.

**Response:** `200 OK`

### Discover OAuth Endpoints

```
POST /api/v1/oauth/discover
```

Discovers OAuth endpoints from an OpenID Connect discovery document or well-known URL.

**Auth:** Session or CLI token (no workspace context needed)

**Response:** `200 OK` -- discovered endpoints.

### Auto-Connect Integration

```
POST /api/v1/oauth/auto-connect?workspace_id={workspaceId}
```

Automatically creates an integration from an OAuth connection.

**Response:** `200 OK`

***

## MCP Registry

Public registry of MCP server definitions for easy discovery and installation.

### List Registry Entries

```
GET /api/v1/mcp-registry
```

**Auth:** Session or CLI token (no workspace context needed)

**Response:** `200 OK` -- array of registry entries.

### Search Registry

```
GET /api/v1/mcp-registry/search?q={query}
```

**Auth:** Session or CLI token

**Response:** `200 OK` -- matching registry entries.

### Sync Registry

```
POST /api/v1/mcp-registry/sync?workspace_id={workspaceId}
```

Triggers a manual sync of the MCP registry.

**Auth:** Session or CLI token + workspace membership

**Response:** `200 OK`
