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

# Setting up your own agent for embedded AI chat

> Mint an MCP token for an embed user from your backend so your own AI chat can query Omni as that user.

If you're building your own AI chat into your application, your backend can mint an MCP token for each embed user. Your agent sends that token to Omni's [MCP Server](https://docs.omni.co/ai/mcp), and Omni runs each query as that user.

This is one of [two ways to add AI chat to your embedded application](https://docs.omni.co/embed/customization/ai-chat.md). If you want to use the Omni Agent instead, see [Embedding the Omni Agent](https://docs.omni.co/embed/customization/ai-chat/omni-agent.md).

## How it works

MCP clients such as Claude or ChatGPT sign a user in through Omni's OAuth flow. Embed users don't sign in to Omni, so your backend vouches for them instead:

1. Your backend calls `createEmbedMcpToken` from the `@omni-co/embed` SDK with the user's identity and role values, the same as for a signed embed URL.
2. The SDK signs a short-lived JWT with your embed secret and sends it to your Omni instance's `/oauth/token` endpoint. This uses OAuth's JWT-bearer grant ([RFC 7523](https://www.rfc-editor.org/rfc/rfc7523)).
3. Omni creates or updates the embed user, starts an embed session for them, and returns a standard OAuth access token.
4. Your agent sends the token as `Authorization: Bearer` on every MCP request, the same as a token from the OAuth sign-in flow. Omni runs each query as that embed user.

The token lasts as long as the [**Session length**](https://docs.omni.co/embed/admin/security.md#session-length) setting in **Settings > Embed**, which defaults to 24 hours. There's no refresh token. When Omni answers `401`, your backend mints a new one.

## Requirements

To follow the steps on this page, you'll need:

* **Organization Admin** permissions
* The **MCP server** setting enabled in [**AI Hub > MCP**](https://docs.omni.co/ai/settings/mcp.md#mcp-server)
* **For Omni embed**:
  * The **Embed** feature enabled in your Omni instance
  * Embed MCP tokens enabled for your organization. Reach out to Omni support to enable them.
  * An [embed secret](https://docs.omni.co/embed/admin/secrets.md#create). If you already sign embed URLs, reuse that secret.
  * `@omni-co/embed` version 1.2.0 or later installed in your backend:

    ```bash theme={null}
    npm install @omni-co/embed
    ```

## Setup

### 1. Mint a token for the user

On your server, mint a token with the user's identity and roles. If you also embed Omni, you can use the same `externalId` and `name` as your embed URLs, so both belong to the same Omni user, but read the warning below first.

> [!WARNING]
>
> A token updates the embed user the same way an embed URL does, meaning Omni removes groups, attributes, roles, and entity settings that the token doesn't include for every session. See [How sessions update embed users](https://docs.omni.co/embed/setup/url-parameters.md#user-updates) for the full list.
>
> If your embed URLs and tokens use the same `externalId`, the most recent session wins. A token removes access that only your embed URL gives, and the next embed session removes access that only the token gives. To prevent this, do one of the following:
>
> * Send the same parameters in your embed URLs and your tokens for every session
> * Use a separate `externalId` for MCP, such as `mcp:<your user ID>`
>
> If the user's embed URL puts them in a group with a higher role, you must use a separate `externalId`. Omni refuses that group in a token, and if you leave it out, Omni removes it.

The token's access comes only from its roles, so at least one of [`connectionRoles`](https://docs.omni.co/embed/setup/url-parameters/connectionRoles) or [`modelRoles`](https://docs.omni.co/embed/setup/url-parameters/modelRoles) is required.

* Use a standard role of **Restricted Querier** or **Viewer**, or a [custom role](https://docs.omni.co/administration/users/custom-roles.md) that resolves to one of them, the same as for an embed URL. For a chat agent, use **Restricted Querier** or higher. A **Viewer** token can't use `getData` or `askOmni`.
* Omni refuses a `groups` or `entity` value that names a group with a higher role.

See the [MCP token reference](https://docs.omni.co/embed/customization/ai-chat/mcp-reference.md) for every parameter the call accepts.

Mint tokens on your server only, for the same reason you [generate embed URLs server-side](https://docs.omni.co/embed/setup/standard-sso/latest.md#generate-an-embed-url). The token itself is a credential for that user's data until it expires, so proxy MCP calls through your backend instead of sending it to a browser.

**SDK (Recommended)**

> [!NOTE]
>
> The Omni TypeScript SDK uses Node's `crypto` module, which is only available in Node environments. If your backend isn't running Node, use the **Direct request** tab instead.

Call `createEmbedMcpToken` from the [TypeScript SDK](https://www.npmjs.com/package/@omni-co/embed). If you use a [custom domain](https://docs.omni.co/embed/customization/vanity-domains.md), use the **Custom domain** tab to view an example call.

**Non-custom domain**

```typescript title="Mint a token with the SDK" wrap theme={null}
import { createEmbedMcpToken } from '@omni-co/embed'

// `user` is the signed-in user, from your app's own session
const { accessToken, expiresAt } = await createEmbedMcpToken({
  externalId: user.id,
  name: user.name,
  connectionRoles: {
    'c0f12353-4817-4398-bcc0-d501e6dd2f64': 'RESTRICTED_QUERIER',
  },
  userAttributes: { tenant_id: user.tenantId },
  host: 'myorg.embed-omniapp.co',
  secret: process.env.OMNI_EMBED_SECRET!,
})
```

**Custom domain**

If you use a [custom domain](https://docs.omni.co/embed/customization/vanity-domains.md), also pass `organizationShortId`:

```typescript title="Mint a token on a custom domain" theme={null}
import { createEmbedMcpToken } from '@omni-co/embed'

// `user` is the signed-in user, from your app's own session
const { accessToken, expiresAt } = await createEmbedMcpToken({
  externalId: user.id,
  name: user.name,
  connectionRoles: {
    'c0f12353-4817-4398-bcc0-d501e6dd2f64': 'RESTRICTED_QUERIER',
  },
  host: 'analytics.blobsrus.com',
  organizationShortId: 'blobsrus',
  secret: process.env.OMNI_EMBED_SECRET!,
})
```

> [!WARNING]
>
> **Don't hardcode user parameter values.** Set user information like `externalId`, `name`, and `userAttributes` from your server's own record of the signed in user. If these values are hard-coded, every user will have the same Omni identity and access.

A successful call returns `accessToken` and `expiresAt`.

**Direct request**

If your backend isn't running a JavaScript runtime, send a signed JWT to your Omni instance's token endpoint (`POST https://<YOUR OMNI HOSTNAME>/oauth/token`).

The body is form-encoded with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, `assertion=<JWT>`, and optionally `resource=https://<YOUR OMNI HOSTNAME>/mcp`.

The JWT is HS256, signed with an active embed secret, with these registered claims:

| Claim | Value |
| - | - |
| `iss` | Your organization's short ID, in lowercase |
| `sub` | The user's `externalId` |
| `aud` | The origin you send the request to, such as `https://myorg.embed-omniapp.co` |
| `iat`, `exp` | A short lifetime. The SDK uses five minutes. Omni refuses an assertion issued too long ago or expiring too far ahead. |
| `jti` | A random string of 16 to 256 characters. Omni redeems each one once. |

The other claims are the identity and role parameters, under the same names as the [embed URL parameters](https://docs.omni.co/embed/setup/url-parameters.md), such as `name` and `connectionRoles`.

A success response is:

```json wrap theme={null}
{ access_token, token_type: "Bearer", expires_in, scope, resource }
```

A refusal includes `error`, `error_description`, and `error_reason` properties. See the [error table in the MCP token reference](https://docs.omni.co/embed/customization/ai-chat/mcp-reference.md#error-codes) for more information about error codes.

### 2. Call the MCP Server as the user

Send the token from the previous step as a `Bearer` token to your Omni instance's MCP endpoint. Omni serves the same endpoint at `/mcp` and `/mcp/https`, so use either path.

If you also embed Omni, request the token on the same host your embed URLs use.

> [!NOTE]
>
> Omni identifies embed users by `externalId` and host, so a token from `blobsrus.embed-omniapp.co` and an embed URL on `analytics.blobsrus.com` are two different users.

```typescript wrap theme={null}
const response = await fetch('https://<your-omni-instance>.embed-omniapp.co/mcp', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
    Accept: 'application/json, text/event-stream',
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'tools/call',
    params: {
      name: 'getData',
      arguments: {
        modelId: '<MODEL_ID>',
        prompt: 'Top 5 categories by revenue',
      },
    },
  }),
})
```

Replace `<MODEL_ID>` with a model the user can query. Use the [`pickModel`](https://docs.omni.co/ai/mcp/tools) tool to list them.

The token can call the tools that are enabled in [**AI Hub > MCP**](https://docs.omni.co/ai/settings/mcp.md) and that the user's roles allow.

> [!NOTE]
>
> **The MCP endpoints don't support the `X-MCP-User-ID` or `X-MCP-User-Required` headers**. Omni returns a `403` if a token request sends `X-MCP-User-ID` or sets `X-MCP-User-Required: true`.

### 3. Cache the token and implement 401 handling

Cache one token per `externalId` until shortly before its `expiresAt`, in a store all your servers share. Each call to `createEmbedMcpToken` ends the user's previous token, so servers that each mint their own token will keep logging each other out.

When Omni answers `401` - which means the token stopped working - call `createEmbedMcpToken` again and retry once:

```typescript wrap title="Refresh on 401" theme={null}
let res = await callMcp(await getCachedToken(user))
if (res.status === 401) {
  const { accessToken } = await createEmbedMcpToken(props) // save it in your cache
  res = await callMcp(accessToken)
}
```

Common causes of a `401` include:

* A newer token was issued for the user
* The token's session was deleted or expired
* The feature was turned off for your organization

Omni limits how many tokens each user can request in a short window. Over that limit, the call throws with the code `rate_limited` and a `retryAfterSeconds` value. Reuse the cached token instead of requesting another token.

## Limiting what the agent can do

Omni gives each token only the tools that are enabled in [**AI Hub > MCP**](https://docs.omni.co/ai/settings/mcp.md) and that the user's roles allow. For example, a **Viewer** token doesn't get `getData`. To narrow what your agent can do for a user, combine the following:

**1. Data access**

1. Use the `connectionRoles`, `modelRoles`, and `userAttributes` parameters in your mint calls to control which connections, models, and rows the user can query.
2. Define [access filters](https://docs.omni.co/modeling/topics/parameters/access-filters.md) in your model to set up row-level filters using `userAttributes`. An attribute doesn't filter anything until a topic's access filter uses it. Take attribute values such as `tenant_id` from the user's authenticated session on your server, never from a request the client controls.

**2. Topics**

1. Use the [`ai_chat_topics`](https://docs.omni.co/modeling/models/ai-chat-topics) model parameter to limit which topics the AI can pick.
2. You can also use the `X-MCP-Model-ID` and `X-MCP-Topic-Name` headers to pin a session to one model or topic.

**3. MCP tools**

Your agent is the MCP client, so it decides which tools the LLM sees. After calling `tools/list`, pass only the tools you want, such as `pickModel`, `pickTopic`, and `getData`, to your LLM. Hosted agent APIs that take an MCP endpoint URL usually accept an allowed-tools list that does the same thing.

> [!WARNING]
>
> Omni doesn't check which tools your agent passes to the LLM. On the server, Omni limits tools only by the **AI Hub > MCP** settings and the user's roles. `askOmni` calls other tools on the server, and your tool list has no effect on those calls. What `askOmni` can do is limited only by the user's own access.

## Managing tokens

Tokens show up as embed sessions in **Settings > Embed > Sessions**. See [Managing user sessions](https://docs.omni.co/embed/admin/sessions.md) for more information.

Rotating or deleting an embed secret doesn't end tokens already issued. A token lives as long as its session, so to immediately end a token, delete the user's session. Every active secret can sign new assertions, so you can add a new secret before you delete the old one. See [Managing embed secrets](https://docs.omni.co/embed/admin/secrets.md) for more information.

## Troubleshooting

> **MCP requests return 401 right after minting a token**
>
> Another server minted a newer token for the same `externalId`, which ended this one. Share the token cache across your servers.

> **MCP requests return 403**
>
> Check that the request doesn't include `X-MCP-User-ID` or `X-MCP-User-Required`, and that the **MCP server** setting is enabled in [**AI Hub > MCP**](https://docs.omni.co/ai/settings/mcp.md#mcp-server).

For every error `createEmbedMcpToken` can throw, see [Error codes in the MCP token reference](https://docs.omni.co/embed/customization/ai-chat/mcp-reference.md#error-codes).

## Next steps

* Look up parameters and error codes in the [MCP token reference](https://docs.omni.co/embed/customization/ai-chat/mcp-reference.md)
* Review the [MCP Server tools](https://docs.omni.co/ai/mcp/tools.md) your agent can call and enable the ones you need in [**AI Hub > MCP**](https://docs.omni.co/ai/settings/mcp.md)
* [Optimize your models for AI](https://docs.omni.co/modeling/develop/ai-optimization.md) so the agent gets better answers
* Set [AI credit limits for embed entities](https://docs.omni.co/embed/admin/ai-credits.md) to manage usage per customer


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