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

# Server handlers

> Serve an MCP server from your app with addMcp, and verify Reintersect-signed tokens anywhere with verifyReintersectToken.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

Reintersect doesn't ship its own MCP server framework. Build the server with any library that
produces a fetch-style handler, then either mount it on your app with `.addMcp`, or run it on its own
and verify Reintersect's tokens with `verifyReintersectToken`.

## Mount a server with addMcp

```ts src/mcp.ts theme={null}
import { createMcpHandler, fromJsonSchema, McpServer } from "@modelcontextprotocol/server";

export const mcpServer = createMcpHandler(
  ({ authInfo }) => {
    const server = new McpServer({ name: "launchpad", version: "1.0.0" }, { capabilities: { tools: {} } });

    server.registerTool(
      "search_deployments",
      {
        description: "Search deployments by project or branch",
        inputSchema: fromJsonSchema<{ query: string }>({
          type: "object",
          properties: { query: { type: "string" } },
          required: ["query"],
        }),
        annotations: { readOnlyHint: true, openWorldHint: false },
      },
      async ({ query }) => ({
        content: [{ type: "text", text: await searchDeployments(query, authInfo?.extra?.reintersect) }],
      }),
    );

    return server;
  },
  { legacy: "stateless" },
);
```

```ts src/app.ts theme={null}
import { createReintersectApp } from "@reintersect/sdk/app";
import { mcpServer } from "./mcp";

export const app = createReintersectApp({ name: "Launchpad", slug: "launchpad" }).addMcp(mcpServer, {
  auth: "reintersect",
});
```

`addMcp(handler, { auth })`:

| Argument | Description |
| - | - |
| `handler` | A function `(request: Request, extra?: { authInfo? }) => Response \| Promise<Response>`, or an object with that `fetch` method. |
| `auth` | Required: `"oauth"`, `"reintersect"` or `"none"`. See [Authentication](/mcp/authentication). |
| `appId` | Optional, with `auth: "reintersect"`: only accept tokens whose `rei_app` is this app id. Hosted apps check their own app id without it. |

The app serves the handler at `/mcp` and every path under it, next to its delivery path and
[routes](/hosting/routes), so routes may not use `/mcp`. The handler receives the request with the
path rewritten to start at `/mcp`. Calling `addMcp` twice is a type error.

With `auth: "reintersect"`, the SDK verifies the bearer token before calling your handler and passes
the caller as `extra.authInfo`:

```ts theme={null}
interface ReintersectAuthInfo {
  token: string;
  clientId: string;
  scopes: string[];
  expiresAt: number;
  resource: URL;
  extra: {
    reintersect: {
      memberId: string;
      workspaceId: string;
      installId: string;
      appId: string;
      tokenId: string;
      agentConversationId?: string;
    };
  };
}
```

`agentConversationId` is set when the agent makes the call. With `"oauth"` and `"none"`, your handler
receives the request untouched and does its own checks.

On the [Effect entry](/effect), `ReintersectApp.layer(app)` mounts the MCP handler at `/mcp` on your
`HttpLayerRouter` the same way. See [Services and layers](/effect/services-and-layers) in the Effect
guide.

## Verify tokens in a standalone server

When your MCP server runs outside the app, for example in an existing Next.js API, verify
Reintersect's tokens with `verifyReintersectToken` from `@reintersect/sdk/mcp`:

```ts app/mcp/route.ts theme={null}
import { verifyReintersectToken } from "@reintersect/sdk/mcp";
import { mcpServer } from "@/mcp";

const verify = verifyReintersectToken({ audience: "https://launchpad.example.com/mcp" });

export async function POST(request: Request) {
  const authInfo = await verify(request);

  if (authInfo === undefined) {
    return new Response(JSON.stringify({ error: "invalid_token" }), {
      status: 401,
      headers: { "www-authenticate": 'Bearer error="invalid_token"' },
    });
  }

  return mcpServer(request, { authInfo });
}
```

| Option | Description |
| - | - |
| `audience` | Required. Your MCP server's exact absolute URL. Tokens for any other URL are rejected. |
| `appId` | Optional. Only accept tokens issued for this app (`rei_app`); others are rejected with the reason `AppMismatch`. |
| `issuer` | Defaults to `REINTERSECT_BASE_URL` or `https://api.reintersect.com`. |
| `fetch` | A custom `fetch` for loading the signing keys. |

The returned function takes the request and, optionally, a bearer token you already extracted. It
resolves to the `ReintersectAuthInfo` above, or `undefined` when the token is missing or invalid; the
reason (such as `Expired`, `AudienceMismatch` or `AppMismatch`) is logged, never thrown. Keys come from `https://api.reintersect.com/.well-known/reintersect/jwks.json`
and are cached, with a refetch when a token names a key the cache doesn't have.

`ReintersectTokenInvalidError` and the types `McpHandler`, `McpFetchHandler`, `McpRequestExtra`,
`ReintersectAuthInfo`, `ReintersectCaller` and `VerifyReintersectTokenOptions` are exported from the
same entry point.


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