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

# AI tools from your server (MCP)

> Host an MCP server and Ola can use its tools for every store that installed your app, even when your app is not open

[WebMCP tools](https://docs.salesive.com/apps/webmcp-tools.md) live in your embedded page, so Ola can use
them only while the merchant has your app open. If your app has a backend, you
can also host a **remote MCP server**. Ola connects to it whenever a merchant
who installed your app chats with her, and in scheduled tasks too.

Your server speaks the standard [Model Context Protocol](https://modelcontextprotocol.io)
over the **Streamable HTTP** transport, so any MCP server SDK works.

## How a call flows

1. You set an **MCP server URL** in the developer editor (Production and
   Development tabs, like the webhook URL).
2. When a merchant of a store that has your app installed sends Ola a message,
   Salesive calls your server: `initialize`, then `tools/list`.
3. Your tools appear to Ola beside her own, named after your app.
4. When Ola wants one, the merchant sees the same **approval card** as for
   WebMCP tools, with **Approve**, **Reject** and **Always allow**.
5. On approval, Salesive sends `tools/call` to your server and passes the result
   back to Ola. The card shows the outcome.

Every request carries a **session token** that tells you which store and which
merchant it is for.

## Verifying the session token

Each request to your server has an `Authorization: Bearer <token>` header. The
token is a JWT signed **ES256** by Salesive, with the same key and the same
claims as the [session tokens](https://docs.salesive.com/apps/session-tokens.md) your embedded page gets
from `getSessionToken()`. Verify it against Salesive's public keys at
`https://api.salesive.com/api/v1/oauth/jwks`, using the key whose `kid`
matches the token header. No secret is involved, so it works whether or not
your app has a webhook secret.

| Claim | Meaning |
| - | - |
| `aud` | Your app's client ID. Reject the token if it does not match. |
| `iss` | `https://api.salesive.com` |
| `shop`, `dest` | The store the request is for (same value). |
| `sub` | The merchant Ola is acting for. |
| `role` | `owner` or `staff`. |
| `installation` | The installation id (unique per app and store). |
| `env` | `production` or `development`. |
| `purpose` | `mcp`. Refuse anything else. |
| `iat`, `nbf`, `exp` | Issued, not before, expiry (five minutes after issue). |
| `jti` | A random id. |

```js theme={null}
import { createRemoteJWKSet, jwtVerify } from "jose";

const jwks = createRemoteJWKSet(new URL("https://api.salesive.com/api/v1/oauth/jwks"));

async function salesiveSession(req) {
  const token = (req.headers.authorization || "").replace(/^Bearer /, "");
  // Throws if the signature, algorithm, audience, issuer or expiry is wrong.
  const { payload } = await jwtVerify(token, jwks, {
    algorithms: ["ES256"],
    audience: process.env.SALESIVE_CLIENT_ID,
    issuer: "https://api.salesive.com",
    clockTolerance: 5,
  });
  if (payload.purpose !== "mcp") throw new Error("wrong purpose");
  return payload; // payload.shop is the store, payload.sub the merchant
}
```

The [session tokens](https://docs.salesive.com/apps/session-tokens.md) page has a version with no
dependencies, and the app starter's `server/sessionToken.js` does the same
checks: call `verifySessionToken(token, { purpose: "mcp" })`.

Then look the `shop` up in your own install table and use the API token you
stored for it at install time. Answer `401` when the token is missing or wrong.

> [!WARNING]
>
> Only accept `ES256`, and always check `aud` and `purpose`. Salesive signs
> tokens for every app with the same key, so without the `aud` check another
> app's token would pass, and a dashboard session token (`purpose: "session"`)
> must not be accepted by your MCP server. Never trust a `shop` header or a
> tool argument to tell you which store a call is for: use the `shop` claim.

## A minimal server

```js theme={null}
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

const app = express();
app.use(express.json());

app.post("/mcp", async (req, res) => {
  let session;
  try {
    session = salesiveSession(req);
  } catch {
    return res.status(401).end();
  }

  const server = new McpServer({ name: "stock-sync", version: "1.0.0" });
  server.registerTool(
    "get_stock",
    {
      title: "Get stock",
      description: "Stock level for a SKU in this store's warehouse.",
      inputSchema: { sku: z.string() },
      annotations: { readOnlyHint: true },
    },
    async ({ sku }) => ({
      content: [{ type: "text", text: `SKU ${sku}: ${await stockFor(session.shop, sku)} units` }],
    }),
  );

  // Stateless: one transport per request.
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  res.on("close", () => transport.close());
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000);
```

## Approval and channels without a screen

In the dashboard chat, every call needs the merchant's approval unless they
chose **Always allow** for that tool. The grant is per store installation.

Ola also runs where nobody can click a card, such as **scheduled tasks**. There
she only sees tools the merchant marked **Always allow**, and tools you annotate
`readOnlyHint: true` (and not `destructiveHint: true`). Those run without
asking.

> [!WARNING]
>
> Mark a tool `readOnlyHint` only if it changes nothing. A tool that writes data
> while claiming to be read only will get your app removed from the marketplace.

Workflow AI steps never get app tools.

## Limits

| Limit | Value |
| - | - |
| Transport | Streamable HTTP, JSON or SSE responses |
| URL | Production must be public `https`. Redirects are refused. Private, loopback and link-local addresses are refused. |
| `initialize` + `tools/list` | 8 seconds in total, 3 pages, 512 KB |
| `tools/call` | 30 seconds, 1 MB response |
| Tools per app | 32 (extra tools are dropped) |
| Tools per store, all apps | 48, and 96 KB of definitions |
| Tool name | Letters, digits and `_`, up to 30 characters. Other characters become `_`. |
| Description | 500 characters |
| `inputSchema` | JSON Schema with `type: "object"`, 8 KB |
| Result shown to Ola | 16,000 characters. Text and embedded text resources are kept; images and audio are replaced by a note. |

Tool lists are cached for about five minutes, so a new tool can take that long
to appear. A server that fails to answer is skipped for a minute.

Each call opens a fresh MCP session, so a stateless server is the simplest fit.

## Security model

* The token says which store and merchant a request is for, and expires in five
  minutes. Verify it on every request.
* Salesive never puts your URL, token or secret in front of the model.
* Your tool results reach Ola marked as untrusted third-party data. Ola will
  not follow instructions written inside them.
* A merchant can uninstall your app at any time. From then on Salesive stops
  calling your server for that store within about 30 seconds, and you should
  reject tokens for stores you no longer have.

## Testing

On the **Development** tab, set your local URL (for example
`http://localhost:3000/mcp`). Your own test installs use it, and their tokens
say `env: "development"`. Salesive's hosted Ola cannot reach your machine, so use a
public tunnel (for example `cloudflared` or `ngrok`) with an `https` address
while you test against the real dashboard.

## Next steps

> [card] [WebMCP tools](https://docs.salesive.com/apps/webmcp-tools.md)
>
> Tools that run inside your embedded page.

> [card] [Session tokens](https://docs.salesive.com/apps/session-tokens.md)
>
> The same key and claims, for requests from your embedded page.


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