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

# Build an App (Alpha)

> Package an MCP server as a Wassist App and give its tools to your organisation's WhatsApp agents

<Warning>
  **Apps are in alpha.** The API and manifest may still change. If you start building an app, email [contact@wassist.app](mailto:contact@wassist.app) and we'll add you to the alpha group, so you hear about changes before they ship.
</Warning>

A Wassist App is an [MCP](https://modelcontextprotocol.io) server packaged so that your Wassist organisation can install it. Apps can only be installed on the organisation that owns them. When you install your app, you choose which of your agents get it and which tools each agent can use. From then on those agents can call your tools in live WhatsApp conversations and in dashboard tests.

You can build an app in one of two ways:

<CardGroup cols={2}>
  <Card title="Wrap an existing MCP server" icon="bolt" href="#path-a-wrap-an-existing-mcp-server">
    **Least work.** If you already run an MCP server, you can usually publish it as an app without changing any code. It takes about 15 minutes.
  </Card>

  <Card title="Build a Wassist-aware server" icon="code" href="#path-b-build-a-wassist-aware-server">
    **Deeper integration.** Your tools know which business, agent and WhatsApp conversation each call comes from, and can read and send messages through the Wassist API.
  </Card>
</CardGroup>

Most developers start with Path A and then add Wassist-specific features from Path B.

## How it fits together

<Frame>
  ```mermaid theme={null}
  sequenceDiagram
      participant I as Installer (your organisation)
      participant W as Wassist
      participant M as Your MCP server
      I->>W: Installs your app
      W->>M: OAuth login (if your app needs it)
      I->>W: Picks agents and tools
      Note over W,M: Later, in a WhatsApp conversation
      W->>M: tools/call (with Wassist context in _meta)
      M-->>W: Tool result
      W->>I: Agent replies to the customer
  ```
</Frame>

* **App**: your MCP server URL, how it authenticates, and the name, icon and description installers see. Your organisation owns it.
* **Installation**: your app installed on your organisation, with its own agent and tool choices.

## Before you start

Your MCP server must meet these requirements to work as an app:

| Requirement                             | Details                                                                                                                                                                                                                    |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Streamable HTTP transport**           | SSE-only servers (URLs ending in `/sse`) aren't supported.                                                                                                                                                                 |
| **HTTPS**                               | The server URL must be `https://`.                                                                                                                                                                                         |
| **Supported authentication**            | Either no auth, or OAuth 2.1 following the [MCP authorization spec](https://modelcontextprotocol.io/specification/basic/authorization). See [Check that your server is compatible](#check-that-your-server-is-compatible). |
| **Tool calls finish within 30 seconds** | Longer calls time out. For slow jobs, start the work, return straight away, and tell the customer when it's done.                                                                                                          |

You'll also need a Wassist account with an organisation. You create and manage apps under **Settings → Developers → Apps**.

***

## Path A: wrap an existing MCP server

### Check that your server is compatible

Wassist finds your OAuth server at `/.well-known/oauth-authorization-server` on **the same host as your MCP URL**. For `https://mcp.example.com/mcp` it requests `https://mcp.example.com/.well-known/oauth-authorization-server`.

```bash theme={null}
# 1. Does the server speak Streamable HTTP?
curl -s -X POST https://mcp.example.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}'

# 2. Does it publish OAuth metadata on the MCP host?
curl -s https://mcp.example.com/.well-known/oauth-authorization-server

# 3. Does that metadata include a registration_endpoint (dynamic client registration)?
curl -s https://mcp.example.com/.well-known/oauth-authorization-server | jq .registration_endpoint
```

Then find your situation below:

| Your server                                                                                                                     | What to do                                                                                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Needs no authentication                                                                                                         | Use `"auth": "none"`.                                                                                                                                                                                    |
| Uses OAuth, metadata on the MCP host, **with** a `registration_endpoint`                                                        | Nothing to configure. Wassist registers itself as an OAuth client.                                                                                                                                       |
| Uses OAuth, metadata on the MCP host, **without** a `registration_endpoint`                                                     | Register a client for Wassist in your OAuth provider and add its client ID (and secret, if it has one) under **Advanced settings**. See [Using a client you registered](#using-a-client-you-registered). |
| Uses OAuth, but the metadata is only on a separate authorization server (found through `/.well-known/oauth-protected-resource`) | Not supported yet in the alpha. Serve `/.well-known/oauth-authorization-server` on the MCP host as well; it can be a copy of your authorization server's metadata.                                       |
| Uses a static API key or custom header                                                                                          | Not supported directly. Put a small proxy in front of it; see [Servers that use API keys](#servers-that-use-api-keys).                                                                                   |

### Choose who logs in

The auth mode (`mcp.auth` in the manifest, or **Who logs in** in the form) decides whose account your tools act on:

| Mode           | Who logs in                                                                                                                                                                 | Best for                                                                                                                 |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `installer`    | Someone at the installing business, once, while installing. Every agent in their organisation uses that login.                                                              | Business tools: CRMs, booking systems, helpdesks, store back offices.                                                    |
| `end_customer` | Each WhatsApp customer, the first time they need one of your tools. The agent sends them a **Log in** button in the chat. When they come back, the conversation carries on. | Consumer accounts: loyalty programmes, bank or telco accounts, subscriptions.                                            |
| `none`         | Nobody.                                                                                                                                                                     | Public data, or servers that identify the business from the signed [context token](#wassist-context-on-every-tool-call). |

<Note>
  With `end_customer` auth, nobody has logged in when a business installs your app, so Wassist can't list your tools live. You must publish a tool catalogue before you can publish the app: either click **Sync tools** and log in with a test account, or list your tools under `mcp.tools_preview` in the manifest.
</Note>

### Publish your app

<Steps>
  <Step title="Create the app">
    Go to **Settings → Developers → Apps** and click **New app**. Give it a name, which you can change later.

    Copy the **client ID** (`wapp_…`) and **client secret** (`wsec_…`). The secret is only shown once. Path A doesn't use them yet, but you'll need them to verify calls or use the Wassist API.
  </Step>

  <Step title="Point it at your server">
    Under **Configuration**, fill in the form: the name, developer name, description, icon URL, **MCP server URL** and **Who logs in**. You can switch to **Manifest JSON** and paste a manifest instead:

    ```json theme={null}
    {
      "name": "Acme Bookings",
      "slug": "acme-bookings",
      "description": "Let your agent check availability and book appointments.",
      "developer_name": "Acme",
      "icon_url": "https://acme.dev/icon.png",
      "mcp": {
        "url": "https://mcp.acme.dev/mcp",
        "auth": "installer"
      }
    }
    ```

    Click **Save draft**.
  </Step>

  <Step title="Sync your tools">
    Click **Sync tools**. Wassist connects to your server and lists its tools. If your server needs a login, you'll be sent through its OAuth flow first. Check that the tool names, titles and descriptions look right: installers see them, and the agent uses the descriptions to decide when to call each tool.
  </Step>

  <Step title="Publish">
    Click **Publish v1**. Your app is now installable on your organisation.
  </Step>

  <Step title="Install it on your organisation">
    Under **Install**, click **Install on this organisation**. Go through the flow: review, connect (for `installer` auth), then choose agents and tools. Only owners and admins of the organisation can install apps.

    Then open one of those agents and test it in the dashboard. Ask it something that should trigger one of your tools. For `end_customer` apps, the agent sends you a login button in the test chat.
  </Step>
</Steps>

<Tip>
  Your tools reach the model with your app's slug as a prefix (for example `acme_bookings__check_availability`), so they never clash with another app's tools. Your server still sees its own tool names.
</Tip>

### Using a client you registered

If your OAuth server can't register clients dynamically, register one for Wassist yourself:

1. Create an OAuth client in your provider that supports the authorization code flow with PKCE and refresh tokens.

2. Allow this redirect URI. The exact value is shown under **Configuration → Advanced settings** on your app page:

   ```
   https://backend.wassist.app/api/v1/apps/<your app id>/oauth/callback/
   ```

3. Put the client ID (and secret, if any) in **Advanced settings**, or under `mcp.oauth` in the manifest:

   ```json theme={null}
   "mcp": {
     "url": "https://mcp.acme.dev/mcp",
     "auth": "installer",
     "oauth": { "client_id": "your-client-id", "client_secret": "optional", "scopes": ["bookings:write", "offline_access"] }
   }
   ```

If you leave `scopes` empty, Wassist asks for `offline_access` when your server supports it, so it can refresh tokens without asking anyone to log in again.

<Note>
  With dynamic registration and `end_customer` auth, Wassist registers a separate OAuth client for each agent, named after the agent (for example "Acme Assistant powered by Wassist"). This means your login screen shows customers which business is asking. With a client you registered yourself, every agent shares that one client.
</Note>

### Servers that use API keys

The alpha doesn't let you store static headers such as `Authorization: Bearer <api key>`. If your MCP server authenticates with API keys, put a thin proxy in front of it:

1. Set the app's auth to `none` and add a `setup_url` (your own settings page).
2. After installing, Wassist sends the installer to your setup page with a signed `installation_id`. Ask them for their API key there and store it against that installation.
3. In the proxy, verify the [context token](#wassist-context-on-every-tool-call) on each `tools/call`, look up the API key for its `installation_id`, and forward the request to your real server with that key.

This uses the Wassist SDK from Path B.

***

## Path B: build a Wassist-aware server

Every tool call from Wassist carries context that a generic MCP server ignores: which organisation installed you, which agent is calling, and which session it's in. A Wassist-aware server uses this context to:

* tell which business (and which of your accounts) a call is for, without any OAuth;
* read the WhatsApp conversation behind the call, for example to get the customer's name or phone number, or earlier messages;
* send messages into the conversation;
* use any other part of the [Wassist API](/api-reference/introduction) on behalf of the installing business.

### Install the SDK

```bash theme={null}
npm install @wassist/sdk @modelcontextprotocol/sdk zod
```

```ts theme={null}
import { WassistApp } from '@wassist/sdk/apps';

const wassist = new WassistApp({
  clientId: process.env.WASSIST_CLIENT_ID!,       // wapp_…
  clientSecret: process.env.WASSIST_CLIENT_SECRET!, // wsec_…
});
```

### Read the context in your tools

```ts theme={null}
server.registerTool(
  'get_points',
  {
    title: 'Check points',
    description: "The customer's loyalty points balance and tier.",
    inputSchema: {},
  },
  async (_args, extra) => {
    // Throws unless Wassist signed this call for your app in the last ten minutes.
    const ctx = wassist.verifyContextToken(extra._meta);

    // Where the agent is talking: a WhatsApp conversation, or a dashboard test.
    const channel = await wassist.resolveChannel(ctx);

    if (channel.type === 'whatsapp') {
      const conversation = await channel.getConversation();
      const balance = await pointsFor(ctx.organizationId, channel.contactId);
      return {
        content: [{ type: 'text', text: `${conversation.contact.name} has ${balance} points.` }],
      };
    }

    // channel.type === 'web_builder': the business is testing their agent in the dashboard.
    return { content: [{ type: 'text', text: 'Test customer has 120 points.' }] };
  }
);
```

<Warning>
  Handle both channels. Businesses test their agents in the dashboard before going live, and your tools run there too. Dashboard tests have no WhatsApp contact.
</Warning>

The channel objects give you:

| Channel                                     | Properties                                 | Methods                                                          |
| ------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------- |
| `WhatsAppChannel` (`type: 'whatsapp'`)      | `conversationId`, `contactId`, `sessionId` | `getConversation()`, `listMessages({ limit })`, `sendText(body)` |
| `WebBuilderChannel` (`type: 'web_builder'`) | `simulationId`, `sessionId`                | `getSimulation()`, `listMessages()`                              |

For anything else, `wassist.client(ctx.installationId)` returns a full Wassist API client acting as the installing organisation. With it you can use `conversations`, `agents`, `sessions`, `simulations` and the rest of the API.

### Add a setup page (optional)

If you set `setup_url`, Wassist sends installers there right after they install, and again whenever they click **Configure** on the installation. Use it to link the installation to an account in your system, or to collect settings.

```ts theme={null}
app.get('/wassist/setup', (req, res) => {
  try {
    const { installationId, organizationId } = wassist.verifySetupRedirect(req.originalUrl);
    // Look up or create the matching account in your system, then show your settings UI.
    res.send(`Connected installation ${installationId}`);
  } catch {
    res.status(400).send('This link has expired. Open it again from Wassist.');
  }
});
```

Setup links are signed and expire after five minutes, so don't bookmark them. Ask the installer to open the page again from Wassist.

### A complete example

A runnable Express server with two tools, context verification, channel resolution and a setup page is in the SDK repository: [`examples/app-mcp-server`](https://github.com/wassist/sdk/tree/main/examples/app-mcp-server). To try it:

```bash theme={null}
cp .env.example .env   # add WASSIST_CLIENT_ID and WASSIST_CLIENT_SECRET
npm install
npm start
cloudflared tunnel --url http://localhost:3000   # or any HTTPS tunnel
```

Put `https://<tunnel>/mcp` in your app's MCP server URL, set **Who logs in** to **Nobody**, then sync, publish and install as in [Path A](#publish-your-app).

***

## Reference

### Wassist context on every tool call

Every `tools/call` from a Wassist agent includes these keys in `params._meta`:

| Key                           | Value                                                                     |
| ----------------------------- | ------------------------------------------------------------------------- |
| `wassist.app/installation_id` | The installation (one per organisation)                                   |
| `wassist.app/organization_id` | The installing organisation                                               |
| `wassist.app/agent_id`        | The agent making the call                                                 |
| `wassist.app/session_id`      | The agent session. Resolve it to find the conversation or dashboard test. |
| `wassist.app/context_token`   | A JWT containing all of the above, signed with your client secret         |

Only trust these IDs after verifying the token. It's signed with HS256 using your client secret, has `iss` set to `https://wassist.app` and `aud` set to your client ID, and expires after ten minutes. `verifyContextToken` checks all of this for you. In other languages, any JWT library can verify it.

### Calling the Wassist API without the SDK

An installed app can use the whole [v1 API](/api-reference/introduction) (`https://backend.wassist.app/api/v1`) as the installing organisation. Authenticate each request with your client credentials and the installation you're acting for:

```
Authorization: Basic base64(client_id:client_secret)
X-Wassist-Installation: <installation_id>
```

To find where a tool call came from, call `GET /sessions/{session_id}/`. It returns:

```json theme={null}
{ "id": "…", "agentId": "…", "createdAt": "…",
  "channel": { "type": "whatsapp", "conversationId": "…", "contactId": "…" } }
```

During a dashboard test, the channel is `{ "type": "web_builder", "simulationId": "…" }` instead.

### Manifest

```json theme={null}
{
  "name": "Acme Loyalty",
  "slug": "acme-loyalty",
  "description": "Points and rewards for every order.",
  "developer_name": "Acme",
  "icon_url": "https://acme.dev/icon.png",
  "setup_url": "https://acme.dev/wassist/setup",
  "mcp": {
    "url": "https://mcp.acme.dev/mcp",
    "auth": "end_customer",
    "oauth": { "client_id": "", "scopes": ["points:read"] },
    "tools_preview": [
      { "name": "get_points", "title": "Check points", "description": "The customer's balance", "default_enabled": true },
      { "name": "redeem", "title": "Redeem points", "description": "Spend points on a reward", "default_enabled": false }
    ]
  }
}
```

| Field                 | Notes                                                                                                                                                                                     |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `slug`                | Unique across Wassist. Used in the install URL and as the prefix on your tool names.                                                                                                      |
| `setup_url`           | Optional. Your own page, where installers land after installing and when they click **Configure**.                                                                                        |
| `mcp.url`             | Streamable HTTP over `https`.                                                                                                                                                             |
| `mcp.auth`            | `installer`, `end_customer` or `none`.                                                                                                                                                    |
| `mcp.oauth.client_id` | Leave blank to use dynamic client registration. Only set it (along with `client_secret`, if you have one) for a client you registered yourself.                                           |
| `mcp.oauth.scopes`    | Requested when someone logs in. Defaults to `offline_access` if your server supports it.                                                                                                  |
| `mcp.tools_preview`   | Required for `end_customer` apps unless you use **Sync tools**. For other modes it's only a preview. Set `default_enabled: false` to leave a tool off until the installer switches it on. |

### Versions and updates

Edits go into a draft version. **Publish** makes the draft live for every installation straight away. Wassist lists each installation's tools again after you publish, every day after that, and whenever an installer clicks **Refresh tools**.

If a new version changes the MCP URL, auth mode or OAuth client, Wassist drops the existing tokens. Installations using `installer` auth show **Needs reconnecting** until someone logs in again, and customers of `end_customer` apps are asked to log in again. Adding, removing or renaming tools doesn't affect tokens.

When you rotate your client secret, the old secret keeps working for 24 hours. During the switch, pass it to the SDK as `previousClientSecret` so tokens signed with the old secret still verify.

### Security

* Verify the context token on every call. Never trust the plain IDs in `_meta` on their own.
* Keep the client secret on your server. It signs context tokens and setup links, and together with an installation ID it gives full API access to that organisation.
* Client credentials only work for organisations that currently have your app installed. Uninstalling cuts off access immediately.
* Uninstalling also revokes every token Wassist holds for the installation. If your OAuth server advertises a `revocation_endpoint`, Wassist calls it.

## Alpha limitations

* **Your own organisation only.** Apps can only be installed on the organisation that owns them. There's no way to share an app with other businesses yet.
* **OAuth discovery on the MCP host only.** Wassist doesn't yet follow `/.well-known/oauth-protected-resource` to a separate authorization server.
* **No static headers or API keys.** Use OAuth, `none` auth, or a proxy.
* **Streamable HTTP only.** SSE isn't supported.
* **TypeScript SDK only.** The context token and setup links are standard JWT and HMAC-SHA256, so you can verify them in any language, but only the TypeScript SDK has helpers.

## Troubleshooting

<AccordionGroup>
  <Accordion title="&#x22;The MCP server doesn't support dynamic client registration&#x22;">
    Your OAuth metadata has no `registration_endpoint`. Either enable dynamic client registration on your authorization server, or [register a client for Wassist](#using-a-client-you-registered) and add its client ID under **Advanced settings**.
  </Accordion>

  <Accordion title="&#x22;Couldn't discover OAuth metadata&#x22;">
    Wassist couldn't load `https://<your MCP host>/.well-known/oauth-authorization-server`. Make sure it's served on the same host as your MCP URL and returns JSON. If your server has no OAuth, set **Who logs in** to **Nobody**.
  </Accordion>

  <Accordion title="&#x22;Your server asked for a login, but the app's auth is set to none&#x22;">
    Your server answered `401` when Wassist listed its tools. Switch the app to `installer` or `end_customer` auth, or make your server accept calls without a token.
  </Accordion>

  <Accordion title="&#x22;End-customer apps need a tool catalogue before publishing&#x22;">
    Click **Sync tools** and log in with a test account, or add `mcp.tools_preview` to the manifest.
  </Accordion>

  <Accordion title="&#x22;SSE endpoints aren't supported&#x22;">
    Your MCP URL ends in `/sse`. Serve Streamable HTTP (usually at `/mcp`) and use that URL instead.
  </Accordion>

  <Accordion title="The installation says Needs reconnecting">
    For `installer` auth, this means refreshing the token failed or your server answered `401`. Wassist removes the app's tools from agents until someone at the installing business clicks reconnect. Check that your server issues refresh tokens (ask for `offline_access`) and that tokens are long-lived enough.
  </Accordion>

  <Accordion title="The agent never calls my tool">
    Check that the installer enabled the tool for that agent (on the installation page, or on the agent's **Capabilities** page). Then improve the tool description: the agent uses it to decide when to call the tool, so say when to use it, not just what it does.
  </Accordion>
</AccordionGroup>

## What's next

<CardGroup cols={2}>
  <Card title="API reference" icon="book" href="/api-reference/introduction">
    Everything your app can do with the Wassist API.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/concepts/webhooks">
    React to conversation events outside of tool calls.
  </Card>
</CardGroup>
