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

# Call your apps through MCP

> Use the apps and app_api MCP tools to find the apps in your workspace and read or change their data from Claude, ChatGPT, Cursor, or any other MCP client.

## Overview

Apps you build in Assembly can have their own API: a Project Tracker can list projects, a time tracker can start a timer. The [Assembly MCP server](https://assembly.com/docs/connect-ai-tools/mcp.md) gives your AI assistant two tools to reach those APIs:

* **`apps`** finds the apps in your workspace and describes what each one can do.
* **`app_api`** calls one of those operations.

The tools are generic: they work for every app, so they don't know in advance what any app exposes. The assistant always discovers first and calls second. You don't need to set anything up per app; once you are [connected to the MCP server](https://assembly.com/docs/connect-ai-tools/mcp.md#connect-claude), you can ask about your apps in plain language.

> [!NOTE]
>
> App calls through MCP are rolling out gradually. If your assistant doesn't list the `apps` or `app_api` tools, they aren't enabled for your workspace yet.

## Who can call an app

* **Workspace members only.** Every call runs as you, the person signed in to the MCP connection. The app sees you as the user and returns only what you could see if you opened it in the dashboard.
* **Not clients.** Clients can't connect to the MCP server, and a client's credential is refused if it tries to call an app this way.
* **Your workspace only.** You can reach the apps installed in the workspace you are working in. An app from another workspace, or one that has been uninstalled, answers as if it doesn't exist.

## Step 1: Discover with `apps`

The `apps` tool is read-only and has three actions.

| Action | Takes | Returns |
| - | - | - |
| `list` | nothing | Every app you can call, each with its `appId`, `name`, and `contractHash`. |
| `retrieve` | `appId` | The app's operations: each one's HTTP `method`, `path`, and `operationId`. Operations the app declared for automations also carry a `description` and their `input` fields. |
| `retrieve_contract` | `appId`, optional `If-None-Match` | The full API description (an OpenAPI document) the app published, with request and response shapes for each operation. |

`retrieve` is usually enough to pick the right operation. The assistant reaches for `retrieve_contract` when it needs the exact request body or response fields. The `contractHash` from `list` identifies the current version of that document. To skip re-reading it, pass the hash wrapped in double quotes as `If-None-Match` (for example `"3b1f0c9e…"`); the tool answers `304 Not Modified` when nothing has changed, so the assistant can reuse the copy it already read.

## Step 2: Call with `app_api`

The `app_api` tool sends one request to one app.

| Field | Required | What it holds |
| - | - | - |
| `action` | Yes | The operation's HTTP method: `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`. |
| `appId` | Yes | The app's id, from `apps`. |
| `path` | Yes | The operation's path from `retrieve` with every placeholder filled in, for example `projects` or `projects/123`. |
| `query` | No | The query string, already encoded, for example `status=overdue&limit=10`. |
| `body` | No | The JSON request body, for `POST`, `PUT`, and `PATCH` only. |
| `workspaceId` | No | Which workspace to use, if your account belongs to more than one. |

The path must be one the app lists in `retrieve`. The assistant never invents a path; a path the app doesn't expose is refused.

The app's answer comes back as the app sent it, including its own error messages. A `GET` only reads. A `POST`, `PUT`, `PATCH`, or `DELETE` changes data in the app, so the assistant tells you what it is about to send, and to which app, before it calls, and asks for your confirmation before any `DELETE`.

## Worked example: Project Tracker

Say your workspace has a Project Tracker app, and you ask:

> *"Which projects in Project Tracker are overdue? Mark the Acme website redesign as completed."*

The paths and fields below are illustrative; each app defines its own.

**1. Find the app**

The assistant calls `apps` with `action: "list"` and picks Project Tracker from the result.

```json theme={null}
{
  "data": [
    { "appId": "9f1c2d3e-4b5a-6789-0abc-def012345678", "name": "Project Tracker", "contractHash": "3b1f0c9e…" },
    { "appId": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809", "name": "Time Tracker", "contractHash": "" }
  ]
}
```

**2. Read its operations**

It calls `apps` with `action: "retrieve"` and that `appId`.

```json theme={null}
{
  "appId": "9f1c2d3e-4b5a-6789-0abc-def012345678",
  "name": "Project Tracker",
  "contractHash": "3b1f0c9e…",
  "operations": [
    { "method": "GET", "path": "/projects", "operationId": "listProjects", "origin": "scan+contract" },
    { "method": "GET", "path": "/projects/{projectId}", "operationId": "getProject", "origin": "scan+contract" },
    { "method": "PATCH", "path": "/projects/{projectId}", "operationId": "updateProject", "origin": "scan+contract" }
  ]
}
```

If it needs to know which filters `listProjects` accepts or which fields `updateProject` takes, it calls `retrieve_contract` next.

**3. Read the overdue projects**

It calls `app_api`:

```json theme={null}
{
  "action": "GET",
  "appId": "9f1c2d3e-4b5a-6789-0abc-def012345678",
  "path": "projects",
  "query": "status=overdue"
}
```

Project Tracker returns the overdue projects you can see, and the assistant lists them for you, including the id of the Acme website redesign (say `prj_481`).

**4. Mark one completed**

Because this changes data, the assistant first tells you it will set `prj_481` to completed in Project Tracker. Once you confirm, it calls `app_api`:

```json theme={null}
{
  "action": "PATCH",
  "appId": "9f1c2d3e-4b5a-6789-0abc-def012345678",
  "path": "projects/prj_481",
  "body": { "status": "completed" }
}
```

Project Tracker records the change as made by you.

## Errors

When a call fails, the tool returns the HTTP status and a short message, for example `HTTP 404: App not found.`

| Status | Message | What it means |
| - | - | - |
| 404 | `App not found.` | The `appId` isn't an app you can call: it doesn't exist, belongs to another workspace, was uninstalled, or app calls aren't enabled for your workspace. These causes can't be told apart on purpose. Run `apps` `list` again for a current id. |
| 404 | `No operation of this app matches the request.` | The method and path don't match any operation the app exposes. Check them against `apps` `retrieve`, with every placeholder filled in. |
| 404 | `This app has not published an API description.` | From `retrieve_contract`: the app has no API description yet. Its `contractHash` in `list` is empty. `retrieve` may still list operations. |
| 400 | `Provided request is invalid. Please check the request body and query params.` | The request doesn't fit the operation, for example a query parameter it doesn't accept. |
| 403 | `A viewer's credential cannot call another app through the platform API. …` | The caller isn't a workspace member, for example a client. |
| 429 | | Too many requests. App calls share the [Platform API rate limits](https://assembly.com/docs/api-reference/rate-limits.md). |
| 500 | `App api request failed. Please try again later.` | Assembly couldn't get an answer from the app. Try again later. |
| Any other | The app's own message | The app itself refused or failed, for example a `400` for a missing field or a `404` for a project id that doesn't exist. The status is the app's. |


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