# Introduction (/docs) ## Getting started xmcp is the easiest and fastest way to build an MCP server. It automatically handles registering tools, prompts and resources. There's no extra setup needed, and it provides a complete toolkit for building production ready MCP servers. If you're using these docs in an LLM, access [llms-full.txt](./llms-full.txt) for the complete documentation context to get started quickly. You can get started by [bootstrapping a new application](/docs/getting-started/installation) from scratch, or you can plug into your existing [Next.js](/docs/adapters/nextjs) or [Express](/docs/adapters/express) app. # Express (/docs/adapters/express) ## Installation `xmcp` can work on top of your existing Express project. To get started, run the following command in your project directory: ```bash npx init-xmcp@latest ``` After setting up the project, your build and dev command should look like this: ```json { "scripts": { "dev": "xmcp dev & existing-build-command", "build": "xmcp build && existing-build-command" } } ``` When running `dev` or `build` command, `xmcp` will bundle your tools into `.xmcp/adapter`. You should add the `/mcp` endpoint in your existing server. ```typescript import { xmcpHandler } from "path/to/.xmcp/adapter"; app.get("/mcp", xmcpHandler); app.post("/mcp", xmcpHandler); ``` `middleware.ts` is not supported in this mode. # NestJS (/docs/adapters/nestjs) ## Overview The NestJS adapter allows you to integrate xmcp into your existing NestJS application. It provides: * **Automatic tool discovery** from your `src/tools/` directory * **Scaffolded module** with customizable controller, filter, and route configuration * **NestJS integration** with `xmcpService` and `xmcpController` ## Installation `xmcp` can work on top of your existing NestJS project. To get started, run the following command in your project directory: ```bash npx init-xmcp@latest ``` On initialization, you'll see the following prompts: { "? Tools directory path: (tools)" } The package manager and framework will be detected automatically. After initialization, xmcp generates a `src/xmcp/` folder with customizable module files: ``` src/xmcp/ ├── xmcp.filter.ts # Exception filter for JSON-RPC errors ├── xmcp.controller.ts # Controller with configurable route └── xmcp.module.ts # NestJS module configuration ``` After setting up the project, update your `package.json` scripts: ```json title="package.json" { "scripts": { "dev": "xmcp dev & nest start --watch", "build": "xmcp build && nest build", "start": "node dist/main.js" } } ``` Before using the `@xmcp/adapter` import, you need to: 1. Run `npx xmcp build` to generate the `.xmcp` folder 2. Update your `tsconfig.json` to include the path mapping and the `.xmcp` folder: ```json title="tsconfig.json" { "compilerOptions": { "paths": { "@xmcp/*": ["./.xmcp/*"] } }, "include": ["src/**/*", "xmcp-env.d.ts", ".xmcp/**/*"] } ``` After these steps, TypeScript errors will be resolved. ## Project Structure After initialization, your project structure will look like this: ``` my-nestjs-app/ ├── src/ │ ├── tools/ # Tool files are auto-discovered here │ │ └── greet.ts │ ├── xmcp/ # Generated xmcp module (customizable) │ │ ├── xmcp.filter.ts │ │ ├── xmcp.controller.ts │ │ └── xmcp.module.ts │ ├── app.module.ts # Import XmcpModule here │ └── main.ts ├── .xmcp/ # Generated by xmcp build (gitignored) ├── xmcp.config.ts # xmcp configuration ├── xmcp-env.d.ts # Type declarations ├── package.json └── tsconfig.json ``` ### Generated Files **`src/xmcp/`** - Contains the scaffolded module with controller, filter, and module configuration. These files are yours to customize - change the route path, add middleware, modify error handling, or extend functionality as needed. **`.xmcp/`** - Contains the compiled adapter and auto-generated TypeScript definitions. This directory is created by `xmcp build` and should be added to `.gitignore`. It includes `xmcpService`, `xmcpController`, and all type definitions needed to integrate with NestJS. **`xmcp-env.d.ts`** - Provides TypeScript type declarations for xmcp imports like `@xmcp/adapter`. This file is auto-generated and should not be edited manually. It ensures TypeScript can resolve the path alias configured in `tsconfig.json`. ## Basic Usage Import and add the `XmcpModule` to your application module: ```typescript title="src/app.module.ts" import { Module } from "@nestjs/common"; import { XmcpModule } from "./xmcp/xmcp.module"; @Module({ imports: [XmcpModule], }) export class AppModule {} ``` This registers a `/mcp` endpoint that handles MCP requests via POST. ## Generated Module Files ### Exception Filter The exception filter provides JSON-RPC error handling for MCP endpoints: ```typescript title="src/xmcp/xmcp.filter.ts" import { ExceptionFilter, Catch, ArgumentsHost, Logger } from "@nestjs/common"; import { Response } from "express"; @Catch() export class McpExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(McpExceptionFilter.name); catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); this.logger.error( "MCP request failed", exception instanceof Error ? exception.stack : String(exception) ); if (!response.headersSent) { response.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error", }, id: null, }); } } } ``` This filter is scaffolded into your project, so you can customize it to handle specific error types or modify the error response format. ### Controller The controller extends `xmcpController` and uses NestJS decorators: ```typescript title="src/xmcp/xmcp.controller.ts" import { Controller, UseFilters } from "@nestjs/common"; import { xmcpController } from "@xmcp/adapter"; import { McpExceptionFilter } from "./xmcp.filter"; @Controller("mcp") @UseFilters(McpExceptionFilter) export class McpController extends xmcpController {} ``` To change the route, simply modify the `@Controller` argument: ```typescript @Controller("api/v1/mcp") // Now accessible at /api/v1/mcp export class McpController extends xmcpController {} ``` ### Module The module configures the controller and providers: ```typescript title="src/xmcp/xmcp.module.ts" import { Module } from "@nestjs/common"; import { xmcpService } from "@xmcp/adapter"; import { McpController } from "./xmcp.controller"; import { McpExceptionFilter } from "./xmcp.filter"; @Module({ controllers: [McpController], providers: [xmcpService, McpExceptionFilter], exports: [xmcpService], }) export class XmcpModule {} ``` ## Configuration Configure the NestJS adapter in your `xmcp.config.ts`: ```typescript title="xmcp.config.ts" import { type XmcpConfig } from "xmcp"; const config: XmcpConfig = { http: true, experimental: { adapter: "nestjs", }, }; export default config; ``` The NestJS adapter uses HTTP transport and integrates with NestJS's module system, allowing you to use the `xmcpService` in your custom controllers. ## NestJS Integration Features The adapter provides full NestJS integration with proper lifecycle management and error handling: ### Lifecycle Hooks `xmcpService` implements `OnModuleInit` and `OnModuleDestroy` for proper initialization and shutdown logging: * **Startup**: Logs `[xmcpService] XMCP service initialized` when the module initializes * **Shutdown**: Logs `[xmcpService] XMCP service shutting down` when the application stops ### Structured Logging All xmcp internal logs use the NestJS `Logger` class, automatically inheriting your application's logging configuration. ## Adding Tools Tools are automatically discovered from your `src/tools/` directory. Create a new file and export a default handler function: ```typescript title="src/tools/greet.ts" import { z } from "zod"; import { type InferSchema } from "xmcp"; export const schema = { name: z.string().describe("The name of the user to greet"), }; export const metadata = { name: "greet", description: "Greet the user by name", }; export default async function greet({ name }: InferSchema) { return `Hello, ${name}!`; } ``` When you run `xmcp dev` or `xmcp build`, xmcp automatically discovers this file and registers it as an MCP tool. No additional configuration needed. For more details on creating tools, schemas, and metadata, see the [Tools documentation](/docs/core-concepts/tools). ## Authentication The NestJS adapter provides a `createMcpAuthGuard` factory function for JWT authentication. You provide the verification logic, and the adapter handles token extraction, error responses, and attaching auth info to requests. ### Setup First, install the JWT library: ```bash npm install jsonwebtoken npm install -D @types/jsonwebtoken ``` Create an auth guard configuration file: ```typescript title="src/xmcp/xmcp.auth.ts" import { createMcpAuthGuard } from "@xmcp/adapter"; import * as jwt from "jsonwebtoken"; export const McpAuthGuard = createMcpAuthGuard({ verifyToken: async (token) => { const decoded = jwt.verify( token, process.env.JWT_SECRET! ) as jwt.JwtPayload; return { clientId: decoded.sub || "unknown", scopes: decoded.scope?.split(" ") || [], expiresAt: decoded.exp, }; }, required: false, // Set to true to require authentication }); ``` ### Enable Authentication To enable authentication, add the guard to your controller: ```typescript title="src/xmcp/xmcp.controller.ts" import { Controller, UseFilters, UseGuards } from "@nestjs/common"; import { xmcpController } from "@xmcp/adapter"; import { McpExceptionFilter } from "./xmcp.filter"; import { McpAuthGuard } from "./xmcp.auth"; @Controller("mcp") @UseFilters(McpExceptionFilter) @UseGuards(McpAuthGuard) export class McpController extends xmcpController {} ``` And add it to your module providers: ```typescript title="src/xmcp/xmcp.module.ts" import { Module } from "@nestjs/common"; import { xmcpService } from "@xmcp/adapter"; import { McpController } from "./xmcp.controller"; import { McpExceptionFilter } from "./xmcp.filter"; import { McpAuthGuard } from "./xmcp.auth"; @Module({ controllers: [McpController], providers: [xmcpService, McpExceptionFilter, McpAuthGuard], exports: [xmcpService], }) export class XmcpModule {} ``` ### Configuration Options | Option | Type | Default | Description | | ------------- | -------------------------------------------------- | -------- | --------------------------------------------- | | `verifyToken` | `(token: string) => Promise \| AuthInfo` | Required | Verify the token and return auth info | | `required` | `boolean` | `false` | If true, requests without tokens are rejected | The `verifyToken` function receives the Bearer token (without the "Bearer " prefix) and should return: ```typescript interface AuthInfo { clientId: string; // User/client identifier scopes: string[]; // Permissions/scopes expiresAt?: number; // Token expiration (Unix timestamp) extra?: Record; // Additional custom data } ``` If verification fails, throw an error with a descriptive message. ### Accessing Auth Info in Tools Auth info is available in tools via the `extra` argument: ```typescript title="src/tools/whoami.ts" import { type ToolMetadata, type ToolExtraArguments } from "xmcp"; export const schema = {}; export const metadata: ToolMetadata = { name: "whoami", description: "Returns information about the authenticated user", }; export default async function whoami( _args: unknown, extra: ToolExtraArguments ) { const authInfo = extra.authInfo; if (!authInfo) { return { content: [{ type: "text", text: "Not authenticated" }], }; } return { content: [ { type: "text", text: JSON.stringify({ clientId: authInfo.clientId, scopes: authInfo.scopes, }, null, 2), }, ], }; } ``` ### Testing with curl ```bash # Without authentication (if required: false) curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' # With authentication curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' ``` ## Troubleshooting ### Cannot find module '@xmcp/adapter' This error occurs when the `.xmcp` directory hasn't been generated yet. **Solution:** Run `npx xmcp build` before starting your NestJS application. ### TypeScript path resolution errors If TypeScript can't resolve `@xmcp/*` imports: 1. Ensure `tsconfig.json` has the path mapping: ```json { "compilerOptions": { "paths": { "@xmcp/*": ["./.xmcp/*"] } } } ``` 2. Ensure `.xmcp` is included in the `include` array: ```json { "include": ["src/**/*", "xmcp-env.d.ts", ".xmcp/**/*"] } ``` ## Next Steps # Next.js (/docs/adapters/nextjs) ## Installation `xmcp` can work on top of your existing Next.js project. To get started, run the following command in your project directory: ```bash npx init-xmcp@latest ``` On initialization, you'll see the following prompts: { "? Tools directory path: (tools)\n? Prompts directory path: (prompts)\n? Resources directory path: (resources)\n? Route directory path: (app/mcp)" } The package manager and framework will be detected automatically. After setting up the project, your build and dev commands should look like this: ```json { "scripts": { "dev": "xmcp dev & next dev", "build": "xmcp build && next build" } } ``` Before using the `@xmcp/adapter` import, you need to: 1. Run `npx xmcp build` to generate the `.xmcp` folder 2. Update your `tsconfig.json` to include the path mapping: ```json title="tsconfig.json" { "compilerOptions": { "paths": { "@xmcp/*": ["./.xmcp/*"] } } } ``` After these steps, TypeScript errors will be resolved. Based on your configuration, it will create the tools, prompts, resources and route folders and add an endpoint to your Next.js app. ```typescript title="app/mcp/route.ts" import { xmcpHandler } from "@xmcp/adapter"; export { xmcpHandler as GET, xmcpHandler as POST }; ``` `middleware.ts` and `xmcp/headers` are not supported since Next.js already supports those features. ## Authentication You can use the `withAuth` function to add authentication to your MCP server. ```typescript title="app/mcp/route.ts" import { xmcpHandler, withAuth, VerifyToken } from "@xmcp/adapter"; /** * Verify the bearer token and return auth information * In a real implementation, this would validate against your auth service */ const verifyToken: VerifyToken = async (req: Request, bearerToken?: string) => { if (!bearerToken) return undefined; // TODO: Replace with actual token verification logic // This is just an example implementation const isValid = bearerToken.startsWith("__TEST_VALUE__"); if (!isValid) return undefined; return { token: bearerToken, scopes: ["read:messages", "write:messages"], clientId: "example-client", extra: { userId: "user-123", // Add any additional user/client information here permissions: ["user"], timestamp: new Date().toISOString(), }, }; }; const options = { verifyToken, required: true, requiredScopes: ["read:messages"], resourceMetadataPath: "/.well-known/oauth-protected-resource", }; const handler = withAuth(xmcpHandler, options); export { handler as GET, handler as POST }; ``` # API Key (/docs/authentication/api-key) To enable API key authentication, you can use the `apiKeyAuthMiddleware` middleware on your app. ```typescript title="src/middleware.ts" import { apiKeyAuthMiddleware, type Middleware } from "xmcp"; const middleware: Middleware = [ apiKeyAuthMiddleware({ headerName: "x-api-key", apiKey: "12345", }), // ... other middlewares ]; export default middleware; ``` If no `headerName` is provided, the middleware will default to `x-api-key`. This middleware can also be used with a validation function. It should **return a boolean** value indicating if the API key is valid. ```typescript title="src/middleware.ts" import { apiKeyAuthMiddleware, type Middleware } from "xmcp"; const middleware: Middleware = apiKeyAuthMiddleware({ headerName: "x-api-key", validateApiKey: async (apiKey) => { return apiKey === "12345"; }, }); export default middleware; ``` Next time you connect to your MCP server, you'll need to provide the API key in the `x-api-key` header (or the name you specified in the middleware). Your connection object will look like this: ```json { "mcpServers": { "my-project": { "url": "http://localhost:3001/mcp", "headers": { "x-api-key": "12345" // <- This is the API key you provided in the middleware } } } } ``` Make sure to check the [connecting](/docs/getting-started/connecting) documentation for more details on the different clients. # JSON Web Token (/docs/authentication/jwt) To enable JWT authentication, you can use the `jwtAuthMiddleware` middleware on your app. ```typescript title="src/middleware.ts" import { jwtAuthMiddleware, type Middleware } from "xmcp"; const middleware: Middleware = [ jwtAuthMiddleware({ secret: process.env.JWT_SECRET!, algorithms: ["HS256"], }), // ... other middlewares ]; export default middleware; ``` You can customize the middleware using the configuration object containing the JWT secret and verify options. ```typescript const middleware = jwtAuthMiddleware({ secret: process.env.JWT_SECRET!, algorithms: ["HS256"], issuer: "https://example.com", audience: "https://example.com", subject: "user-id", expiresIn: "1h", notBefore: "1h", clockTolerance: 30, }); ``` Check out the [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) library for more details on the configuration options. # OAuth (/docs/authentication/oauth) The experimental `oauth` configuration has been deprecated in favor of production-ready plugin implementations. The built-in OAuth system has been replaced with dedicated authentication plugins that provide better DX, improved security, and more features out of the box. Use one of the official plugins below that handle OAuth flows, token management, and session handling for you: Each plugin integrates directly with its respective auth provider and requires minimal configuration. Choose the one that matches your existing infrastructure or start fresh with any of them. # Bundler (/docs/configuration/bundler) `xmcp` uses rspack to bundle your tools. You can customize the configuration by adding the following to your `xmcp.config.ts` file: ```typescript title="xmcp.config.ts" const config: XmcpConfig = { bundler: (config) => { // Add raw loader for images to get them as base64 config.module?.rules?.push({ test: /\.(png|jpe?g|gif|svg|webp)$/i, type: "asset/inline", }); return config; }, }; ``` Rspack provides configurations similar to webpack. You can find more details in the [rspack documentation](https://rspack.rs/config/). # Custom Directories (/docs/configuration/custom-directories) Customize where `xmcp` looks for tools, prompts, and resources by configuring the `paths` option. If not specified, these are the defaults: ```typescript title="xmcp.config.ts" const config: XmcpConfig = { paths: { tools: "src/tools", prompts: "src/prompts", resources: "src/resources", }, }; export default config; ``` ## Disabling directories To disable a specific directory, set it to `false`: ```typescript title="xmcp.config.ts" const config: XmcpConfig = { paths: { tools: "src/tools", prompts: false, // Prompts directory disabled resources: "src/resources", }, }; export default config; ``` ## Troubleshooting If you delete a directory without updating the config, `xmcp` will throw an error and prompt you to update it. # Telemetry (/docs/configuration/telemetry) ## What we collect (in brief) xmcp tracks anonymous build stats—command, versions, OS, adapter/transport picks, component counts, and coarse success/error signals. No code, prompts, logs, or secrets leave your machine, and every payload stays tied to a random anonymous ID rather than repository data. For the long-form policy, visit [/telemetry](/telemetry). ## Disable telemetry per run Use the environment flag in front of any command, including CI tasks: ```bash XMCP_TELEMETRY_DISABLED=true npx xmcp dev XMCP_TELEMETRY_DISABLED=true npx xmcp build ``` Only the literal string `true` (case-insensitive) disables telemetry, so values like `false` or `0` keep it enabled. ## Opt out globally * **Shell/CI env:** Export `XMCP_TELEMETRY_DISABLED=true` in your shell profile, `.env`, or CI secrets to stop telemetry everywhere. * **Config file:** Delete the generated `telemetry.json` (location printed in debug logs) after setting the env flag if you want to purge the existing anonymous ID. Removing the file while the env variable is set keeps telemetry off and regenerates a fresh opt-in prompt whenever you re-enable it. When disabled, xmcp skips generating anonymous IDs, avoids writing telemetry event files, and no build metadata leaves the machine. ## Inspecting payloads If you want to audit what would be sent without disabling telemetry, set `XMCP_DEBUG_TELEMETRY=true`. This mirrors each payload to `stderr` with the `[telemetry]` prefix while still sending events. # Transports (/docs/configuration/transports) ## HTTP transport The `http` configuration customizes the HTTP server. Set it to `true` to use defaults, or provide an object to override specific options: ```typescript title="xmcp.config.ts" const config: XmcpConfig = { http: { port: 3001, host: "127.0.0.1", endpoint: "/mcp", bodySizeLimit: 10485760, // 10MB debug: false, // adds extra logging to the console }, }; export default config; ``` These are the default values. Override only what you need to customize. ### CORS CORS (Cross-Origin Resource Sharing) middleware that can be configured to control cross-origin requests. ```typescript title="xmcp.config.ts" const config: XmcpConfig = { http: { cors: { origin: "*", methods: ["GET", "POST"], allowedHeaders: [ "Content-Type", "Authorization", "mcp-session-id", "mcp-protocol-version", ], exposedHeaders: ["Content-Type", "Authorization", "mcp-session-id"], credentials: false, maxAge: 86400, }, }, }; export default config; ``` ## STDIO transport The `stdio` configuration customizes the STDIO transport. Set it to `true` to use defaults, or provide an object to override specific options: By default you enable STDIO transport by setting it to `true`. ```typescript title="xmcp.config.ts" const config: XmcpConfig = { stdio: true, }; ``` You can also customize the debug mode and this would enable it as well. ```typescript title="xmcp.config.ts" const config: XmcpConfig = { stdio: { debug: false, // adds extra logging to the console }, }; ``` ## Troubleshooting Keep in mind that clients like Claude Desktop are not compatible with STDIO logging and would cause a JSON parsing error. # CSS (/docs/core-concepts/css) If you're using [ChatGPT widgets](/docs/core-concepts/tools#openai-metadata) or [MCP Apps](/docs/core-concepts/tools#mcp-apps-metadata), xmcp provides your application multiple ways to use CSS: * [Tailwind CSS](#tailwind-css) * [CSS](#css) * [CSS Modules](#css-modules) xmcp automatically detects and imports a `globals.css` file if it exists in `globals.css`, `src/globals.css`, or `src/tools/globals.css` (in priority order). You don't need to manually import it in every tool. ## Tailwind CSS A CSS framework that provides utility classes like `flex`, `pt-4`, `text-center`, and `rotate-90`. You use these classes directly in your component to build layouts and designs. Install Tailwind CSS: Add the PostCSS plugin to your `postcss.config.mjs` file: ```js export default { plugins: { '@tailwindcss/postcss': {}, }, } ``` Create a `globals.css` file in your project root (or `src/globals.css`) and import Tailwind: ```css @import 'tailwindcss'; ``` Now you can use Tailwind classes in your tools: ```tsx import type { ToolMetadata } from "xmcp"; export const metadata: ToolMetadata = { name: "greet", description: "Hello, world!", }; export default function handler() { return (

