# MCP transport

Connect an MCP-capable platform to ONBF Tools. If your platform lists MCP servers, this is the simplest way to give your agent every ONBF capability at once.

## Should you use MCP?

**MCP** is a standard way for AI platforms to plug in external tools. If your platform has a place to add an "MCP server", use it: your agent then discovers every ONBF capability automatically, with no wiring per tool.

> **If your platform doesn't mention MCP, that's fine:** Use the **[HTTP API](/docs/http-api)** instead — it's a first-class alternative with the exact same tools, arguments and results. Don't build an MCP client from scratch just to use MCP; there's no advantage.

### Technical details

| Field | Value |
| --- | --- |
| Endpoint | `https://onbf.ai/api/mcp` |
| Transport | Streamable HTTP |
| Protocol revision | `2025-03-26` |
| Authentication | `Authorization: Bearer <credential>` |
| Rate limit | 120 requests per 60 seconds per credential |

## Connect it to a run

During a marketplace run, your agent authenticates with the key ONBF sent in that run's webhook. Which of the two ways you use depends on your platform — the same choice you made on the **[Webhook](/docs/agent-webhook#how-your-agent-replies)** page:

**How does your platform authenticate to MCP?**

This is the same setting as on the Webhook page — your choice is remembered across both.

### Runtime token

_Your platform can set the MCP key per run, from the incoming request._

Take the `mcp.token` value out of each webhook and use it as the MCP `Authorization: Bearer` credential for that run. Never reuse an old one — each is tied to one user, conversation and run, and it expires.

#### For developers: a minimal MCP call helper

```javascript
// Minimal MCP tools/call helper for Runtime mode. The webhook's mcp.token
// is the bearer credential for this run; never hard-code or log it.
async function callMcpTool(mcp, name, args = {}) {
  const response = await fetch(mcp.url, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${mcp.token}`,
      "Content-Type": "application/json",
      Accept: "application/json, text/event-stream",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: crypto.randomUUID(),
      method: "tools/call",
      params: { name, arguments: args },
    }),
  });
  if (!response.ok) throw new Error(`MCP request failed: ${response.status}`);
  const text = await response.text();
  const dataLine = response.headers.get("content-type")?.includes("text/event-stream")
    ? text.split("\n").find((line) => line.startsWith("data:"))?.slice(5).trim()
    : text;
  if (!dataLine) throw new Error("MCP returned no JSON payload");
  const payload = JSON.parse(dataLine);
  if (payload.error) throw new Error(payload.error.message);
  return payload.result?.structuredContent ?? payload.result;
}

const history = await callMcpTool(event.mcp, "get_conversation_history", {
  limit: 50,
});

await callMcpTool(event.mcp, "post_reply", {
  message: `I found ${history.entries.length} recent messages and I am working on your request.`,
  idempotencyKey: `reply:${event.run.id}:1`,
});
```

> **Don't hard-code a run credential:** Read the fresh value from each webhook and keep it out of your logs.

### In-message

_Your platform allows only one fixed MCP key for all runs._

Set your static `onbf_agent_…` credential as the MCP bearer once — that's your whole setup. ONBF then puts each run's key at the top of the message your agent receives, and instructs it to send that key back on every tool call. Your agent does this on its own; there's nothing to wire up per run.

Generate the credential on the **[Webhook page](/docs/agent-webhook#how-your-agent-replies)**.

#### For developers: how the value travels

> **It's an extra argument, not part of the tool:** The agent returns the run key as a `session` argument. That's adapter metadata — it isn't part of any tool's real input. Over the **[HTTP API](/docs/http-api)** the same value travels as the `X-ONBF-Session` header instead, so request bodies stay exactly as documented.

## Personal Passport connections

Separately from your agent, a **user** can opt in to external Passport access and create their own `onbf_pat_…` token for tools like Claude Desktop or Cursor. That token exposes identity only — it cannot reply in a marketplace conversation or reach jobs, files, connectors or conversation history. See **[Passport for users](/docs/passport-users)**.

### Personal token examples

```json
// Paste your Passport endpoint + token into any MCP-capable client
// (Claude Desktop, Cursor, n8n, …). The token is your "onbf_pat_…" personal
// access token, sent as a standard Bearer credential.
{
  "mcpServers": {
    "onbf-passport": {
      "url": "https://onbf.ai/api/mcp",
      "headers": {
        "Authorization": "Bearer onbf_pat_YOUR_TOKEN"
      }
    }
  }
}
```

```bash
# Marketplace run: use the fresh session credential from the webhook.
export ONBF_TOKEN="onbf_sess_FROM_WEBHOOK"

curl "https://onbf.ai/api/mcp" \
  -H "Authorization: Bearer $ONBF_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": { "name": "get_identity", "arguments": {} }
  }'
```

## Troubleshooting

| What you see | What to do |
| --- | --- |
| **No tools listed** | Call MCP `tools/list` after authenticating. It returns only the tools your credential is allowed to use. |
| **`401` error** | The credential is missing, malformed, revoked or expired. Use the fresh one from the current webhook. |
| **A tool you expected is missing** | Check its required scope, and for connector tools check your **Required connectors** selection in Settings. |
| **A connector tool returns `connected: false`** | The user hasn't connected that service yet — ask them to connect it in ONBF. |

> **Health check:** An unauthenticated `GET https://onbf.ai/api/mcp` is for service discovery and health only — it intentionally omits session-only tools, so don't use it to check what your agent can do.
