---
title: "MCP Server"
description: "Expose your agent-native app as a remote MCP server so Claude, ChatGPT, Claude Code, Cursor, and other AI tools can call your app's actions directly."
---

# MCP Server

**This page: the lower-level MCP server reference.** How every agent-native app exposes its actions over MCP — the auto-mounted endpoint, auth modes, the `tools/call` / `ask-agent` surface, and custom mounting. Reach for it when you need server internals; for connecting a host, start with [External Agents](/docs/external-agents).

| If you want to…                                              | Read                                     |
| ------------------------------------------------------------ | ---------------------------------------- |
| Connect an external agent/host to your app                   | [External Agents](/docs/external-agents) |
| Give your agent more tools (consume other MCP servers)       | [MCP Clients](/docs/mcp-clients)         |
| Build inline UIs that render in Claude/ChatGPT               | [MCP Apps](/docs/mcp-apps)               |
| Build generated inline UI inside Agent-Native chat           | [Generative UI](/docs/generative-ui)     |
| Lower-level MCP server reference (auth, tools, custom mount) | **This page** — MCP Server               |

Every agent-native app automatically exposes a remote MCP (Model Context Protocol) server, so external AI tools like Claude, ChatGPT custom MCP apps, Claude Code, Cursor, Codex, and VS Code GitHub Copilot can discover and call your app's actions directly — no extra code needed. If your goal is to _connect_ one of those hosts to a hosted app, [External Agents](/docs/external-agents) covers the recommended single Dispatch connector, per-app URLs, OAuth, MCP Apps inline UIs, and deep links. This page documents what sits underneath that.

## Overview {#overview}

MCP is the standard protocol for connecting AI tools to external capabilities. When you deploy an agent-native app, it auto-mounts an MCP endpoint alongside the existing A2A endpoint. Any MCP-compatible host can connect and use your app's tools.

Key concepts:

- **Auto-mounted** — every app gets `/mcp` for free, no setup required
- **Streamable HTTP** — uses the modern MCP transport over standard HTTP (POST + SSE)
- **Same actions** — the exact same action registry that powers agent chat and A2A
- **`ask-agent` tool** — a meta-tool that delegates to the full agent loop for complex tasks
- **MCP Apps** — actions can advertise interactive UI resources through the official `io.modelcontextprotocol/ui` extension
- **Standard remote MCP OAuth** — OAuth 2.1 discovery, dynamic client registration, authorization-code + PKCE, refresh tokens
- **Bearer auth fallback** — uses `ACCESS_TOKEN`, `ACCESS_TOKENS`, or connect-minted JWTs for clients that cannot run OAuth

## Fetch-first discovery {#fetch-first-discovery}

Agent-Native additionally serves `GET /.well-known/mcp.json` as an HTTP
compatibility catalog. It is designed for simple agents and integrations that
can use `fetch` or `curl` but do not have a running browser. The catalog
describes eligible action endpoints, their JSON schemas, whether they are
read-only, and whether authentication is required. Each tool can be called at
`POST /mcp/tool/:name`; that route reuses the same action dispatcher as the
framework's existing WebMCP HTTP route.

This document is an Agent-Native interoperability convention, not a claim that
`/.well-known/mcp.json` is part of the WebMCP browser API or the MCP
Streamable HTTP protocol. MCP hosts should continue to connect to `/mcp` and
run `initialize` → `tools/list` → `tools/call`. Browser agents should use
page-local `document.modelContext` when they need live UI state. Both remain
first-class paths.

For object-scoped resources, follow the resource page's alternate JSON link or
embedded discovery metadata. For example, a Clips share page points to an
agent context document that advertises complete transcript and JPEG frame URLs,
so a non-browser agent can fetch the content directly without opening the
player.

<Diagram id="doc-block-1362vx8" title="Your app as an MCP server" summary={"External hosts connect over Streamable HTTP. Each action is one tool; ask-agent delegates to the full agent loop."}>

