# 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/integrations/nextjs) or [Express](/docs/integrations/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.
# 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"
}
}
```
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)
This is an experimental feature and may not work as expected.
The OAuth provider implementation strictly implements Dynamic Client Registration.
You can configure the OAuth provider by adding the following to your `xmcp.config.ts` file:
```typescript title="xmcp.config.ts"
import { XmcpConfig } from "xmcp";
const config: XmcpConfig = {
experimental: {
oauth: {
baseUrl: "https://my-app.com",
endpoints: {
authorizationUrl: "https://auth-provider.com/oauth/authorize",
tokenUrl: "https://auth-provider.com/oauth/token",
registerUrl: "https://auth-provider.com/oauth/register", // mandatory
},
issuerUrl: "https://my-app.com",
defaultScopes: ["openid", "profile", "email"],
pathPrefix: "/oauth2",
},
},
};
export default config;
```
The usage of this configuration is only limited to the HTTP transport on apps scaffolded with `create-xmcp-app`, not with the adapter modes.
# 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.
# 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
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,
},
debug: false, // adds extra logging to the console
},
};
export default config;
```
These are the default values. Override only what you need to customize.
## 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.
# Webpack (/docs/configuration/webpack)
`xmcp` uses webpack and swc 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 = {
webpack: (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;
},
};
```
# 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](../../authentication/api-key) and [JSON web token
authentication](../../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)
`xmcp` detects files under the `/src/tools/` directory and registers them as tools. This path 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.
## References
### Schema (optional)
Defines the tool's input parameters with type validation:
* **Key**: Parameter name.
* **Value**: Zod schema with `.describe()` for documentation and tool inspection.
* **Purpose**: Type validation and automatic parameter documentation.
### Metadata (optional)
Defines the tool's identity and behavior hints:
* **Name**: Unique identifier for the tool (defaults to file name if omitted)
* **Description**: Brief explanation of what the tool does (defaults to placeholder if omitted)
* **Annotations**: Behavioral hints for AI models and UIs
While metadata is optional, we strongly recommend providing a clear
description. This helps LLMs discover and use your tools more efficiently,
reducing token costs and improving accuracy.
### 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 array, or a string/number shortcut.
* **Async**: Supports async operations for API calls, file I/O, etc.
# 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 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.
# 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:
```bash
npm install @xmcp-dev/better-auth pg
npm install -D @types/pg
```
## 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.
# MCP UI (/docs/integrations/mcp-ui)
xmcp is compatible with [MCP UI](https://mcpui.dev), which can be used to build interactive interfaces for your MCP server.
## Installation
In your current xmcp project, install the following dependency:
## Create a UI resource
You can create a UI resource for your MCP server using the `createUIResource` function. It allows you to define UI snippets on the server-side, which can then be seamlessly and securely rendered on the client.
### Direct HTML (Text Encoding)
Direct HTML content delivered as plain text:
```typescript
import { createUIResource } from "@mcp-ui/server";
const resource = createUIResource({
uri: "ui://my-component/instance-1",
content: { type: "rawHtml", htmlString: "Hello World
" },
encoding: "text",
});
```
Output:
```json
{
"type": "resource",
"resource": {
"uri": "ui://my-component/instance-1",
"mimeType": "text/html",
"text": "Hello World
"
}
}
```
### Direct HTML (Base64 Blob)
Direct HTML content delivered as a Base64-encoded blob:
```typescript
const resource = createUIResource({
uri: "ui://my-component/instance-2",
content: { type: "rawHtml", htmlString: "Complex HTML
" },
encoding: "blob",
});
```
Output:
```json
{
"type": "resource",
"resource": {
"uri": "ui://my-component/instance-2",
"mimeType": "text/html",
"blob": "PGgxPkNvbXBsZXggSFRNTDwvaDE+"
}
}
```
### External URL (Text Encoding)
External URL delivered as plain text for iframe embedding:
```typescript
const dashboardUrl = "https://my.analytics.com/dashboard/123";
const resource = createUIResource({
uri: "ui://analytics-dashboard/main",
content: { type: "externalUrl", iframeUrl: dashboardUrl },
encoding: "text",
});
```
Output:
```json
{
"type": "resource",
"resource": {
"uri": "ui://analytics-dashboard/main",
"mimeType": "text/uri-list",
"text": "https://my.analytics.com/dashboard/123"
}
}
```
### External URL (Blob Encoding)
External URL delivered as a Base64-encoded blob:
```typescript
const chartApiUrl = "https://charts.example.com/api?type=pie&data=1,2,3";
const resource = createUIResource({
uri: "ui://live-chart/session-xyz",
content: { type: "externalUrl", iframeUrl: chartApiUrl },
encoding: "blob",
});
```
Output:
```json
{
"type": "resource",
"resource": {
"uri": "ui://live-chart/session-xyz",
"mimeType": "text/uri-list",
"blob": "aHR0cHM6Ly9jaGFydHMuZXhhbXBsZS5jb20vYXBpP3R5cGU9cGllJmRhdGE9MSwyLDM="
}
}
```
### Remote DOM Script
Remote DOM script for building interactive components:
```typescript
const remoteDomScript = `
const button = document.createElement('ui-button');
button.setAttribute('label', 'Click me for a tool call!');
button.addEventListener('press', () => {
window.parent.postMessage({
type: 'tool',
payload: {
toolName: 'uiInteraction',
params: { action: 'button-click', from: 'remote-dom' }
}
}, '*');
});
root.appendChild(button);
`;
const resource = createUIResource({
uri: "ui://remote-component/action-button",
content: {
type: "remoteDom",
script: remoteDomScript,
framework: "react", // or 'webcomponents'
},
encoding: "text",
});
```
Output:
```json
{
"type": "resource",
"resource": {
"uri": "ui://remote-component/action-button",
"mimeType": "application/vnd.mcp-ui.remote-dom+javascript; framework=react",
"text": "\\n const button = document.createElement('ui-button');\\n button.setAttribute('label', 'Click me for a tool call!');\\n button.addEventListener('press', () => {\\n window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'remote-dom' } } }, '*');\\n });\\n root.appendChild(button);\\n"
}
}
```
## Usage
UI resources are served through tools in your MCP server. This works with both standalone applications and projects where xmcp is integrated, like Next.js or Express.
In xmcp, simply return the UI resource in your tool's `content` array:
```typescript title="src/tools/get-ui.ts"
import { type ToolMetadata } from "xmcp";
import { uiResource } from "@/lib/ui";
// ...schema and metadata
export default function getUI() {
return {
content: [uiResource],
};
}
```
# 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:
```bash
npm install @xmcp-dev/polar
```
## 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";
}
```