Hello, world!

); } ``` ## CSS Write standard CSS to style your components without any framework or tooling. Create a `globals.css` file: ```css .title { font-size: 2rem; font-weight: bold; } ``` Use the styles in your tool: ```tsx import type { ToolMetadata } from "xmcp"; export const metadata: ToolMetadata = { name: "greet", description: "Hello, world!", }; export default function handler() { return (

Hello, world!

); } ``` ## CSS Modules Scoped styles that are tied to a specific tool file. Create a CSS module file with the `.module.css` extension: ```css .container { padding: 2rem; } .title { font-size: 2rem; font-weight: bold; } ``` Import the styles object and use it in your tool: ```tsx import type { ToolMetadata } from "xmcp"; import styles from "./greet.module.css"; export const metadata: ToolMetadata = { name: "greet", description: "Hello, world!", }; export default function handler() { return (

Hello, world!

); } ``` ## Summary Pick the approach that fits your project. You can also mix them, for example, use Tailwind for layout and CSS Modules for component-specific styles. # External Clients (/docs/core-concepts/external-clients) xmcp lets you connect to external MCP servers and generate fully typed clients. The CLI generates TypeScript clients with autocomplete for all tools exposed by HTTP or STDIO-based MCP servers. For production deployments, use the HTTP transport. STDIO is limited to local development because it cannot be deployed in production environments. ## Creating the Clients File Create a `src/clients.ts` file and export a `ClientConnections` object. The object keys become the client names: ```typescript title="src/clients.ts" import { ClientConnections } from "xmcp"; export const clients: ClientConnections = { context: { url: "https://mcp.context7.com/mcp", headers: [{ name: "CONTEXT7_API_KEY", env: "CONTEXT7_API_KEY" }], }, playwright: { npm: "@playwright/mcp", }, }; ``` ## HTTP Clients (recommended) HTTP clients connect to remote MCP servers over HTTP. This is the recommended transport for production deployments. ```typescript type HttpClientConfig = { name?: string; // Optional, defaults to object key type?: "http"; // Optional, inferred from url url: string; // MCP server URL (required) headers?: CustomHeaders; // Optional headers array }; type CustomHeaders = CustomHeader[]; type CustomHeader = StaticHeader | EnvHeader; // Static value (non-sensitive) interface StaticHeader { name: string; value: string; } // Environment variable (sensitive values like API keys) interface EnvHeader { name: string; env: string; // Environment variable name to read at runtime } ``` Example: ```typescript { context: { url: "https://mcp.context7.com/mcp", headers: [ { name: "CONTEXT7_API_KEY", env: "CONTEXT7_API_KEY" }, ], }, } ``` ## STDIO Clients (local servers) STDIO clients spawn local processes that communicate via standard input/output. ```typescript type StdioClientConfig = { name?: string; // Optional, defaults to object key type?: "stdio"; // Optional, inferred from npm/command command?: string; // Command to run (e.g., "npx", "bunx", "node") args?: string[]; // Command arguments npm?: string; // npm package to run via npx npmArgs?: string[]; // Arguments to pass to npm package env?: Record; // Environment variables cwd?: string; // Working directory stderr?: "pipe" | "inherit" | "ignore"; // Stderr handling }; ``` Examples: ```typescript // Simple npm package { npm: "@playwright/mcp" } // With arguments { npm: "@playwright/mcp", npmArgs: ["--browser", "chromium"] } // Custom command { command: "bunx", args: ["-y", "@upstash/context7-mcp"] } // With environment variables { npm: "@some/mcp-server", env: { DEBUG: "true", LOG_LEVEL: "verbose" } } ``` The npm package will be installed automatically if it is not already installed in your project. ## Running the Generator Run the generator from your project root: ```bash npx @xmcp-dev/cli generate ``` This reads from `src/clients.ts` and writes generated clients to `src/generated/`. **Options:** * `-o, --out ` - Output directory (default: `src/generated`) * `-c, --clients ` - Clients file path (default: `src/clients.ts`) ## Generated Output For each client defined in `clients.ts`, the CLI generates a `client.{name}.ts` file containing: * Zod schemas for each tool's arguments * Type exports (e.g., `GreetArgs`) * Tool metadata objects * `createRemoteToolClient()` factory function * Pre-instantiated client export An index file (`client.index.ts`) is always generated with a unified `generatedClients` object for accessing all clients. ## Using Generated Clients Import `generatedClients` from the generated index file and call tools directly: ```typescript title="src/tools/get-library-docs.ts" import { InferSchema, type ToolMetadata } from "xmcp"; import { generatedClients } from "../generated/client.index"; import { z } from "zod"; export const schema = { libraryName: z.string().describe("The name of the library"), }; export const metadata: ToolMetadata = { name: "get-library-docs", description: "Get documentation for a library", }; export default async function handler({ libraryName, }: InferSchema) { const docs = await generatedClients.context.getLibraryDocs({ context7CompatibleLibraryID: libraryName, }); return (docs.content as any)[0].text; } ``` The generated clients provide full autocomplete for all available tools and their arguments. ## Example: Browser Navigation ```typescript title="src/tools/browser-navigate.ts" import { InferSchema, type ToolMetadata } from "xmcp"; import { generatedClients } from "../generated/client.index"; import { z } from "zod"; export const schema = { url: z.string().describe("The URL to navigate to"), }; export const metadata: ToolMetadata = { name: "browser-navigate", description: "Navigate to a URL", }; export default async function handler({ url }: InferSchema) { await generatedClients.playwright.browserNavigate({ url }); return `Navigated to: ${url}`; } ``` ## Caveats * **Server must be available** — The CLI connects over HTTP or spawns the STDIO package to fetch tool definitions. Ensure the remote server is reachable or the npm package is installed. * **Prefer env for secrets** — API keys can be provided as CLI args or via the `env` map. Prefer `env` for sensitive values. # Middlewares (/docs/core-concepts/middlewares) Middlewares intercept HTTP requests and responses, enabling authentication, rate limiting, and other processing tasks. Create a `src/middleware.ts` file to define your middleware: ```typescript title="src/middleware.ts" import { type Middleware } from "xmcp"; const middleware: Middleware = async (req, res, next) => { const authHeader = req.headers.authorization; if (!customHeaderValidation(authHeader)) { res.status(401).json({ error: "Invalid API key" }); return; } return next(); }; export default middleware; ``` xmcp provides built-in middlewares for common tasks like [API key authentication](/docs/authentication/api-key) and [JSON web token authentication](/docs/authentication/jwt). ## Chaining middlewares Define multiple middlewares as an array to chain them in sequence: ```typescript title="src/middleware.ts" import { type Middleware } from "xmcp"; const middleware: Middleware = [ async (req, res, next) => { // First middleware return next(); }, async (req, res, next) => { // Second middleware return next(); }, ]; export default middleware; ``` ## Accessing headers Use the `xmcp/headers` module to read request headers in your tools, prompts, or resources—useful for API keys, authentication tokens, and other custom headers. ```typescript title="src/tools/search.ts" import { headers } from "xmcp/headers"; export default async function search({ query }: InferSchema) { const requestHeaders = headers(); const apiKey = requestHeaders["x-api-key"]; const data = await fetchSomeData(apiKey); return JSON.stringify(data); } ``` # Prompts (/docs/core-concepts/prompts) `xmcp` detects files under the `/src/prompts/` directory and registers them as prompts. This path can be configured in the `xmcp.config.ts` file. The prompt file should export three elements: * **Schema**: The input parameters using Zod schemas. * **Metadata**: The prompt's identity and behavior hints. * **Default**: The prompt handler function. ```typescript title="src/prompts/review-code.ts" import { z } from "zod"; import { type InferSchema, type PromptMetadata } from "xmcp"; // Define the schema for prompt parameters export const schema = { code: z.string().describe("The code to review"), }; // Define prompt metadata export const metadata: PromptMetadata = { name: "review-code", title: "Review Code", description: "Review code for best practices and potential issues", role: "user", }; // Prompt implementation export default function reviewCode({ code }: InferSchema) { return { type: "text", text: `Please review this code for: - Code quality and best practices - Potential bugs or security issues - Performance optimizations - Readability and maintainability Code to review: \`\`\` ${code} \`\`\``, }; } ``` If you're returning a string or number only, you can shortcut the return value to be the string or number directly. ```typescript export default function reviewCode({ code }: InferSchema) { return `Please review this code for: - Code quality and best practices - Potential bugs or security issues - Performance optimizations - Readability and maintainability Code to review: \`\`\` ${code} \`\`\``; `; } ``` We encourage to use this shortcut for readability, and restrict the usage of the content object type only for complex responses, like images, audio or videos. ## References ### Schema (optional) The schema object defines the prompt's parameters with: * **Key**: Parameter name. * **Value**: Zod schema with `.describe()` for documentation and prompt inspection. This will be visible through the inspector. * **Purpose**: Type validation and automatic parameter documentation. This is the exact same as the schema object for tools. ### Metadata (optional) The metadata object provides: * **Name**: Unique identifier for the prompt * **Title**: Human-readable title for the prompt * **Description**: Brief explanation of what the prompt does * **Role**: The role of the prompt in the conversation. Can be either `user` or `assistant`. ### Implementation (required) The default export function that performs the actual work. * **Parameters**: Automatically typed from your schema using the built-in `InferSchema`. * **Returns**: MCP-compatible response with content type. * **Async**: Supports async operations for API calls, file I/O, etc. # Resources (/docs/core-concepts/resources) `xmcp` automatically detects and registers files under the `/src/resources/` directory as resources. This path can be configured in your `xmcp.config.ts` file. Each resource file should export three elements: * **Schema**: Input parameters defined using Zod schemas * **Metadata**: The resource's identity and behavior configuration * **Default**: The resource handler function There are two types of resources: **static** and **dynamic**. When creating resources, it's important to understand how the folder structure determines the resource URI. ## URI composition rules Each resource is uniquely identified by a URI composed from its file path using these rules: * The URI scheme is detected from folders with parentheses. For example, a parent folder named `(users)` creates the URI scheme `users`. * Static folders become literal path segments. * Brackets `[]` indicate dynamic parameters. For example, the following file path: ``` src/resources/(users)/[userId]/profile.ts ``` Will result in the URI `users://{userId}/profile`. ## 1. Static resource Static resources are files that don't require any parameters. Following the composition rules above, the resource below will have the URI `config://app`: ```typescript title="src/resources/(config)/app.ts" import { type ResourceMetadata } from "xmcp"; export const metadata: ResourceMetadata = { name: "app-config", title: "Application Config", description: "Application configuration data", }; export default function handler() { return "App configuration here"; } ``` ## 2. Dynamic resource Dynamic resources accept parameters. The example below creates a resource with the URI `users://{userId}/profile`: ```typescript title="src/resources/(users)/[userId]/profile.ts" import { z } from "zod"; import { type ResourceMetadata, type InferSchema } from "xmcp"; export const schema = { userId: z.string().describe("The ID of the user"), }; export const metadata: ResourceMetadata = { name: "user-profile", title: "User Profile", description: "User profile information", }; export default function handler({ userId }: InferSchema) { return `Profile data for user ${userId}`; } ``` ## References ### Schema (optional) The schema object defines the resource's parameters with: * **Key**: Parameter name. * **Value**: Zod schema with `.describe()` for documentation and resource inspection. This will be visible through the inspector. * **Purpose**: Type validation and automatic parameter documentation. This is the exact same as the schema object for tools and prompts. ### Metadata (optional) The metadata object provides: * **Name**: Unique identifier for the resource * **Title**: Human-readable title for the resource * **Description**: Brief explanation of what the resource does * **MimeType**: The MIME type of the resource * **Size**: The size of the resource ### Implementation (required) The default export function that performs the actual work. * **Parameters**: Automatically typed from your schema using the built-in `InferSchema`. * **Returns**: MCP-compatible response with content type. # Tools (/docs/core-concepts/tools) By default, `xmcp` detects files under the `/src/tools/` directory and registers them as tools, but you can specify a [custom directory](/docs/configuration/custom-directories) if you prefer. The directory to use can be configured in the `xmcp.config.ts` file. A tool file consists of three main exports: * **Default**: The tool handler function. * **Schema** (optional): The input parameters using Zod schemas. * **Metadata** (optional): The tool's identity and behavior hints. If omitted, the name is inferred from the file name and the description defaults to a placeholder. ```typescript title="src/tools/greet.ts" import { z } from "zod"; import { type InferSchema } from "xmcp"; // Define the schema for tool parameters export const schema = { name: z.string().describe("The name of the user to greet"), }; // Define tool metadata export const metadata = { name: "greet", description: "Greet the user", annotations: { title: "Greet the user", readOnlyHint: true, destructiveHint: false, idempotentHint: true, }, }; // Tool implementation export default async function greet({ name }: InferSchema) { const result = `Hello, ${name}!`; return { content: [{ type: "text", text: result }], }; } ``` If you're returning a string or number only, you can shortcut the return value to be the string or number directly. ```typescript export default async function greet({ name }: InferSchema) { return `Hello, ${name}!`; } ``` We encourage to use this shortcut for readability, and restrict the usage of the content array type only for complex responses, like images, audio or videos. ## Schema Definition The schema defines your tool's input parameters using [Zod](https://zod.dev). Use `.describe()` on each parameter to help LLMs understand how to use your tool correctly. ```typescript title="src/tools/create-user.ts" import { z } from "zod"; import { type InferSchema } from "xmcp"; export const schema = { name: z.string().describe("User's full name"), email: z.string().email().describe("Valid email address"), age: z.number().min(18).optional().describe("User's age (18+)"), role: z.enum(["admin", "user"]).describe("User role"), }; export default async function createUser(args: InferSchema) { // args is automatically typed: { name: string; email: string; age?: number; role: "admin" | "user" } const { name, email, age, role } = args; // Implementation here } ``` ### Type Inference The `InferSchema` utility automatically infers TypeScript types from your Zod schema, giving you full type safety without manual type definitions: ```typescript import { type InferSchema } from "xmcp"; export const schema = { tags: z.array(z.string()).describe("List of tags"), metadata: z .object({ priority: z.number(), assignee: z.string().optional(), }) .describe("Task metadata"), }; // TypeScript infers: // { // tags: string[]; // metadata: { priority: number; assignee?: string }; // } export default async function handler(args: InferSchema) { // Full autocomplete and type checking args.tags.forEach((tag) => console.log(tag)); args.metadata.priority; // number args.metadata.assignee; // string | undefined } ``` Clear descriptions are crucial for LLM tool discovery. For comprehensive Zod validation options (regex patterns, constraints, transformations), see the [Zod documentation](https://zod.dev). ## Metadata The metadata export defines your tool's identity and provides behavioral hints to LLMs and clients. ```typescript title="src/tools/delete-user.ts" import { type ToolMetadata } from "xmcp"; export const metadata: ToolMetadata = { name: "delete-user", description: "Permanently delete a user account", annotations: { title: "Delete User Account", destructiveHint: true, idempotentHint: false, }, }; ``` ### Core Properties **`name`** (required) * Unique identifier for the tool * Defaults to the filename if not provided * Use kebab-case (e.g., `get-user-profile`) **`description`** (required) * Clear explanation of what the tool does * Defaults to placeholder if not provided * Critical for LLM tool discovery and selection ### Annotations Behavioral hints that help LLMs and UIs understand how to use your tool: ```typescript annotations: { // Human-readable title displayed in UIs title: "Create New Task", // Tool doesn't modify its environment (safe to retry) readOnlyHint: true, // Tool may perform destructive updates (use with caution) destructiveHint: false, // Repeated calls with same args have no additional effect idempotentHint: true, // Tool interacts with external entities (APIs, databases) openWorldHint: true, } ``` These hints are advisory only. LLMs may use them to make better decisions about when and how to call your tools, but they don't enforce any behavior. ### OpenAI Metadata For ChatGPT widget integration, add OpenAI-specific metadata under `_meta.openai`: ```typescript export const metadata: ToolMetadata = { name: "show-analytics", description: "Display analytics dashboard", _meta: { openai: { // Tool-specific widgetAccessible: true, toolInvocation: { invoking: "Loading analytics...", invoked: "Dashboard ready!", }, // Resource-specific (for auto-generated widgets) widgetDescription: "Real-time analytics dashboard", widgetPrefersBorder: true, widgetCSP: { connect_domains: ["https://api.analytics.com"], resource_domains: ["https://cdn.analytics.com"], }, }, }, }; ``` **Tool-specific properties:** * `widgetAccessible` - Enable widget-to-tool communication (required for widgets) * `toolInvocation.invoking` - Message shown while executing (≤64 chars) * `toolInvocation.invoked` - Message shown after completion (≤64 chars) * `outputTemplate` - Custom widget URI (auto-generated if not provided) **Resource-specific properties:** * `widgetDescription` - Human-readable widget summary * `widgetPrefersBorder` - UI rendering hint for borders * `widgetCSP` - Content Security Policy for external resources * `widgetDomain` - Optional dedicated subdomain * `widgetState` - Initial state object passed to widget ### MCP Apps metadata Unlike OpenAI widgets, MCP Apps do not require specific metadata configuration. Widgets work automatically without additional setup. ```typescript export const metadata: ToolMetadata = { name: "show-analytics", description: "Display analytics dashboard", _meta: { ui: { csp: { connectDomains: ["https://api.analytics.com"], resourceDomains: ["https://cdn.analytics.com"], }, domain: "https://analytics-widget.example.com", prefersBorder: true, }, }, }; ``` **Resource-specific properties:** * `csp.connectDomains` - Origins for fetch/XHR/WebSocket connections * `csp.resourceDomains` - Origins for images, scripts, stylesheets, fonts, media * `domain` - Optional dedicated subdomain for the widget's sandbox origin * `prefersBorder` - Request visible border + background (`true`/`false`/omitted) ## Handler Types Tools support three types of handlers, each suited for different use cases: | Type | Best For | Returns | | ---------------- | ------------------------------------- | ---------------------------------- | | Standard | Data queries, calculations, API calls | Unstructured or structured content | | Template Literal | Simple widgets with external scripts | HTML string | | React Component | Interactive, stateful widgets | React component | ### 1. Standard Handlers Standard handlers are functions that return text, structured content, or simple data. This is the default approach for most tools. **When to use:** * Performing calculations or data transformations * Calling external APIs and returning results * Querying databases * Any task that returns text or structured data without UI interaction ```typescript title="src/tools/calculate.ts" import { z } from "zod"; import { type InferSchema } from "xmcp"; export const schema = { operation: z.enum(["add", "subtract"]), a: z.number(), b: z.number(), }; export const metadata = { name: "calculate", description: "Perform basic calculations", }; export default async function calculate({ operation, a, b, }: InferSchema) { const result = operation === "add" ? a + b : a - b; return `Result: ${result}`; } ``` ### 2. Template Literal Handlers Return HTML directly to create interactive widgets in ChatGPT. When you return HTML and include OpenAI metadata, xmcp automatically generates a widget resource. To enable widgets, you need to add the `_meta.openai` configuration in your metadata with `widgetAccessible: true`. ```typescript title="src/tools/show-chart.ts" import { type ToolMetadata } from "xmcp"; export const metadata: ToolMetadata = { name: "show-chart", description: "Display an interactive chart", _meta: { openai: { widgetAccessible: true, toolInvocation: { invoking: "Loading chart...", invoked: "Chart loaded!", }, }, }, }; export default async function showChart() { return `

Sales Data

`; } ``` ### 3. React Component Handlers Return React components for interactive, composable widgets. xmcp renders the component to HTML and generates a widget resource automatically. To enable widgets, you need to add the `_meta.openai` configuration in your metadata with `widgetAccessible: true`. ```typescript title="src/tools/interactive-todo.tsx" import { type ToolMetadata } from "xmcp"; import { useState } from "react"; export const metadata: ToolMetadata = { name: "interactive-todo", description: "Interactive todo list widget", _meta: { openai: { widgetAccessible: true, toolInvocation: { invoking: "Loading todo list...", invoked: "Todo list ready!", }, }, }, }; export default function InteractiveTodo() { const [todos, setTodos] = useState([]); const [input, setInput] = useState(""); const addTodo = () => { if (input.trim()) { setTodos([...todos, input]); setInput(""); } }; return (

Todo List

setInput(e.target.value)} placeholder="Add a todo..." />
    {todos.map((todo, idx) => (
  • {todo}
  • ))}
); } ``` **Setup Requirements:** 1. Use `.tsx` file extension for React component tools 2. Install React dependencies: `npm install react react-dom` 3. Configure `tsconfig.json`: ```json { "compilerOptions": { "jsx": "react-jsx" } } ``` ## Return Values Tools support multiple return formats depending on your needs: ### Simple Values Return strings or numbers directly - xmcp automatically wraps them in the proper format: ```typescript export default async function calculate() { return "Result: 42"; // or return 42; } ``` ### Content Array Return an object with a `content` array for rich media responses: ```typescript export default async function getProfile() { return { content: [ { type: "text", text: "Profile information:", }, { type: "image", data: "base64encodeddata", mimeType: "image/jpeg", }, { type: "resource_link", name: "Full Profile", uri: "resource://profile/john", }, ], }; } ``` **Supported content types:** * `text` - Plain text content * `image` - Base64-encoded images with mimeType * `audio` - Base64-encoded audio with mimeType * `resource_link` - Links to MCP resources ### Structured Outputs Return structured data using the `structuredContent` property: ```typescript export default async function getUserData() { return { structuredContent: { user: { id: 123, name: "John Doe", email: "john@example.com", }, metadata: { timestamp: new Date().toISOString(), }, }, }; } ``` ### Combined Response Return both `content` and `structuredContent` for backwards compatibility. If the client cannot process structured outputs, it will fallback to `content`. ```typescript export default async function getData() { return { content: [ { type: "text", text: "Data retrieved successfully", }, ], structuredContent: { data: { key: "value" }, }, }; } ``` # Alpic (/docs/deployment/alpic) Get started by bootstrapping a [new project](https://app.alpic.ai/new/clone?repositoryUrl=https://github.com/alpic-ai/mcp-server-template-xmcp). This command will clone the xmcp template repository and setup zero-configuration deployment to Alpic. ## Deploy an existing project You can deploy your xmcp server to Alpic in 2 steps: 1. Create a new account on [app.alpic.ai](https://app.alpic.ai/). 2. Connect your Github account to Alpic, and select the repository you want to deploy. You can then access your servers's specific MCP analytics, logs, and evals in the [Alpic dashboard](https://app.alpic.ai/). Learn more about deploying xmcp to Alpic in the [Alpic documentation](https://docs.alpic.ai/). # Cloudflare (/docs/deployment/cloudflare) Cloudflare Workers support is built into xmcp with the `--cf` flag. The easiest path is to bootstrap a project that includes Wrangler and the Cloudflare build pipeline. ## Create a new project Start with `create-xmcp-app` and the Cloudflare flag (you can also pass `--cloudflare` when cloning an example with `--example`): ```bash npx create-xmcp-app@latest my-xmcp-app --cloudflare ``` This initializes a Workers-ready setup and wires the following defaults: * `xmcp build --cf` for production builds * `xmcp dev --cf` alongside `wrangler dev` for local development * `wrangler deploy` for deployment ## Build and deploy with the CLI Build a Cloudflare Workers bundle and emit `worker.js` (plus `wrangler.jsonc` if you don’t already have a Wrangler config): ```bash pnpm build # or xmcp build --cf ``` Then deploy with Wrangler: ```bash pnpm deploy # or npx wrangler deploy ``` ## Local development Run the watcher and Wrangler together: ```bash pnpm dev ``` This runs `xmcp dev --cf` (to rebuild the Worker output) and `wrangler dev` to serve it locally. Learn more about deploying Workers in the [Cloudflare Workers documentation](https://developers.cloudflare.com/workers/). # Replit (/docs/deployment/replit) Get started by remixing the [xmcp Replit template](https://replit.com/@matt/MCP-on-Replit-TS#README.md). # Smithery (/docs/deployment/smithery) You can deploy your MCP server and host it on [Smithery](https://smithery.ai). Learn more about publishing hosted MCP servers [here](https://smithery.ai/docs/build/publish#hosted). Once you got your xmcp server deployed, you can publish it to [Smithery](https://smithery.ai/docs/build/publish) for added capabilities like distribution and analytics. Smithery Gateway proxies to your upstream server. 1. Go to [smithery.ai/new](https://smithery.ai/new) 2. Enter your server’s public HTTPS URL 3. Complete the publishing flow Only HTTP servers can be published to Smithery. # Vercel (/docs/deployment/vercel) First, bootstrap a new project with `npx create-xmcp-app@latest`. Then, [connect your Git repository](https://vercel.com/new) or [use Vercel CLI](https://vercel.com/docs/cli): ```bash vc deploy ``` ## Get started with Vercel CLI You can initialize a new xmcp app with Vercel CLI with the following command: ```bash vc init xmcp ``` This will clone the [xmcp example repository](https://github.com/vercel/vercel/tree/main/examples/xmcp) in a directory called `xmcp`. Learn more about deploying xmcp to Vercel in the [Vercel documentation](https://vercel.com/docs/frameworks/backend/xmcp). # Connecting to your server (/docs/getting-started/connecting) At this point, you can configure to connect to your MCP server on clients like `Cursor` or `Claude Desktop`. Notice that, unless you start the development server, or have built your project for production, your server will not be shown available. ## HTTP transport By default, xmcp will use the port `3001`. If you're using a different port, you can change it in your `xmcp.config.ts` file. Read more about configuring transports [here](../configuration/transports). ### Cursor If you're using the HTTP transport with Cursor, your configuration should look like this: ```json { "mcpServers": { "my-project": { "url": "http://localhost:3001/mcp" } } } ``` ### Claude Desktop If you're using the HTTP transport with Claude Desktop, your configuration should look like this: ```json { "mcpServers": { "my-project": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:3001/mcp"] } } } ``` ## STDIO transport If you're using the STDIO transport, your configuration for local development should look like this: ```json { "mcpServers": { "my-project": { "command": "node", "args": ["/ABSOLUTE/PATH/TO/my-project/dist/stdio.js"] } } } ``` # Installation (/docs/getting-started/installation) ## System requirements Before you begin, make sure your system meets the following requirements: * Node.js 20 or later. * macOS, Windows or Linux. ## Automatic installation The quickest way to get started with xmcp is using `create-xmcp-app`. This CLI tool allows you to scaffold a template project with all the necessary files and dependencies to get you up and running quickly. To create an xmcp project, run: ```bash npx create-xmcp-app@latest ``` On installation, you'll see the following prompts: { "? What is your project named? (my-xmcp-app)\n? Select a template: (Use arrow keys)\n❯ Default (Standard MCP server)\n GPT App (ChatGPT/OpenAI widgets)\n MCP App (React widgets for ext-apps)" } You can also skip prompts using flags: `--gpt` (GPT App), `--ui` (MCP App), `--tailwind` or `--tw` (Tailwind CSS). Run `npx create-xmcp-app --help` for all options. { "? Select a package manager: (Use arrow keys)\n❯ npm \n yarn \n pnpm \n bun\n? Select the transport you want to use: (Use arrow keys)\n❯ HTTP (runs on a server) \n STDIO (runs on the user's machine)\n? Select components to initialize: (Press to select, to toggle all, to invert selection, and to proceed)\n❯◉ Tools\n ◉ Prompts\n ◉ Resources" } After the prompts, create-xmcp-app will create a folder with your project name and install the required dependencies. ## Manual installation To manually create an xmcp project, install the required dependencies: Then add the following scripts to your package.json: ```json title="package.json" { "scripts": { "dev": "xmcp dev", "build": "xmcp build", "start": "node dist/[transport].js" } } ``` These scripts refer to the different stages of developing an application: * `xmcp dev`: Starts the development server. This listens for changes and automatically reloads the server. * `xmcp build`: Builds the application for production. This will create a `dist` directory with the compiled code. * `node dist/[transport].js`: Starts the production server. This is the server that will be used in production. You can then run the scripts based on the package manager you've set up. Based on the transport you've chosen when bootstrapping your project, the \[transport] placeholder will be replaced with the appropriate one. This is correlated with the `xmcp.config.ts` configuration. ## Troubleshooting If you encounter issues when running the built server, make sure the transport is matching the configured one in `xmcp.config.ts`. If you're working with HTTP, your configuration should look like this: ```typescript title="xmcp.config.ts" const config: XmcpConfig = { http: true, }; ``` If you're working with STDIO, your configuration should look like this: ```typescript title="xmcp.config.ts" const config: XmcpConfig = { stdio: true, }; ``` You can have both transports configured, but you'll need to update the scripts to match them. For example, this is a valid script configuration you could use: ```json title="package.json" { "scripts": { "start:http": "node dist/http.js", "start:stdio": "node dist/stdio.js" } } ``` # Project structure (/docs/getting-started/project-structure) ## Overview A basic project structure is as follows: ``` my-project/ ├── src/ │ ├── middleware.ts # Middleware for http request/response processing │ └── tools/ # Tool files are auto-discovered here │ ├── greet.ts │ ├── search.ts │ └── prompts/ # Prompt files are auto-discovered here │ ├── review-code.ts │ ├── team-greeting.ts │ └── resources/ # Resource files are auto-discovered here │ ├── (config)/app.ts │ ├── (users)/[userId]/profile.ts ├── dist/ # Built output (generated) ├── package.json ├── tsconfig.json └── xmcp.config.ts # Configuration file for xmcp ``` ## Top-level files There are the three top-level files that are required for your project: * `package.json`: Contains your project's dependencies and scripts. * `tsconfig.json`: Contains your project's TypeScript configuration. * `xmcp.config.ts`: Contains your project's xmcp configuration. ## Source directory The `src/` directory houses your project's implementation. xmcp follows a declarative, file-system based approach—simply create a file in the appropriate directory, and it will be automatically discovered and registered. The optional `middleware.ts` file at the root of `src/` processes HTTP requests and responses. You can customize the location of `tools/`, `prompts/`, and `resources/` directories in your `xmcp.config.ts` file. See the [custom directories](../../configuration/custom-directories) documentation for details. # Authentication (/docs/guides/authentication) ## Overview MCP provides authorization capabilities at the transport level, enabling clients to make requests to restricted servers on behalf of resource owners. The authorization mechanism is based on OAuth 2.1 and implements several related standards. ## Quickstart xmcp provides authentication plugins that handle the entire OAuth flow, enabling login requirements and role-based access control for your tools. ## When do you need authentication? Some MCP servers can operate without authentication. The decision depends on how your server is deployed and what it exposes. **HTTP-based MCP servers** that are deployed remotely should implement authentication. When your server is accessible over the network, you need to verify who is making requests before granting access to tools and resources. This applies to servers deployed on platforms like Vercel, AWS, or any cloud provider, as well as self-hosted servers exposed to the internet. **STDIO-based MCP servers** running locally on a user's machine can typically skip authentication. Since the server runs in the user's own environment with their permissions, the user is implicitly trusted. Instead of OAuth, these servers should retrieve any needed credentials from environment variables or local configuration files. The key distinction is trust and exposure. A local STDIO server inherits trust from the user's operating system. A remote HTTP server is exposed to the network and must establish trust through authentication before processing requests. ## Authentication vs authorization Authentication confirms a user's identity. Authorization controls what that user can access. Every request to your MCP server raises two questions: who is making this request, and what are they allowed to do? Not every server needs complex authorization. Sometimes a valid credential is enough to grant access. Other servers require fine-grained control: different permissions for different users, or access restricted to specific tools. OAuth handles both authentication and authorization through tokens and scopes. For simpler needs, xmcp provides alternatives like API keys and JWTs. [API Keys](/docs/authentication/api-key) are static credentials included in each request. They work well for server-to-server communication or internal services where you control both ends. [JSON Web Tokens](/docs/authentication/jwt) are self-contained tokens that carry user claims like identity and roles. They're useful when you manage authentication externally and want to pass verified user information to your MCP server. ## How MCP authentication works MCP authentication is built on OAuth 2.1 with mandatory PKCE (Proof Key for Code Exchange). PKCE is a security extension that protects the authorization flow. This makes authentication secure even for public clients like desktop apps and CLI tools that are not designed for persistent secret storage. MCP Authentication Flow ### Discovery When an MCP client first connects to an authenticated server, it needs to discover where and how to authenticate. This happens through a two-step process: first finding the authorization server, then retrieving its configuration. #### Resource Discovery The client starts by fetching the Protected Resource Metadata from `/.well-known/oauth-protected-resource`. This document tells the client which authorization server handles authentication for this MCP server. #### Authorization Server Discovery Next, the client retrieves the authorization server's configuration. For a server at `https://auth.example.com/tenant1`, the client tries these endpoints in order: 1. `https://auth.example.com/.well-known/oauth-authorization-server/tenant1` 2. `https://auth.example.com/.well-known/openid-configuration/tenant1` 3. `https://auth.example.com/tenant1/.well-known/openid-configuration` For servers without a path component (like `https://auth.example.com`), the client tries: 1. `https://auth.example.com/.well-known/oauth-authorization-server` 2. `https://auth.example.com/.well-known/openid-configuration` The authorization server metadata includes the `authorization_endpoint` where users sign in and the `token_endpoint` where clients exchange authorization codes for access tokens. ### Client registration Before a client can authenticate users, it needs to identify itself to the authorization server. MCP supports three approaches: **Client ID Metadata Documents** are the recommended approach. Clients host a JSON document at an HTTPS URL that describes their identity, name, and allowed redirect URIs. The URL itself becomes the client ID, eliminating the need for pre-registration. A Client ID Metadata Document looks like this: ```json { "client_id": "https://app.example.com/oauth/client-metadata.json", "client_name": "My MCP Client", "client_uri": "https://app.example.com", "redirect_uris": [ "http://127.0.0.1:3000/callback", "http://localhost:3000/callback" ], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none" } ``` The URL where this document is hosted becomes the client ID. Authorization servers that support this approach advertise `client_id_metadata_document_supported: true` in their metadata. **Dynamic Client Registration** allows clients to register automatically by making a request to the authorization server. This creates a unique client ID for each installation. ### User authentication Once the client knows where to authenticate, it redirects the user to the authorization server's login page. The user signs in with their credentials and grants the client permission to access the MCP server on their behalf. PKCE protects this flow by generating a unique code verifier for each authentication attempt. The client sends a hashed version of this verifier with the authorization request and proves possession of the original when exchanging the authorization code for tokens. This ensures authorization codes remain secure even if intercepted. ### Token-based access After successful authentication, the client receives an access token. This token is included in every request to the MCP server via the `Authorization: Bearer` header. The server validates the token and extracts user information to authorize the request. Tokens are bound to the specific MCP server using the `resource` parameter during authentication. This ensures tokens issued for one server remain valid only for that server, protecting against token confusion attacks. ### Resource indicators MCP clients must include the `resource` parameter [(RFC 8707)](https://datatracker.ietf.org/doc/html/rfc8707) in authorization and token requests. This parameter identifies the specific MCP server the token is intended for: ``` &resource=https%3A%2F%2Fmcp.example.com ``` The resource parameter provides critical security benefits: * **Audience binding**: Tokens are bound to a specific MCP server and are valid only for the intended server * **Token confusion prevention**: Ensures tokens issued for one server remain valid only for that server * **Server validation**: MCP servers must validate that tokens were specifically issued for them When making authorization requests, clients include the MCP server's canonical URI as the resource parameter. The authorization server embeds this in the token, and the MCP server validates it before processing requests. ### Scopes Scopes define what an access token is allowed to do. They act as permissions that limit the capabilities of a token, even for an authenticated user. When a client initiates authentication, it requests specific scopes like `read`, `write`, or `admin`. The authorization server includes the granted scopes in the access token. Your MCP server can then check these scopes before allowing certain operations. Scopes are particularly useful when: * Different clients need different permission levels * You want to limit what third-party integrations can do * Users should be able to grant partial access to their account The MCP server advertises its supported scopes in the protected resource metadata, and clients can request specific scopes during the authorization flow. For example, a token with only the `read` scope could access tools that fetch data but would require additional scopes to use tools that modify data. This provides fine-grained access control beyond simple authentication. When initiating authentication, clients determine which scopes to request using this priority order: 1. Use the `scope` parameter from the `WWW-Authenticate` header if the server provided one 2. Request all scopes listed in `scopes_supported` from the Protected Resource Metadata 3. Omit the scope parameter entirely if `scopes_supported` is not defined This strategy ensures clients request appropriate permissions based on what the server advertises. ## References * [MCP Specification - Authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) * [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) * [RFC 8707 - Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707) * [RFC 8414 - Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) * [RFC 9728 - Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728) # Monetization (/docs/guides/monetization) ## Overview MCP servers can monetize tools through payment verification. xmcp supports two approaches: license key validation where tools check credentials before returning results, and crypto payments where the transport layer blocks execution until payment is verified. ## Quickstart xmcp provides monetization plugins that handle payment verification and access control for your tools. ## When do you need monetization? Not every MCP server needs monetization. The decision depends on how your tools are deployed and what value they provide. **Monetize your tools when** they provide significant value that justifies payment. This includes tools that access proprietary data, perform expensive computations, call paid external APIs, or provide unique capabilities users are willing to pay for. **Monetization may not be necessary** for simple utilities, internal tools, or when your business model relies on other revenue streams like consulting or support contracts. ## Human vs agent monetization The two monetization approaches serve fundamentally different use cases based on who initiates payment. **Human-based monetization** with [Polar](/docs/integrations/polar) requires a person to purchase a license key and configure it in their MCP client. The human decides to pay upfront through a checkout flow, subscription, or credit purchase. The agent then uses that credential for subsequent requests. This model works well for SaaS-style billing where users manage their own subscriptions. **Agent-based monetization** with [x402](/docs/integrations/x402) enables agents to pay autonomously. When an agent encounters a paid tool, it can sign a crypto payment from its wallet without human intervention. The agent receives payment requirements, authorizes the transaction, and continues. This enables true agent-to-agent commerce where AI agents can purchase services from other AI agents. ### License key validation With Polar, the client includes a license key in the request headers. The server validates this key against Polar's API, checking that the license is active and within usage limits. ```typescript title="src/tools/premium-tool.ts" import { PolarProvider } from "@xmcp-dev/polar"; import { headers } from "xmcp/headers"; const polar = PolarProvider.getInstance({ token: process.env.POLAR_TOKEN, organizationId: process.env.POLAR_ORGANIZATION_ID, productId: process.env.POLAR_PRODUCT_ID, }); export default async function premiumTool({ data }) { const licenseKey = headers()["license-key"]; const response = await polar.validateLicenseKey(licenseKey); if (!response.valid) { return response.message; } return `Result: ${data}`; } ``` The validation checks multiple conditions: license status, usage limits, expiration dates, and meter credits. If validation fails, the response includes a checkout URL where users can purchase or renew their license. #### Usage metering Polar supports usage-based billing through meter credits. When you pass an event to `validateLicenseKey`, the plugin tracks consumption against the user's credit balance: ```typescript title="src/tools/metered-tool.ts" const response = await polar.validateLicenseKey(licenseKey, { name: "api_call", metadata: { tool_name: "premium-tool", calls: 1 }, }); ``` If the user has exhausted their meter credits, validation fails with a message and a checkout URL to purchase more. For more details, check the [Polar integration guide](/docs/integrations/polar). ### Crypto payment flow With x402, the payment flow follows the [HTTP 402 Payment Required standard](https://www.x402.org/): 1. Client calls a paid tool without payment 2. Server responds with payment requirements (price, wallet, network) 3. Client signs a payment authorization from their wallet 4. Client retries the request with the signed payment in headers 5. Server verifies the payment signature with a facilitator 6. Tool executes and client receives the response 7. Payment settles on-chain ```typescript title="src/middleware.ts" import { x402Provider } from "@xmcp-dev/x402"; export default x402Provider({ wallet: process.env.X402_WALLET, defaults: { price: 0.01, currency: "USDC", network: "base", }, }); ``` ```typescript title="src/tools/paid-tool.ts" import { paid } from "@xmcp-dev/x402"; export default paid( { price: 0.05 }, async function paidTool({ input }) { return `Processed: ${input}`; } ); ``` The `paid()` wrapper marks a tool as requiring payment. Tools without this wrapper remain free. You can set prices per-tool or use the middleware defaults. #### Payment requirements When a client calls a paid tool without valid payment, the server returns the payment requirements: ```json { "error": "Payment required", "accepts": [{ "scheme": "exact", "network": "eip155:8453", "amount": "50000", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0x..." }] } ``` The client uses this information to construct a signed payment authorization. The `amount` is in atomic units (6 decimals for USDC), so `50000` equals $0.05. #### Payment context Inside a paid tool, you can access payment information: ```typescript title="src/tools/paid-tool.ts" import { paid, payment } from "@xmcp-dev/x402"; export default paid(async function paidTool({ input }) { const { payer, transactionHash } = payment(); return `Processed for ${payer}`; }); ``` ## Pricing considerations **For subscriptions**, consider what comparable services charge and what value users derive over a billing period. Polar supports multiple tiers, usage limits, and meter-based consumption tracking. **For pay-per-use**, consider the cost of executing the tool (API calls, compute, etc.) and add a margin. Prices between $0.001 and $0.10 per call are common for micropayments, depending on the tool's complexity and value. ## References * [Polar Integration](/docs/integrations/polar) - Full setup guide for license keys * [x402 Integration](/docs/integrations/x402) - Full setup guide for crypto payments * [x402 Protocol](https://www.x402.org/) - HTTP 402 payment standard * [Polar Documentation](https://docs.polar.sh/) - License key management # xmcp MCP server (/docs/guides/xmcp-mcp-server) ## Getting started ### Connect to a client Click any card to copy the connection config for your client. ### Standard connection For clients not listed above, you can use the following connection method. ```json { "command": "npx", "args": ["mcp-remote", "https://xmcp.dev/mcp"] } ``` ## Available tool The server exposes a `search` tool that agents use automatically: ```typescript // Search by query search({ query: "how to configure transports" }); // Get specific page by ID search({ id: "configuration/transports" }); ``` ## Example usage Once connected, just ask your agent about xmcp: * "How do I configure xmcp for Next.js?" * "What transport options does xmcp support?" * "How do I create a tool with authentication?" The agent will search the docs and provide accurate answers with code examples. ## Troubleshooting * **Not connecting**: Verify the URL `https://xmcp.dev/mcp` is correct and restart your agent * **No results**: Try more specific queries or use exact doc IDs * **Agent not using MCP**: Check your agent's MCP configuration and logs # Auth0 (/docs/integrations/auth0) ## Installation Install the Auth0 plugin: ## Auth0 Tenant Setup MCP clients use Dynamic Client Registration (DCR) and the OAuth 2.0 Resource Parameter to authenticate. Your Auth0 tenant requires specific configuration for this to work. ### 1. Enable Dynamic Client Registration MCP clients register themselves automatically with your Auth0 tenant. 1. Go to **Auth0 Dashboard** → **Settings** → **Advanced** 2. Enable **"OIDC Dynamic Application Registration"** 3. Save changes. ### 2. Enable Resource Parameter Compatibility Profile 1. Go to **Auth0 Dashboard** → **Settings** → **Advanced** 2. Enable **"Resource Parameter Compatibility Profile"** 3. Save changes. ### 3. Promote Connection to Domain Level DCR-registered clients are third-party by default and can only use domain-level connections. 1. Go to **Auth0 Dashboard** → **Authentication** → **Database** 2. Select your connection (e.g., `Username-Password-Authentication`) 3. Enable **"Enable for third-party clients"** (or "Promote to domain level") 4. Save changes. ### 4. Create the API 1. Go to **Auth0 Dashboard** → **Applications** → **APIs** 2. Click **Create API** 3. Set: * **Name**: e.g., `MCP Server API` * **Identifier**: Your server URL (for development we will use `http://localhost:3001/`) ### 5. Set Default Audience 1. Go to **Auth0 Dashboard** → **Settings** → **General** 2. Under **API Authorization Settings**, set **Default Audience** to your API identifier. 3. Save changes. ### 6. Note your Domain, Client ID, and Client Secret * In your **Dashboard**, go to **Applications** and create an M2M application. * Go to **Settings** and under **Basic Information**, you will find your **Domain** (following the format `..auth0.com`), **Client ID**, and **Client Secret**. * Save these values in your environment variables under `DOMAIN`, `CLIENT_ID`, and `CLIENT_SECRET`. ### 7. Enable Management API Required for permission checking. The plugin queries Auth0 to determine which tools require permissions. 1. In your M2M application, go to **APIs** tab 2. Enable **Auth0 Management API** 3. Grant the following permissions: * `read:resource_servers`: Check which tool permissions are defined * `read:users`: Verify user has the required permissions ## Environment Variables Configure the following environment variables in your `.env` file: ```bash # Credentials DOMAIN=your-tenant.auth0.com AUDIENCE=http://127.0.0.1:3001/ CLIENT_ID=your-m2m-client-id CLIENT_SECRET=your-m2m-client-secret # App configuration BASE_URL=http://127.0.0.1:3001 ``` `AUDIENCE` must match your Auth0 API identifier. ## Set up the Provider Create a `middleware.ts` file in your xmcp app's `src` directory: ```typescript title="src/middleware.ts" import { auth0Provider } from "@xmcp-dev/auth0"; export default auth0Provider({ domain: process.env.DOMAIN!, audience: process.env.AUDIENCE!, baseURL: process.env.BASE_URL!, clientId: process.env.CLIENT_ID!, clientSecret: process.env.CLIENT_SECRET!, }); ``` ### Configuration Options * **`domain`**: Your Auth0 domain (e.g., `your-tenant.auth0.com`) * **`audience`**: The API identifier configured in Auth0. * **`baseURL`**: Base URL of your MCP server. * **`clientId`**: Application client ID. * **`clientSecret`**: Application client secret. * **`scopesSupported`**: (Optional) Array of additional OAuth scopes beyond the defaults (`openid`, `profile`, `email`) * **`management`**: (Optional) Override configuration for the Management API. * `audience`: (Optional) Custom audience for the Management API. * `resourceServerIdentifier`: (Optional) Resource server identifier. ## Public vs Protected tools By default, all tools are public and accessible by any user that is authenticated. This means any user with a valid Auth0 token can access the tool, regardless of their roles or permissions. Use public tools for: * General-purpose utilities that all users need. * Non-sensitive operations like greeting users or displaying public information. * Tools that don't access or modify restricted resources. ### Protected Tools Protected tools require specific permissions to access. Use them to restrict sensitive operations to authorized users only. Use protected tools for: * Operations that modify critical resources. * Features limited to specific user tiers or roles. * Access to Token Vault. ### How It Works The plugin queries Auth0 Management API on each request: 1. xmcp constructs the permission name as `tool:` using the tool's `metadata.name` 2. **Check if permission exists** → queries `read:resource_servers` to see if `tool:` is defined 3. **If permission exists** → queries `read:users` to verify the user has it assigned 4. **If permission does not exist** → tool is public, any authenticated user can access Users without the required permission will see: "You don't have permission to use the 'tool-name' tool." If Management API calls fail, the secure default is to deny access. ## Configure roles and RBAC for protected tools This section is optional, only needed when you want to use permission-protected tools. ### Enable RBAC 1. Go to **Auth0 Dashboard** → **Applications** → **APIs** → your API 2. Go to **Settings** tab 3. Enable **"Enable RBAC"** 4. Enable **"Add Permissions in the Access Token"** 5. Save changes ### Create Roles and Assign Permissions 1. Go to **Auth0 Dashboard** → **User Management** → **Roles** 2. Click **Create Role** (e.g., "MCP Admin") 3. Go to **Permissions** tab → **Add Permissions** 4. Select your API and add permissions (e.g., `tool:greet`, `tool:whoami`) 5. Go to **Users** tab → **Add Users** to assign the role to users ## Get a user session Access the authenticated user's session in your tools using `getAuthInfo()`. ### Example: Greet the user with their Auth0 identity ```typescript title="src/tools/greet.ts" import { z } from "zod"; import type { InferSchema, ToolMetadata } from "xmcp"; import { getAuthInfo } from "@xmcp-dev/auth0"; export const schema = { name: z.string().optional().describe("The name of the user to greet"), }; export const metadata: ToolMetadata = { name: "greet", description: "Greet the user with their Auth0 identity", }; export default function greet({ name }: InferSchema): string { const authInfo = getAuthInfo(); const displayName = authInfo.user.name ?? name ?? "there"; return `Hello, ${displayName}! Your Auth0 user ID is ${authInfo.user.sub}`; } ``` The `authInfo` object contains token data and user claims: * `authInfo.token`: The raw access token * `authInfo.clientId`: OAuth client ID. * `authInfo.scopes`: Array of granted scopes. * `authInfo.permissions`: Array of additional permissions from the token. * `authInfo.expiresAt`: Token expiration timestamp. * `authInfo.user.sub`: User ID (subject claim). ## Access the clients The `getClient()` and `getManagement()` functions give you access to the full Auth0 SDKs, allowing you to leverage all Auth0 features in your MCP tools. ### Example: Exchange tokens to call external APIs Use `getClient()` to exchange tokens and call external APIs on behalf of authenticated users using Auth0's Custom Token Exchange flow: ```typescript title="src/tools/call-api.ts" import type { ToolMetadata } from "xmcp"; import { getClient, getAuthInfo } from "@xmcp-dev/auth0"; export const metadata: ToolMetadata = { name: "call-external-api", description: "Call an external API on behalf of the authenticated user", }; async function exchangeCustomToken(subjectToken: string) { const client = getClient(); return await client.getTokenByExchangeProfile(subjectToken, { subjectTokenType: "urn:ietf:params:oauth:token-type:access_token", audience: process.env.EXTERNAL_API_AUDIENCE!, ...(process.env.EXCHANGE_SCOPE && { scope: process.env.EXCHANGE_SCOPE }), }); } export default async function callExternalApi(): Promise { const authInfo = getAuthInfo(); try { const { access_token } = await exchangeCustomToken(authInfo.token); // Use the exchanged token to call your external API const response = await fetch(process.env.EXTERNAL_API_URL!, { headers: { Authorization: `Bearer ${access_token}` }, }); return await response.text(); } catch (error) { return error instanceof Error ? error.message : "Failed to call API"; } } ``` This example demonstrates how to use `getTokenByExchangeProfile()` to exchange the user's MCP access token for a new token with a different audience, allowing your MCP server to call external APIs on the user's behalf. ### Example: Update user metadata ```typescript title="src/tools/update-preferences.ts" import type { ToolMetadata } from "xmcp"; import { getManagement, getAuthInfo } from "@xmcp-dev/auth0"; export const metadata: ToolMetadata = { name: "update-preferences", description: "Update user preferences using the Management API", }; export default async function updatePreferences(): Promise { const authInfo = getAuthInfo(); const client = getManagement(); try { await client.users.update(authInfo.user.sub, { user_metadata: { theme: "dark" }, }); return "Preferences updated!"; } catch (error) { return error instanceof Error ? error.message : "Failed to update preferences"; } } ``` The `getManagement()` function provides typed methods for all Auth0 Management operations and is only available when the `management` configuration is provided. ## Troubleshooting These are the most common errors you may encounter when using the Auth0 plugin: * `unauthorized`: The request is missing the Authorization header. The client needs to authenticate before accessing protected resources. * `token_expired`: The access token has expired. MCP clients should automatically refresh tokens; users can disconnect and reconnect to get fresh tokens. * `invalid_token`: Token verification failed. Check that your Auth0 configuration matches your tenant settings. * `InsufficientScopeError`: The token doesn't have the required scopes for the tool. Ensure the scope is defined in your Auth0 API and requested during login. * `Service not found`: The API identifier must match your `BASE_URL` exactly, including the trailing slash. MCP clients send a `resource` parameter that Auth0 uses as the audience, and any mismatch causes this error. ### OAuth Init Failed If you see "OAuth init failed" when connecting: * Ensure Dynamic Client Registration is enabled in Auth0 Settings → Advanced * Enable the Resource Parameter Compatibility Profile in Auth0 Settings → Advanced ### Access Denied / Service Not Found If you see "access denied" or "Service not found" errors: * Your Auth0 API identifier must match `BASE_URL` exactly (including trailing slash) * Promote your database connection to domain level (Authentication → Database → Enable for third-party clients) * Set the Default Audience in Settings → General → API Authorization Settings ### Token Expired Errors Access tokens are short-lived. If you see `token_expired` errors: * MCP clients should automatically refresh tokens * Users can disconnect and reconnect to get fresh tokens ### Invalid Token Errors If token verification fails: * Verify `DOMAIN` matches your Auth0 tenant * Verify `AUDIENCE` matches your API identifier exactly (including trailing slash) ### Permission Check Failed If you see "You don't have permission..." errors for tools that should be public: * Your M2M app needs `read:resource_servers` and `read:users` permissions on the Auth0 Management API * Ensure `CLIENT_ID` and `CLIENT_SECRET` are set correctly * Verify the permissions are granted in the M2M app's **APIs** tab ### Session Not Initialized If `getAuthInfo()` throws an error: * Ensure `auth0Provider` is exported as default from `middleware.ts` * Ensure the tool is called on a route under `/mcp/*` * Don't call `getAuthInfo()` at module load time—only inside tool handlers # Better Auth (/docs/integrations/better-auth) ## Overview The Better Auth plugin provides comprehensive authentication for your xmcp server using [Better Auth](https://www.better-auth.com/), supporting email/password authentication, OAuth providers, and session management. Currently supports PostgreSQL as the database provider. ## Installation Install the Better Auth plugin and PostgreSQL dependencies: ## Database Setup Better Auth requires a PostgreSQL database with specific tables for user management, sessions, and OAuth applications. We recommend [Neon](https://neon.tech/) for easy PostgreSQL setup, especially with Vercel's storage integration. Run the following SQL script to create the necessary tables: ```sql -- User table for storing user information CREATE TABLE "user" ( "id" text NOT NULL PRIMARY KEY, "name" text NOT NULL, "email" text NOT NULL UNIQUE, "emailVerified" boolean NOT NULL, "image" text, "createdAt" timestamp NOT NULL, "updatedAt" timestamp NOT NULL ); -- Session table for managing user sessions CREATE TABLE "session" ( "id" text NOT NULL PRIMARY KEY, "expiresAt" timestamp NOT NULL, "token" text NOT NULL UNIQUE, "createdAt" timestamp NOT NULL, "updatedAt" timestamp NOT NULL, "ipAddress" text, "userAgent" text, "userId" text NOT NULL REFERENCES "user" ("id") ); -- Account table for OAuth and local authentication CREATE TABLE "account" ( "id" text NOT NULL PRIMARY KEY, "accountId" text NOT NULL, "providerId" text NOT NULL, "userId" text NOT NULL REFERENCES "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" timestamp, "refreshTokenExpiresAt" timestamp, "scope" text, "password" text, "createdAt" timestamp NOT NULL, "updatedAt" timestamp NOT NULL ); -- Verification table for email verification and password resets CREATE TABLE "verification" ( "id" text NOT NULL PRIMARY KEY, "identifier" text NOT NULL, "value" text NOT NULL, "expiresAt" timestamp NOT NULL, "createdAt" timestamp, "updatedAt" timestamp ); -- OAuth application table for OAuth provider functionality CREATE TABLE "oauthApplication" ( "id" text NOT NULL PRIMARY KEY, "name" text NOT NULL, "icon" text, "metadata" text, "clientId" text NOT NULL UNIQUE, "clientSecret" text, "redirectURLs" text NOT NULL, "type" text NOT NULL, "disabled" boolean, "userId" text, "createdAt" timestamp NOT NULL, "updatedAt" timestamp NOT NULL ); -- OAuth access token table CREATE TABLE "oauthAccessToken" ( "id" text NOT NULL PRIMARY KEY, "accessToken" text NOT NULL UNIQUE, "refreshToken" text NOT NULL UNIQUE, "accessTokenExpiresAt" timestamp NOT NULL, "refreshTokenExpiresAt" timestamp NOT NULL, "clientId" text NOT NULL, "userId" text, "scopes" text NOT NULL, "createdAt" timestamp NOT NULL, "updatedAt" timestamp NOT NULL ); -- OAuth consent table for managing user consent CREATE TABLE "oauthConsent" ( "id" text NOT NULL PRIMARY KEY, "clientId" text NOT NULL, "userId" text NOT NULL, "scopes" text NOT NULL, "createdAt" timestamp NOT NULL, "updatedAt" timestamp NOT NULL, "consentGiven" boolean NOT NULL ); ``` Schema generation through Better Auth's CLI is not currently supported. You must run this SQL manually. ## Environment Variables Configure the following environment variables in your `.env` file: ```bash # Database connection string DATABASE_URL=postgresql://:@:/ # Better Auth configuration BETTER_AUTH_SECRET= BETTER_AUTH_BASE_URL= # Optional: OAuth provider credentials GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= ``` Generate a strong, random secret for `BETTER_AUTH_SECRET`. This is used to sign JWT tokens and must be kept secure. ## Configuration Create a `middleware.ts` file in your xmcp app root directory: ```typescript title="src/middleware.ts" import { betterAuthProvider } from "@xmcp-dev/better-auth"; import { Pool } from "pg"; export default betterAuthProvider({ database: new Pool({ connectionString: process.env.DATABASE_URL, }), baseURL: process.env.BETTER_AUTH_BASE_URL || "http://127.0.0.1:3001", secret: process.env.BETTER_AUTH_SECRET || "super-secret-key", providers: { emailAndPassword: { enabled: true, }, google: { clientId: process.env.GOOGLE_CLIENT_ID || "", clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", }, }, }); ``` ### Configuration Options * **`database`** - PostgreSQL Pool instance for database connections * **`baseURL`** - Base URL of your app for generating OAuth callback URLs * **`secret`** - Secret key for signing JWT tokens * **`providers`** - Authentication provider configuration ## Authentication Providers ### Email and Password Enable email/password authentication: ```typescript export default betterAuthProvider({ // ... other config providers: { emailAndPassword: { enabled: true, }, }, }); ``` ### Google OAuth To enable Google OAuth: 1. Visit the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard) 2. Create or select a project 3. Enable the Google+ API 4. Create OAuth 2.0 credentials 5. Set authorized redirect URI: * Development: `http://localhost:3001/auth/callback/google` * Production: `https://yourdomain.com/auth/callback/google` ```typescript export default betterAuthProvider({ // ... other config providers: { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }, }, }); ``` ### Multiple Providers You can enable multiple authentication methods simultaneously: ```typescript export default betterAuthProvider({ // ... other config providers: { emailAndPassword: { enabled: true, }, google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }, }, }); ``` ## Usage in Tools Access the authenticated user session in your xmcp tools using `getBetterAuthSession`: ```typescript title="src/tools/get-user-profile.ts" import { getBetterAuthSession } from "@xmcp-dev/better-auth"; export default async function getUserProfile() { const session = await getBetterAuthSession(); return `Hello! Your user id is ${session.userId}`; } ``` `getBetterAuthSession` will throw an error if called outside of a `betterAuthProvider` middleware context. ## Login Page The authentication UI is automatically generated and available at: ``` http://host:port/auth/sign-in ``` This page handles both sign-in and sign-up functionality based on your provider configuration. ## Next Steps After authentication is configured, users will be prompted to authenticate when establishing a connection to your MCP server. # Clerk (/docs/integrations/clerk) ## Installation Install the Clerk plugin: ## Clerk Setup Before integrating the plugin, configure your Clerk application: 1. Navigate to your [Clerk Dashboard](https://dashboard.clerk.com) 2. Create a new application (or use an existing one) 3. Go to **Configure** and access to **API Keys** to get the following values: * **Secret Key** (`sk_...`) * **Frontend API** URL (`your-app.clerk.accounts.dev`) 4. Click on **Development**, enter to **OAuth Applications** and enable **Dynamic Client Registration** ### Environment Variables Create a `.env` file in the root of your project and configure the following environment variables: ```bash CLERK_SECRET_KEY=sk_... CLERK_DOMAIN=your-app.clerk.accounts.dev BASE_URL=http://127.0.0.1:3001 ``` For production, the `BASE_URL` should be replaced with your deployed server URL. ## Set up the Provider Create a `middleware.ts` file in your xmcp app's `src` directory and import the provider from the package: ```typescript title="src/middleware.ts" import { clerkProvider } from "@xmcp-dev/clerk"; export default clerkProvider({ secretKey: process.env.CLERK_SECRET_KEY!, clerkDomain: process.env.CLERK_DOMAIN!, baseURL: process.env.BASE_URL! }); ``` ### Configuration Options * **`secretKey`**: Clerk Secret Key * **`clerkDomain`**: Clerk Frontend domain * **`baseURL`**: Base URL of your MCP server * **`scopes`**: (Optional) OAuth scopes to request (default: `['profile', 'email']`) * **`docsURL`**: (Optional) URL to your MCP server's API documentation ## Get a user session Access the authenticated user in your xmcp tools using `getSession`: ### Example: Greet the user with their Clerk identity ```typescript title="src/tools/greet.ts" import { z } from "zod"; import type { InferSchema, ToolMetadata } from "xmcp"; import { getSession } from "@xmcp-dev/clerk"; // Define the schema for tool parameters export const schema = { name: z.string().describe("The name of the user to greet"), }; // Define tool metadata export const metadata: ToolMetadata = { name: "greet", description: "Greet the user with their Clerk identity", }; // Tool implementation export default function greet({ name }: InferSchema): string { const session = getSession(); return `Hello, ${name}! Your Clerk user ID is ${session.userId}`; } ``` ## Get user details Access the authenticated user in your xmcp tools using `getUser`: ### Example: Get user details ```typescript title="src/tools/get-user-info.ts" import type { ToolMetadata } from "xmcp"; import { getUser } from "@xmcp-dev/clerk"; // Define tool metadata export const metadata: ToolMetadata = { name: "get-user-info", description: "Get user details", }; // Tool implementation export default async function getUserInfo(): Promise { const user = await getUser(); return JSON.stringify(user, null, 2); } ``` ## Access the client The `getClient()` function gives you access to the full [Clerk Backend SDK](https://clerk.com/docs/references/backend/overview), allowing you to leverage all Clerk features in your MCP tools. ### Example: Retrieve an Organization ```typescript title="src/tools/get-org.ts" import { getSession, getClient } from "@xmcp-dev/clerk"; export default async function getOrganization() { const session = getSession(); const clerk = getClient(); if (!session.organizationId) { return "User is not in an organization"; } const org = await clerk.organizations.getOrganization({ organizationId: session.organizationId, }); return JSON.stringify(org, null, 2); } ``` ## Troubleshooting ### Missing Configuration Errors If you see `"[Clerk] Missing required config: ..."` at startup: * Ensure all required environment variables are set (`CLERK_SECRET_KEY`, `CLERK_DOMAIN`, `BASE_URL`) * Check your `.env` file is being loaded correctly ### Session or Client Not Initialized If `getSession()`, `getUser()`, or `getClient()` throws `"... not initialized"`: * Ensure `clerkProvider` is exported as default from `middleware.ts` * Ensure the tool is called on a route under `/mcp/*` * Don't call these functions at module load time only inside tool handlers ### Token Expired Errors Access tokens are short-lived. If you see `token_expired` errors: * MCP clients should automatically refresh tokens * Users can disconnect and reconnect to get fresh tokens ### Invalid Token Errors If tokens are consistently invalid: * Verify your `CLERK_SECRET_KEY` matches your Clerk application * Verify your `CLERK_DOMAIN` matches your Clerk Frontend API URL * Check that you're using the correct environment (development vs production) ### Authentication Service Misconfigured If you see a `500` error with `"Authentication service misconfigured"`: * Your `CLERK_SECRET_KEY` is invalid or doesn't match your Clerk application * Double-check you're using the correct key for your environment (development vs production) # Polar (/docs/integrations/polar) ## Overview The Polar plugin enables you to add paywalls with license key validation and track tool usage for your xmcp server using [Polar](https://polar.sh/). ## Installation Install the Polar plugin: ## Polar Setup Before integrating the plugin, set up your product on [Polar](https://polar.sh/): 1. Create a new product with your desired payment configuration 2. Add the **License Key** benefit to the product 3. (Optional) Add a **Meter Credit** benefit to track and limit tool usage ## Configuration Initialize the Polar provider to access validation methods: ```typescript title="src/lib/polar.ts" import { PolarProvider } from "@xmcp-dev/polar"; export const polar = PolarProvider.getInstance({ type: "sandbox", // or "production" token: process.env.POLAR_TOKEN, organizationId: process.env.POLAR_ORGANIZATION_ID, productId: process.env.POLAR_PRODUCT_ID, }); ``` ### Configuration Options ```typescript interface Configuration { type?: "production" | "sandbox"; token: string; organizationId: string; productId: string; } ``` * `type` - Environment type (defaults to `"production"` if not set) * `token` - Polar authentication token * `organizationId` - Your Polar organization ID * `productId` - Your Polar product ID License keys must be provided in the `license-key` header. This header name is not customizable. ## License Key Validation Validate license keys in your tools using the `validateLicenseKey` method: ```typescript import { headers } from "xmcp/headers"; const licenseKey = headers()["license-key"]; const response = await polar.validateLicenseKey(licenseKey); ``` ### Response Object The validation response contains: ```typescript { valid: boolean; code: string; message: string; } ``` ### Handling Invalid Keys Return appropriate messages when validation fails: ```typescript if (!response.valid) { return response.message; } ``` This automatically prompts users with the checkout URL when the license key is invalid. ## Usage Tracking Track tool usage by adding a meter credit benefit to your product and passing event objects during validation. ### Meter Credit Setup Configure a meter credit benefit in your Polar product with the appropriate limits and tracking settings. ### Tracking Events Pass an event object to `validateLicenseKey` to record usage: ```typescript const event = { name: "tool_call_event", metadata: { tool_name: "tool_name", calls: 1 }, }; const response = await polar.validateLicenseKey(licenseKey, event); ``` The `metadata` field accepts any string or number values for flexible usage tracking. ## Example Here's a complete example integrating license validation and usage tracking: ```typescript title="src/tools/protected-tool.ts" import { PolarProvider } from "@xmcp-dev/polar"; import { headers } from "xmcp/headers"; export const polar = PolarProvider.getInstance({ type: "production", token: process.env.POLAR_TOKEN, organizationId: process.env.POLAR_ORGANIZATION_ID, productId: process.env.POLAR_PRODUCT_ID, }); export default async function protectedTool() { const licenseKey = headers()["license-key"]; const response = await polar.validateLicenseKey(licenseKey, { name: "tool_call_event", metadata: { tool_name: "protectedTool", calls: 1 }, }); if (!response.valid) { return response.message; } // Your tool logic here return "Tool executed successfully"; } ``` # WorkOS (/docs/integrations/workos) ## Installation Install the WorkOS plugin: ## WorkOS Setup Before getting started, we need to configure our WorkOS application: 1. Go to your [WorkOS Dashboard](https://dashboard.workos.com). 2. In the **Overview** page, under **Quickstart**, find and save your `WORKOS_API_KEY` and `WORKOS_CLIENT_ID`. 3. Go to **Domains** and save the AuthKit domain, it looks like this `https://xxx.authkit.app`. 4. Navigate to **Connect** and go to **Configuration** and enable the following options inside **MCP Auth** settings: **Client ID Metadata Document (CIMD)** and **Dynamic Client Registration (DCR)**. ### Environment Variables Create a `.env` file in the root of your project and configure the following environment variables: ```bash WORKOS_API_KEY=sk_... WORKOS_CLIENT_ID=client_... WORKOS_AUTHKIT_DOMAIN=yourcompany.authkit.app BASE_URL=http://127.0.0.1:3001 ``` For production, the `BASE_URL` should be replaced with your deployed server URL. ## Set up the Provider Create a `middleware.ts` and import the provider from the package: ```typescript title="src/middleware.ts" import { workosProvider } from "@xmcp-dev/workos"; export default workosProvider({ apiKey: process.env.WORKOS_API_KEY!, clientId: process.env.WORKOS_CLIENT_ID!, authkitDomain: process.env.WORKOS_AUTHKIT_DOMAIN!, baseURL: process.env.BASE_URL!, }); ``` ### Configuration Options * **`apiKey`**: WorkOS API key from your dashboard. * **`clientId`**: WorkOS client ID for OAuth. * **`authkitDomain`**: Your AuthKit domain. * **`baseURL`**: Base URL of your app for OAuth callbacks. * **`docsURL`**: (Optional) URL for your MCP server documentation. ## Get a user session Access the authenticated user in your xmcp tools using `getSession`. ### Example: Greet the user with their WorkOS identity ```typescript title="src/tools/greet.ts" import { z } from "zod"; import { type InferSchema, type ToolMetadata } from "xmcp"; import { getSession } from "@xmcp-dev/workos"; // Define the schema for tool parameters export const schema = { name: z.string().describe("The name of the user to greet"), }; // Define tool metadata export const metadata: ToolMetadata = { name: "greet", description: "Greet the user with their WorkOS identity" }; // Tool implementation export default function greet({ name }: InferSchema): string { const session = getSession(); return `Hello, ${name}! Your WorkOS user ID is ${session.userId}`; } ``` ## Get user details Access the authenticated user in your xmcp tools using `getUser`. ### Example: Get user details ```typescript title="src/tools/get-user-info.ts" import type { ToolMetadata } from "xmcp"; import { getUser } from "@xmcp-dev/workos"; // Define tool metadata export const metadata: ToolMetadata = { name: "get-user-info", description: "Get user details" }; // Tool implementation export default async function getUserInfo(): Promise { const user = await getUser(); return JSON.stringify(user, null, 2); } ``` ## Access the client The `getClient()` function gives you access to the full [WorkOS Node SDK](https://workos.com/docs/sdks/node), allowing you to leverage all WorkOS features in your MCP tools. ### Example: Get organization memberships ```typescript title="src/tools/get-memberships.ts" import { type ToolMetadata } from "xmcp"; import { getSession, getClient } from "@xmcp-dev/workos"; export const metadata: ToolMetadata = { name: "get-my-memberships", description: "Returns the user's organization memberships using the WorkOS SDK directly" }; export default async function getMyMemberships(): Promise { const session = getSession(); const client = getClient(); // Use the WorkOS SDK to fetch organization memberships const memberships = await client.userManagement.listOrganizationMemberships({ userId: session.userId, }); if (memberships.data.length === 0) { return "You are not a member of any organizations."; } return JSON.stringify(memberships.data, null, 2); } ``` ## Troubleshooting ### Token Expired Errors Access tokens are short-lived. If you see `token_expired` errors: * MCP clients should automatically refresh tokens. * If errors persist, the client may have a bug or the refresh token expired. * Users can disconnect and reconnect to get fresh tokens. ### Redirect URI Not Registered If you see this error during OAuth: 1. Copy the redirect URI from the error message. 2. Go to WorkOS Dashboard → Connect → Applications 3. Add the redirect URI to the application. ### Finding a Client's Redirect URI If you need to manually register a client: * Check the error message: OAuth errors include the redirect URI that needs to be registered. * Check client documentation: Each MCP client documents its callback URL. * Check your server logs: Look for the `redirect_uri` parameter in failed OAuth requests. ### Session Not Initialized If you see `Session not initialized` or `can only be used within the workos-context-session context` errors: * Ensure `getSession` is called within a tool or handler that runs through the middleware pipeline. * Verify the `workosProvider` middleware is properly configured in your `middleware.ts`. * Make sure the route is under the `/mcp` path. * Check that the request includes a valid bearer token. ### Wrong Redirect URI If you see that the redirect URI is not registered or not working, you can manually register it in the WorkOS Dashboard: * Check the error message: OAuth errors usually include the redirect URI. * Check client documentation: Each client documents its callback URL. * Check server logs: Look for the `redirect_uri` parameter in OAuth requests. # x402 (/docs/integrations/x402) ## Overview The x402 plugin integrates the [x402 payment protocol](https://www.x402.org/) with your xmcp server, enabling you to charge USDC micropayments for tool usage on the Base blockchain. ## Installation Install the x402 plugin: ## Set up your wallet Before integrating the plugin, you need a wallet address to receive payments: 1. Create or use an existing Ethereum-compatible wallet (e.g., Coinbase Wallet, MetaMask) 2. Get your wallet's public address (starts with `0x`) 3. For testing, use Base Sepolia testnet and get test USDC from the [Coinbase Developer Platform](https://portal.cdp.coinbase.com/) under **Wallets > Faucet** The x402 protocol uses USDC stablecoin for payments. On Base mainnet, 1 USDC = 1 USD. ### Environment Variables Create a `.env` file in the root of your project and configure the following environment variables: ```bash X402_WALLET=0x... # Optional: Custom facilitator URL (defaults to https://x402.org/facilitator) X402_FACILITATOR=https://x402.org/facilitator ``` ## Set up the Provider Create a `middleware.ts` file in your xmcp app's `src` directory and import the provider from the package: ```typescript title="src/middleware.ts" import { x402Provider } from "@xmcp-dev/x402"; export default x402Provider({ wallet: process.env.X402_WALLET!, defaults: { price: 0.01, network: "base-sepolia", }, }); ``` ### Configuration Options * **`wallet`**: Your wallet address that receives payments * **`facilitator`**: (Optional) Facilitator URL (defaults to `https://x402.org/facilitator`) * **`debug`**: (Optional) Enable debug logging * **`defaults`**: (Optional) Default values for all paid tools * **`price`**: Price in USDC (default: `0.01`) * **`currency`**: Currency code (default: `"USDC"`) * **`network`**: Blockchain network - `"base"` or `"base-sepolia"` (default: `"base"`) * **`maxPaymentAge`**: Maximum payment age in seconds (default: `300`) ## Monetize a Tool Wrap your tool functions with `paid()` to require payment: ```typescript title="src/tools/analyze.ts" import { paid } from "@xmcp-dev/x402"; export default paid( { price: 0.05 }, async function analyze({ data }) { // Tool logic here return `Analysis complete for: ${data}`; } ); ``` ### Pricing You can set a custom price for each tool by passing a `price` option to `paid()`. If not specified, the tool will use the default price from your provider configuration. If the provider doesn't specify a default price either, the tool will cost **0.01 USDC** per call. ```typescript title="src/tools/premium-analysis.ts" import { paid } from "@xmcp-dev/x402"; // This tool costs 0.10 USDC per call export default paid( { price: 0.10 }, async function premiumAnalysis({ data }) { // Premium tool logic return `Premium analysis complete for: ${data}`; } ); ``` ```typescript title="src/tools/basic-tool.ts" import { paid } from "@xmcp-dev/x402"; // This tool uses the provider's default price, // or 0.01 USDC if no default is configured export default paid(async function basicTool({ input }) { return `Processed: ${input}`; }); ``` ### Tool Options * **`price`**: Price in USDC for this tool (falls back to provider default, then 0.01 USDC) * **`network`**: Override default network * **`maxPaymentAge`**: Override maximum payment age * **`description`**: Description used in payment requirements ## Get Payment Details Access payment details inside your tool using the `payment()` function: ```typescript title="src/tools/paid-greet.ts" import { paid, payment } from "@xmcp-dev/x402"; export default paid(async function paidGreet({ name }) { const { payer, amount, network, transactionHash } = payment(); return `Hello, ${name}! Payment received from ${payer}. Transaction: ${transactionHash}`; }); ``` ## Networks The plugin supports two networks: | Network | Chain ID | Use Case | | -------------- | -------- | ------------------------- | | `base` | 8453 | Production with real USDC | | `base-sepolia` | 84532 | Testing with testnet USDC | ## Troubleshooting ### Payment Required Response If clients receive a 402 response, they need to: * Extract payment requirements from `result.structuredContent.accepts` * Sign a payment authorization using their wallet * Retry with payment in `params._meta["x402/payment"]` ### Payment Verification Failed If payment verification fails: * Ensure the payment amount matches the tool price * Check that the payment is recent (within `maxPaymentAge` seconds) * Verify the correct network is being used (base vs base-sepolia) ### Settlement Errors If settlement fails after tool execution: * Check the facilitator URL is correct and reachable * Verify your wallet address is valid * Ensure the payer has sufficient USDC balance ### Missing Payment Context If `payment()` throws `"x402 context not initialized"`: * Ensure `x402Provider` is exported as default from `middleware.ts` * Only call `payment()` inside paid tool handlers * Don't call `payment()` at module load time