```html
<div class="diagram-mcp">
  <div class="diagram-col">
    <div class="diagram-node">Claude</div>
    <div class="diagram-node">ChatGPT</div>
    <div class="diagram-node">Cursor · Codex</div>
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-panel">
    <span class="diagram-pill accent">POST /mcp</span
    ><small class="diagram-muted">Streamable HTTP</small
    ><small class="diagram-muted"
      >initialize &rarr; tools/list &rarr; tools/call</small
    >
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-col">
    <div class="diagram-box" data-rough>
      each action<br /><small class="diagram-muted">= one tool</small>
    </div>
    <div class="diagram-box" data-rough>
      ask-agent<br /><small class="diagram-muted">&rarr; full agent loop</small>
    </div>
  </div>
</div>
```

```css
.diagram-mcp {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.diagram-mcp .diagram-col {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.diagram-mcp .diagram-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.diagram-mcp .diagram-arrow {
  font-size: 20px;
  line-height: 1;
}
```

</Diagram>

## MCP vs A2A {#mcp-vs-a2a}

Both protocols are auto-mounted. Use whichever fits your use case:

|                    | MCP                                                                      | A2A                                          |
| ------------------ | ------------------------------------------------------------------------ | -------------------------------------------- |
| **Best for**       | External tools calling your app                                          | Agent-to-agent communication                 |
| **Protocol**       | MCP Streamable HTTP                                                      | JSON-RPC 2.0                                 |
| **Tool discovery** | `tools/list`                                                             | Agent card at `/.well-known/agent-card.json` |
| **Endpoint**       | `/mcp`                                                                   | `/_agent-native/a2a`                         |
| **Supported by**   | Claude, ChatGPT, Claude Code, Cursor, Codex, Cowork, and other MCP hosts | Other agent-native apps                      |
| **Execution**      | Direct tool calls (no extra LLM)                                         | Full agent loop (LLM reasoning)              |

You can also use the `ask-agent` MCP tool to get the best of both worlds — call it from Claude Code and let your app's agent reason through complex tasks.

## Manual MCP host config {#manual-config}

For the recommended one-command setup, use [External Agents](/docs/external-agents). If you are hand-writing MCP config for an OAuth-capable host, add your app as a remote MCP server with no static headers:

```bash
claude mcp add --transport http mail https://mail.example.com/mcp
```

Or write the entry by hand in `.mcp.json` (project scope) or `~/.claude.json` (user scope):

```jsonc
// .mcp.json
{
  "mcpServers": {
    "mail": {
      "type": "http",
      "url": "https://mail.example.com/mcp",
    },
  },
}
```

Then run `/mcp` in Claude Code and choose **Authenticate**. For clients that cannot perform remote MCP OAuth, use the Connect page or a static bearer-token entry with `headers.Authorization`. Once authenticated, you can use your app's tools naturally:

```
> draft an email to John about the Q3 report

Claude Code calls: draft-email(to: "john@example.com", subject: "Q3 Report", body: "...")
```

## Connecting from other MCP hosts {#other-clients}

Any MCP host that supports Streamable HTTP transport can connect. The endpoint is:

```
POST https://your-app.example.com/mcp
```

The server supports the standard MCP handshake: `initialize` → `initialized` → `tools/list` → `tools/call`.

<Endpoint id="doc-block-86ic5g" title="MCP endpoint" summary="The auto-mounted Streamable HTTP endpoint every agent-native app exposes." method="POST" path="/mcp" auth="Standard remote MCP OAuth (Bearer access token), connect-minted JWT, or static ACCESS_TOKEN/ACCESS_TOKENS" params={[
  {
    "name": "Authorization",
    "in": "header",
    "type": "string",
    "required": false,
    "description": "Bearer access token. Required except for loopback local-dev probes."
  },
  {
    "name": "method",
    "in": "body",
    "type": "string",
    "required": true,
    "description": "MCP method, e.g. initialize, tools/list, tools/call."
  }
]} request={{
  "contentType": "application/json",
  "example": "{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"ask-agent\",\n    \"arguments\": { \"message\": \"Summarize Q3 signups by source\" }\n  }\n}"
}} responses={[
  {
    "status": "200",
    "description": "MCP result (POST + SSE)."
  },
  {
    "status": "401",
    "description": "Unauthenticated — responds with a WWW-Authenticate header pointing at OAuth discovery."
  }
]}>

Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `initialized` → `tools/list` → `tools/call`) plus `resources/list`, `resources/templates/list`, and `resources/read` when an action declares `mcpApp`. Each action maps to one tool; `ask-agent` delegates to the full agent loop.

</Endpoint>

If an action declares `mcpApp`, the server advertises the official MCP Apps extension and supports the resource routes needed to render it inline:

- Advertises `io.modelcontextprotocol/ui` and supports `resources/list`, `resources/templates/list`, and `resources/read` for the app resource.
- Hosts that render MCP Apps show the UI inline; hosts that don't can still call the tool and use the deep-link fallback.
- Product UIs should use `embedApp()` so the inline surface is the real React app route, or a focused route that renders a shared React component such as an Analytics chart — never a separate plain-HTML implementation.
- The server emits both standard MCP Apps metadata and ChatGPT Apps SDK compatibility metadata so app-capable hosts find the same `ui://` resource.
- Host support varies by plan, release channel, and client version. See [MCP Apps → Host support](/docs/mcp-apps#client-support) for rendering hosts and link-out clients, including Claude Code.

### MCP App embed bridge {#mcp-app-embed-bridge}

`embedApp()` is the low-level URL-first MCP App helper: it launches a signed app
route inline through transplant (Claude), controlled-frame (ChatGPT), or direct
navigation, mediates host actions over the `ui/*` JSON-RPC bridge (and the
`agentNative.mcpHost.*` postMessage relay for the controlled-frame path), and
clamps the resource shell height so a full-app route does not render as an
oversized chat artifact.

See [MCP Apps](/docs/mcp-apps#mcp-app-bridge) for the full embed bridge details — transplant vs controlled-frame, the `ui/*` and postMessage tables, `create_embed_session` / `embedStartUrl`, CSP and domain rules, extension `srcDoc` embedding, height clamping, and the host bridge client API.

## Tools {#tools}

Every caller gets a **compact catalog by default** (template-declared app actions plus the cross-app builtins), with the full action surface served only on explicit opt-in and `tool-search` always available to reach the rest. See [External Agents Catalog → Catalog tiers](/docs/external-agents-catalog#catalog-tiers) for the full explanation.

Each action maps directly to one MCP tool:

| Action property    | MCP tool property |
| ------------------ | ----------------- |
| `tool.description` | `description`     |
| `tool.parameters`  | `inputSchema`     |
| Action name        | Tool name         |

When `mcpApp` is present, the tool entry also includes `_meta.ui.resourceUri`, `_meta["ui/resourceUri"]`, and `_meta["openai/outputTemplate"]`, and the corresponding `ui://` resource is returned as `text/html;profile=mcp-app`.

### The `ask-agent` tool {#ask-agent}

In addition to individual action tools, every MCP server includes an `ask-agent` meta-tool. This sends a natural-language message to the app's AI agent and returns the response.

Hosted MCP requests use a bounded inline wait so a long agent loop does not
keep one `tools/call` open until a client or gateway timeout. The optional
`async` argument starts the durable task and returns immediately; without it,
the server waits briefly for a fast completion and then returns a task handle
when more work remains. The hosted wait is capped at 25 seconds.

Use `ask-agent` for complex tasks that benefit from the agent's reasoning and context:

```json
{
  "name": "ask-agent",
  "arguments": {
    "message": "Draft a follow-up email to the Q3 planning thread with John, summarizing the action items we discussed",
    "async": true
  }
}
```

The agent runs the same loop as the interactive chat — it can call multiple tools, reason about context, and produce a thoughtful response. A task that is not finished returns JSON containing `taskId`, `status`, `pollAfterMs`, and:

```json
{
  "poll": {
    "tool": "ask_app_status",
    "arguments": {
      "app": "mail",
      "taskId": "task_123",
      "taskHandle": "opaque_handle"
    }
  }
}
```

Call `ask_app_status` with those arguments until the task reaches a terminal
state. Dispatch's app-owned `ask_app` uses the same durable task and status
contract, with the selected-app grant checked on both submission and polling.

If a bounded transient status read is unavailable, `ask_app_status` can return
`status: "unknown"`, `statusRead: "unavailable"`, `retryable: true`, the
original `app` and `taskId`, and `pollAfterMs` / `poll`. This means the status
read is unavailable—not that the durable task failed. Retry `ask_app_status`
with the returned `poll.arguments` unchanged, including `taskHandle` when
present; legacy results without a handle must reuse the same `app` and `taskId`.
Never resubmit `ask_app` to recover, because submission can duplicate work.
Permanent authentication, not-found, and protocol failures remain errors.

## Authentication {#authentication}

The MCP endpoint supports standard remote MCP OAuth plus the existing bearer-token fallback:

| Mode                        | How it works                                                                                                          |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Standard MCP OAuth          | Client discovers auth from `WWW-Authenticate`, registers, runs PKCE, and sends `Authorization: Bearer <access-token>` |
| Connect-minted JWT          | `npx @agent-native/core@latest connect` / the Connect page mints a per-user, revocable JWT                            |
| `ACCESS_TOKEN`              | Static bearer token — client sends `Authorization: Bearer <token>`                                                    |
| `ACCESS_TOKENS`             | Comma-separated list of valid static bearer tokens                                                                    |
| `A2A_SECRET`                | JWT-based auth — tokens are verified cryptographically                                                                |
| _(none set, loopback only)_ | No auth required for local dev probes                                                                                 |

For OAuth-capable MCP hosts, configure the remote server URL with no static headers:

```bash
claude mcp add --transport http agent-native https://dispatch.agent-native.com/mcp
```

The first unauthenticated MCP request receives:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://dispatch.agent-native.com/.well-known/oauth-protected-resource", scope="mcp:read mcp:write mcp:apps offline_access"
```

Discovery endpoints:

| Endpoint                                  | Purpose                                     |
| ----------------------------------------- | ------------------------------------------- |
| `/.well-known/oauth-protected-resource`   | RFC 9728 protected-resource metadata        |
| `/.well-known/oauth-authorization-server` | OAuth authorization server metadata         |
| `/mcp/oauth/register`                     | Dynamic public-client registration          |
| `/mcp/oauth/authorize`                    | Browser authorization + consent             |
| `/mcp/oauth/token`                        | Authorization-code and refresh-token grants |

<Diagram id="doc-block-79kjly" title="OAuth discovery flow" summary={"A 401 kicks off discovery, registration, and a PKCE authorize → token exchange. The Bearer token is audience-bound and scoped."}>

```html
<div class="diagram-oauth">
  <div class="diagram-box" data-rough>
    first request<br /><small class="diagram-muted">no token</small>
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-pill warn">401 · WWW-Authenticate</div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-panel">
    <span class="diagram-pill">/.well-known/oauth-protected-resource</span
    ><span class="diagram-pill">/.well-known/oauth-authorization-server</span
    ><small class="diagram-muted">discover</small>
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-col">
    <div class="diagram-pill">register</div>
    <div class="diagram-pill">authorize (PKCE)</div>
    <div class="diagram-pill">token</div>
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-box" data-rough>
    Bearer access token<br /><small class="diagram-muted"
      >audience-bound · mcp:read / write / apps</small
    >
  </div>
</div>
```

```css
.diagram-oauth {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.diagram-oauth .diagram-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.diagram-oauth .diagram-col {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.diagram-oauth .diagram-arrow {
  font-size: 20px;
  line-height: 1;
}
```

</Diagram>

Access tokens are signed JWTs whose audience is the exact MCP resource URL. The server accepts only tokens issued for itself and applies scopes before listing/calling tools:

| Scope       | Allows                                      |
| ----------- | ------------------------------------------- |
| `mcp:read`  | read-only actions                           |
| `mcp:write` | mutating actions and `ask-agent`            |
| `mcp:apps`  | MCP Apps resources (`ui://` HTML resources) |

Refresh tokens are stored only as hashes and are not rotated: a refresh returns the same token, and refresh grants have no fixed expiry. Every request and every refresh re-checks that the user still belongs to the token's organization, so a removed member loses MCP access on their next request. `npx @agent-native/core@latest connect` writes this URL-only OAuth entry for Claude Code clients by default; keep the Connect page, `npx @agent-native/core@latest connect --token <token>`, and static bearer config for local stdio proxying, older clients, and emergency/debug flows.

New authorization codes and refresh tokens retain their issuance owner binding. Unbound legacy grants and owner mismatches return `invalid_grant`. Renewing a revoked or deleted grant returns `invalid_grant` even when the update affects no rows. A failed renewal write returns retryable HTTP 503 with `Retry-After: 5`. Neither failure issues an access token.

The OAuth owner-binding update requires every existing OAuth connection to [reconnect once](/docs/external-agents#reconnect). Old OAuth access tokens are refused after rollout. Unbound legacy refresh tokens return `invalid_grant`, and pending old authorization codes must restart consent. Newly issued access tokens use their configured lifetime and live membership checks. Legacy A2A-format `mcp-connect` credentials and cross-app A2A tokens are unchanged by this cutover. Connections using old OAuth-format Connect tokens must reconnect too.

## Custom MCP setup {#custom-setup}

The MCP server is auto-mounted by the agent-chat plugin. For most apps, no configuration is needed. If you need custom behavior, you can mount it manually in a server plugin:

```ts
// server/plugins/mcp.ts
import { mountMCP } from "@agent-native/core/mcp";
import { autoDiscoverActions } from "@agent-native/core/server";

export default defineNitroPlugin(async (nitro) => {
  const actions = await autoDiscoverActions(import.meta.url);

  mountMCP(nitro, {
    name: "My App",
    description: "Custom MCP server",
    actions,
    // Optional: provide ask-agent handler
    askAgent: async (message) => {
      // Your custom agent logic
      return "Response";
    },
    // Optional: override the route prefix (default "/_agent-native")
    // routePrefix: "/_agent-native",
  });
});
```

## Example: analytics from Claude Code {#example}

You have a deployed analytics app at `analytics.example.com`. From Claude Code:

```bash
claude mcp add --transport http analytics https://analytics.example.com/mcp
```

Or add it by hand in `.mcp.json`:

```jsonc
// .mcp.json
{
  "mcpServers": {
    "analytics": {
      "type": "http",
      "url": "https://analytics.example.com/mcp",
    },
  },
}
```

Now in Claude Code:

```
> How many signups did we get last week?

Claude Code calls: run-query(sql: "SELECT count(*) FROM signups WHERE created_at > now() - interval '7 days'")
→ "1,247 signups last week"
```

For more complex analysis:

```
> Ask the analytics agent to prepare a full breakdown of Q3 signups by source, with trends

Claude Code calls: ask-agent(message: "Prepare a full breakdown of Q3 signups by source, with trends")
→ The analytics agent runs multiple queries, reasons about the data, and returns a formatted report
```

## What's next {#whats-next}

- [**External Agents**](/docs/external-agents) — the recommended connect flow and per-host setup
- [**External Agents Catalog**](/docs/external-agents-catalog) — catalog tiers and tool exposure for template authors
- [**MCP Apps**](/docs/mcp-apps) — inline UI resources for hosts that render them
- [**MCP Clients**](/docs/mcp-clients) — the other direction: your app consuming external MCP servers
- [**A2A Protocol**](/docs/a2a-protocol) — the sibling protocol for agent-to-agent calls
- [**Actions**](/docs/actions-overview) — how an action becomes an MCP tool
