A TypeScript framework for building MCP servers capable of handling client sessions. - Simple Tool, Resource, Prompt definition - Authentication - Passing headers through context - Session ID and Request ID tracking - Image content - Audio content - Error handling - HTTP Streaming (with SSE compatibility) - HTTPS Support for secure connections - Custom HTTP routes for REST APIs, webhooks, and admi
Add this skill
npx mdskills install punkpeye/fastmcpComprehensive framework documentation with extensive features and clear examples for building MCP servers
1# FastMCP23A TypeScript framework for building [MCP](https://glama.ai/mcp) servers capable of handling client sessions.45> [!NOTE]6>7> For a Python implementation, see [FastMCP](https://github.com/jlowin/fastmcp).89## Features1011- Simple Tool, Resource, Prompt definition12- [Authentication](#authentication)13- [Passing headers through context](#passing-headers-through-context)14- [Session ID and Request ID tracking](#session-id-and-request-id-tracking)15- [Sessions](#sessions)16- [Image content](#returning-an-image)17- [Audio content](#returning-an-audio)18- [Embedded](#embedded-resources)19- [Logging](#logging)20- [Error handling](#errors)21- [HTTP Streaming](#http-streaming) (with SSE compatibility)22- [HTTPS Support](#https-support) for secure connections23- [Custom HTTP routes](#custom-http-routes) for REST APIs, webhooks, and admin interfaces24- [Edge Runtime Support](#edge-runtime-support) for Cloudflare Workers, Deno Deploy, and more25- [Stateless mode](#stateless-mode) for serverless deployments26- CORS (enabled by default)27- [Progress notifications](#progress)28- [Streaming output](#streaming-output)29- [Typed server events](#typed-server-events)30- [Prompt argument auto-completion](#prompt-argument-auto-completion)31- [Sampling](#requestsampling)32- [Configurable ping behavior](#configurable-ping-behavior)33- [Health-check endpoint](#health-check-endpoint)34- [Roots](#roots-management)35- CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)3637## When to use FastMCP over the official SDK?3839FastMCP is built on top of the official SDK.4041The official SDK provides foundational blocks for building MCPs, but leaves many implementation details to you:4243- [Initiating and configuring](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L664-L744) all the server components44- [Handling of connections](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L760-L850)45- [Handling of tools](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L1303-L1498)46- [Handling of responses](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L989-L1060)47- [Handling of resources](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L1151-L1242)48- Adding [prompts](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L760-L850), [resources](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L960-L962), [resource templates](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L964-L987)49- Embedding [resources](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L1569-L1643), [image](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L51-L111) and [audio](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L113-L173) content blocks5051FastMCP eliminates this complexity by providing an opinionated framework that:5253- Handles all the boilerplate automatically54- Provides simple, intuitive APIs for common tasks55- Includes built-in best practices and error handling56- Lets you focus on your MCP's core functionality5758**When to choose FastMCP:** You want to build MCP servers quickly without dealing with low-level implementation details.5960**When to use the official SDK:** You need maximum control or have specific architectural requirements. In this case, we encourage referencing FastMCP's implementation to avoid common pitfalls.6162## Installation6364```bash65npm install fastmcp66```6768## Quickstart6970> [!NOTE]71>72> There are many real-world examples of using FastMCP in the wild. See the [Showcase](#showcase) for examples.7374```ts75import { FastMCP } from "fastmcp";76import { z } from "zod"; // Or any validation library that supports Standard Schema7778const server = new FastMCP({79 name: "My Server",80 version: "1.0.0",81});8283server.addTool({84 name: "add",85 description: "Add two numbers",86 parameters: z.object({87 a: z.number(),88 b: z.number(),89 }),90 execute: async (args) => {91 return String(args.a + args.b);92 },93});9495server.start({96 transportType: "stdio",97});98```99100_That's it!_ You have a working MCP server.101102You can test the server in terminal with:103104```bash105git clone https://github.com/punkpeye/fastmcp.git106cd fastmcp107108pnpm install109pnpm build110111# Test the addition server example using CLI:112npx fastmcp dev src/examples/addition.ts113# Test the addition server example using MCP Inspector:114npx fastmcp inspect src/examples/addition.ts115```116117If you are looking for a boilerplate repository to build your own MCP server, check out [fastmcp-boilerplate](https://github.com/punkpeye/fastmcp-boilerplate).118119### Remote Server Options120121FastMCP supports multiple transport options for remote communication, allowing an MCP hosted on a remote machine to be accessed over the network.122123#### HTTP Streaming124125[HTTP streaming](https://www.cloudflare.com/learning/video/what-is-http-live-streaming/) provides a more efficient alternative to SSE in environments that support it, with potentially better performance for larger payloads.126127You can run the server with HTTP streaming support:128129```ts130server.start({131 transportType: "httpStream",132 httpStream: {133 port: 8080,134 },135});136```137138This will start the server and listen for HTTP streaming connections on `http://localhost:8080/mcp`.139140> **Note:** You can also customize the endpoint path using the `httpStream.endpoint` option (default is `/mcp`).141142> **Note:** This also starts an SSE server on `http://localhost:8080/sse`.143144You can connect to these servers using the appropriate client transport.145146For HTTP streaming connections:147148```ts149import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";150151const client = new Client(152 {153 name: "example-client",154 version: "1.0.0",155 },156 {157 capabilities: {},158 },159);160161const transport = new StreamableHTTPClientTransport(162 new URL(`http://localhost:8080/mcp`),163);164165await client.connect(transport);166```167168For SSE connections:169170```ts171import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";172173const client = new Client(174 {175 name: "example-client",176 version: "1.0.0",177 },178 {179 capabilities: {},180 },181);182183const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));184185await client.connect(transport);186```187188##### HTTPS Support189190FastMCP supports HTTPS for secure connections by providing SSL certificate options:191192```ts193server.start({194 transportType: "httpStream",195 httpStream: {196 port: 8443,197 sslCert: "./path/to/cert.pem",198 sslKey: "./path/to/key.pem",199 sslCa: "./path/to/ca.pem", // Optional: for client certificate authentication200 },201});202```203204This will start the server with HTTPS on `https://localhost:8443/mcp`.205206**SSL Options:**207208- `sslCert` - Path to SSL certificate file209- `sslKey` - Path to SSL private key file210- `sslCa` - (Optional) Path to CA certificate for mutual TLS authentication211212**For testing**, you can generate self-signed certificates:213214```bash215openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"216```217218**For production**, obtain certificates from a trusted CA like Let's Encrypt.219220See the [https-server example](src/examples/https-server.ts) for a complete demonstration.221222#### Custom HTTP Routes223224FastMCP allows you to add custom HTTP routes alongside MCP endpoints, enabling you to build comprehensive HTTP services that include REST APIs, webhooks, admin interfaces, and more - all within the same server process.225226```ts227// Add REST API endpoints228server.addRoute("GET", "/api/users", async (req, res) => {229 res.json({ users: [] });230});231232// Handle path parameters233server.addRoute("GET", "/api/users/:id", async (req, res) => {234 res.json({235 userId: req.params.id,236 query: req.query, // Access query parameters237 });238});239240// Handle POST requests with body parsing241server.addRoute("POST", "/api/users", async (req, res) => {242 const body = await req.json();243 res.status(201).json({ created: body });244});245246// Serve HTML content247server.addRoute("GET", "/admin", async (req, res) => {248 res.send("<html><body><h1>Admin Panel</h1></body></html>");249});250251// Handle webhooks252server.addRoute("POST", "/webhook/github", async (req, res) => {253 const payload = await req.json();254 const event = req.headers["x-github-event"];255256 // Process webhook...257 res.json({ received: true });258});259```260261Custom routes support:262263- All HTTP methods: GET, POST, PUT, DELETE, PATCH, OPTIONS264- Path parameters (`:param`) and wildcards (`*`)265- Query string parsing266- JSON and text body parsing267- Custom status codes and headers268- Authentication via the same `authenticate` function as MCP269- **Public routes** that bypass authentication270271Routes are matched in the order they are registered, allowing you to define specific routes before catch-all patterns.272273##### Public Routes274275By default, custom routes require authentication (if configured). You can make routes public by adding the `{ public: true }` option:276277```ts278// Public route - no authentication required279server.addRoute(280 "GET",281 "/.well-known/openid-configuration",282 async (req, res) => {283 res.json({284 issuer: "https://example.com",285 authorization_endpoint: "https://example.com/auth",286 token_endpoint: "https://example.com/token",287 });288 },289 { public: true },290);291292// Private route - requires authentication293server.addRoute("GET", "/api/users", async (req, res) => {294 // req.auth contains authenticated user data295 res.json({ users: [] });296});297298// Public static files299server.addRoute(300 "GET",301 "/public/*",302 async (req, res) => {303 // Serve static files without authentication304 res.send(`File: ${req.url}`);305 },306 { public: true },307);308```309310Public routes are perfect for:311312- OAuth discovery endpoints (`.well-known/*`)313- Health checks and status pages314- Static assets and documentation315- Webhook endpoints from external services316- Public APIs that don't require user authentication317318See the [custom-routes example](src/examples/custom-routes.ts) for a complete demonstration.319320#### Edge Runtime Support321322FastMCP supports edge runtimes like Cloudflare Workers, enabling deployment of MCP servers to the edge with minimal latency worldwide.323324##### Choosing Between FastMCP and EdgeFastMCP325326| Use Case | Class | Import |327| ------------------------------- | ------------- | -------------------------------------------- |328| Node.js, Express, Bun | `FastMCP` | `import { FastMCP } from "fastmcp"` |329| Cloudflare Workers, Deno Deploy | `EdgeFastMCP` | `import { EdgeFastMCP } from "fastmcp/edge"` |330331| Feature | FastMCP | EdgeFastMCP |332| -------------------- | ------------------------------ | -------------------------------------- |333| Runtime | Node.js | Edge (V8 isolates) |334| Start method | `server.start({ port })` | `export default server` |335| Transport | stdio, httpStream, SSE | HTTP Streamable only |336| Sessions | Stateful or stateless | Stateless only |337| File system | Yes | No |338| OAuth/Authentication | Built-in `authenticate` option | Use Hono middleware (built-in planned) |339| Custom routes | `server.getApp()` | `server.getApp()` |340341> **Note:** Built-in authentication for EdgeFastMCP is planned for a future release. Both FastMCP and EdgeFastMCP use Hono internally, so there's no technical barrier—EdgeFastMCP was simply written before OAuth was added to FastMCP. PRs are welcome to add an `authenticate` option that accepts web `Request` instead of Node.js `http.IncomingMessage`.342>343> In the meantime, use Hono middleware:344>345> ```ts346> const app = server.getApp();347> app.use("/api/*", async (c, next) => {348> if (c.req.header("authorization") !== "Bearer secret") {349> return c.json({ error: "Unauthorized" }, 401);350> }351> await next();352> });353> ```354355##### Cloudflare Workers356357To deploy FastMCP to Cloudflare Workers, use the `EdgeFastMCP` class from the `/edge` subpath:358359```ts360import { EdgeFastMCP } from "fastmcp/edge";361import { z } from "zod";362363const server = new EdgeFastMCP({364 name: "My Edge Server",365 version: "1.0.0",366 description: "MCP server running on Cloudflare Workers",367});368369// Add tools, resources, prompts as usual370server.addTool({371 name: "greet",372 description: "Greet someone",373 parameters: z.object({374 name: z.string(),375 }),376 execute: async ({ name }) => {377 return `Hello, ${name}! Served from the edge.`;378 },379});380381// Export the server as the default (required for Cloudflare Workers)382export default server;383```384385##### Edge Runtime Differences386387When running on edge runtimes:388389- **Stateless by default**: Each request is handled independently390- **No filesystem access**: Use fetch APIs for external data391- **V8 Isolates**: Fast cold starts and efficient resource usage392- **Global deployment**: Automatic distribution to edge locations393394##### Custom Routes on Edge395396You can access the underlying Hono app to add custom HTTP routes:397398```ts399const app = server.getApp();400401// Add a landing page402app.get("/", (c) => c.html("<h1>Welcome to my MCP server</h1>"));403404// Add REST API endpoints405app.get("/api/status", (c) => c.json({ status: "ok" }));406```407408##### Deployment409410Configure your `wrangler.toml`:411412```toml413name = "my-mcp-server"414main = "src/index.ts"415compatibility_date = "2024-01-01"416```417418Deploy with:419420```bash421wrangler deploy422```423424See the [edge-cloudflare-worker example](src/examples/edge-cloudflare-worker.ts) for a complete demonstration.425426#### Stateless Mode427428FastMCP supports stateless operation for HTTP streaming, where each request is handled independently without maintaining persistent sessions. This is ideal for serverless environments, load-balanced deployments, or when session state isn't required.429430In stateless mode:431432- No sessions are tracked on the server433- Each request creates a temporary session that's discarded after the response434- Reduced memory usage and better scalability435- Perfect for stateless deployment environments436437You can enable stateless mode by adding the `stateless: true` option:438439```ts440server.start({441 transportType: "httpStream",442 httpStream: {443 port: 8080,444 stateless: true,445 },446});447```448449> **Note:** Stateless mode is only available with HTTP streaming transport. Features that depend on persistent sessions (like session-specific state) will not be available in stateless mode.450451You can also enable stateless mode using CLI arguments or environment variables:452453```bash454# Via CLI argument455npx fastmcp dev src/server.ts --transport http-stream --port 8080 --stateless true456457# Via environment variable458FASTMCP_STATELESS=true npx fastmcp dev src/server.ts459```460461The `/ready` health check endpoint will indicate when the server is running in stateless mode:462463```json464{465 "mode": "stateless",466 "ready": 1,467 "status": "ready",468 "total": 1469}470```471472## Core Concepts473474### Tools475476[Tools](https://modelcontextprotocol.io/docs/concepts/tools) in MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions.477478FastMCP uses the [Standard Schema](https://standardschema.dev) specification for defining tool parameters. This allows you to use your preferred schema validation library (like Zod, ArkType, or Valibot) as long as it implements the spec.479480**Zod Example:**481482```typescript483import { z } from "zod";484485server.addTool({486 name: "fetch-zod",487 description: "Fetch the content of a url (using Zod)",488 parameters: z.object({489 url: z.string(),490 }),491 execute: async (args) => {492 return await fetchWebpageContent(args.url);493 },494});495```496497**ArkType Example:**498499```typescript500import { type } from "arktype";501502server.addTool({503 name: "fetch-arktype",504 description: "Fetch the content of a url (using ArkType)",505 parameters: type({506 url: "string",507 }),508 execute: async (args) => {509 return await fetchWebpageContent(args.url);510 },511});512```513514**Valibot Example:**515516Valibot requires the peer dependency @valibot/to-json-schema.517518```typescript519import * as v from "valibot";520521server.addTool({522 name: "fetch-valibot",523 description: "Fetch the content of a url (using Valibot)",524 parameters: v.object({525 url: v.string(),526 }),527 execute: async (args) => {528 return await fetchWebpageContent(args.url);529 },530});531```532533#### Tools Without Parameters534535When creating tools that don't require parameters, you have two options:5365371. Omit the parameters property entirely:538539 ```typescript540 server.addTool({541 name: "sayHello",542 description: "Say hello",543 // No parameters property544 execute: async () => {545 return "Hello, world!";546 },547 });548 ```5495502. Explicitly define empty parameters:551552 ```typescript553 import { z } from "zod";554555 server.addTool({556 name: "sayHello",557 description: "Say hello",558 parameters: z.object({}), // Empty object559 execute: async () => {560 return "Hello, world!";561 },562 });563 ```564565> [!NOTE]566>567> Both approaches are fully compatible with all MCP clients, including Cursor. FastMCP automatically generates the proper schema in both cases.568569#### Tool Authorization570571You can control which tools are available to authenticated users by adding an optional `canAccess` function to a tool's definition. This function receives the authentication context and should return `true` if the user is allowed to access the tool.572573```typescript574server.addTool({575 name: "admin-tool",576 description: "An admin-only tool",577 canAccess: (auth) => auth?.role === "admin",578 execute: async () => "Welcome, admin!",579});580```581582#### Returning a string583584`execute` can return a string:585586```js587server.addTool({588 name: "download",589 description: "Download a file",590 parameters: z.object({591 url: z.string(),592 }),593 execute: async (args) => {594 return "Hello, world!";595 },596});597```598599The latter is equivalent to:600601```js602server.addTool({603 name: "download",604 description: "Download a file",605 parameters: z.object({606 url: z.string(),607 }),608 execute: async (args) => {609 return {610 content: [611 {612 type: "text",613 text: "Hello, world!",614 },615 ],616 };617 },618});619```620621#### Returning a list622623If you want to return a list of messages, you can return an object with a `content` property:624625```js626server.addTool({627 name: "download",628 description: "Download a file",629 parameters: z.object({630 url: z.string(),631 }),632 execute: async (args) => {633 return {634 content: [635 { type: "text", text: "First message" },636 { type: "text", text: "Second message" },637 ],638 };639 },640});641```642643#### Returning an image644645Use the `imageContent` to create a content object for an image:646647```js648import { imageContent } from "fastmcp";649650server.addTool({651 name: "download",652 description: "Download a file",653 parameters: z.object({654 url: z.string(),655 }),656 execute: async (args) => {657 return imageContent({658 url: "https://example.com/image.png",659 });660661 // or...662 // return imageContent({663 // path: "/path/to/image.png",664 // });665666 // or...667 // return imageContent({668 // buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),669 // });670671 // or...672 // return {673 // content: [674 // await imageContent(...)675 // ],676 // };677 },678});679```680681The `imageContent` function takes the following options:682683- `url`: The URL of the image.684- `path`: The path to the image file.685- `buffer`: The image data as a buffer.686687Only one of `url`, `path`, or `buffer` must be specified.688689The above example is equivalent to:690691```js692server.addTool({693 name: "download",694 description: "Download a file",695 parameters: z.object({696 url: z.string(),697 }),698 execute: async (args) => {699 return {700 content: [701 {702 type: "image",703 data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",704 mimeType: "image/png",705 },706 ],707 };708 },709});710```711712#### Configurable Ping Behavior713714FastMCP includes a configurable ping mechanism to maintain connection health. The ping behavior can be customized through server options:715716```ts717const server = new FastMCP({718 name: "My Server",719 version: "1.0.0",720 ping: {721 // Explicitly enable or disable pings (defaults vary by transport)722 enabled: true,723 // Configure ping interval in milliseconds (default: 5000ms)724 intervalMs: 10000,725 // Set log level for ping-related messages (default: 'debug')726 logLevel: "debug",727 },728});729```730731By default, ping behavior is optimized for each transport type:732733- Enabled for SSE and HTTP streaming connections (which benefit from keep-alive)734- Disabled for `stdio` connections (where pings are typically unnecessary)735736This configurable approach helps reduce log verbosity and optimize performance for different usage scenarios.737738### Health-check Endpoint739740When you run FastMCP with the `httpStream` transport you can optionally expose a741simple HTTP endpoint that returns a plain-text response useful for load-balancer742or container orchestration liveness checks.743744Enable (or customise) the endpoint via the `health` key in the server options:745746```ts747const server = new FastMCP({748 name: "My Server",749 version: "1.0.0",750 health: {751 // Enable / disable (default: true)752 enabled: true,753 // Body returned by the endpoint (default: 'ok')754 message: "healthy",755 // Path that should respond (default: '/health')756 path: "/healthz",757 // HTTP status code to return (default: 200)758 status: 200,759 },760});761762await server.start({763 transportType: "httpStream",764 httpStream: { port: 8080 },765});766```767768Now a request to `http://localhost:8080/healthz` will return:769770```771HTTP/1.1 200 OK772content-type: text/plain773774healthy775```776777The endpoint is ignored when the server is started with the `stdio` transport.778779#### Roots Management780781FastMCP supports [Roots](https://modelcontextprotocol.io/docs/concepts/roots) - Feature that allows clients to provide a set of filesystem-like root locations that can be listed and dynamically updated. The Roots feature can be configured or disabled in server options:782783```ts784const server = new FastMCP({785 name: "My Server",786 version: "1.0.0",787 roots: {788 // Set to false to explicitly disable roots support789 enabled: false,790 // By default, roots support is enabled (true)791 },792});793```794795This provides the following benefits:796797- Better compatibility with different clients that may not support Roots798- Reduced error logs when connecting to clients that don't implement roots capability799- More explicit control over MCP server capabilities800- Graceful degradation when roots functionality isn't available801802You can listen for root changes in your server:803804```ts805server.on("connect", (event) => {806 const session = event.session;807808 // Access the current roots809 console.log("Initial roots:", session.roots);810811 // Listen for changes to the roots812 session.on("rootsChanged", (event) => {813 console.log("Roots changed:", event.roots);814 });815});816```817818When a client doesn't support roots or when roots functionality is explicitly disabled, these operations will gracefully handle the situation without throwing errors.819820### Returning an audio821822Use the `audioContent` to create a content object for an audio:823824```js825import { audioContent } from "fastmcp";826827server.addTool({828 name: "download",829 description: "Download a file",830 parameters: z.object({831 url: z.string(),832 }),833 execute: async (args) => {834 return audioContent({835 url: "https://example.com/audio.mp3",836 });837838 // or...839 // return audioContent({840 // path: "/path/to/audio.mp3",841 // });842843 // or...844 // return audioContent({845 // buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),846 // });847848 // or...849 // return {850 // content: [851 // await audioContent(...)852 // ],853 // };854 },855});856```857858The `audioContent` function takes the following options:859860- `url`: The URL of the audio.861- `path`: The path to the audio file.862- `buffer`: The audio data as a buffer.863864Only one of `url`, `path`, or `buffer` must be specified.865866The above example is equivalent to:867868```js869server.addTool({870 name: "download",871 description: "Download a file",872 parameters: z.object({873 url: z.string(),874 }),875 execute: async (args) => {876 return {877 content: [878 {879 type: "audio",880 data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",881 mimeType: "audio/mpeg",882 },883 ],884 };885 },886});887```888889#### Return combination type890891You can combine various types in this way and send them back to AI892893```js894server.addTool({895 name: "download",896 description: "Download a file",897 parameters: z.object({898 url: z.string(),899 }),900 execute: async (args) => {901 return {902 content: [903 {904 type: "text",905 text: "Hello, world!",906 },907 {908 type: "image",909 data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",910 mimeType: "image/png",911 },912 {913 type: "audio",914 data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",915 mimeType: "audio/mpeg",916 },917 ],918 };919 },920921 // or...922 // execute: async (args) => {923 // const imgContent = await imageContent({924 // url: "https://example.com/image.png",925 // });926 // const audContent = await audioContent({927 // url: "https://example.com/audio.mp3",928 // });929 // return {930 // content: [931 // {932 // type: "text",933 // text: "Hello, world!",934 // },935 // imgContent,936 // audContent,937 // ],938 // };939 // },940});941```942943#### Custom Logger944945FastMCP allows you to provide a custom logger implementation to control how the server logs messages. This is useful for integrating with existing logging infrastructure or customizing log formatting.946947```ts948import { FastMCP, Logger } from "fastmcp";949950class CustomLogger implements Logger {951 debug(...args: unknown[]): void {952 console.log("[DEBUG]", new Date().toISOString(), ...args);953 }954955 error(...args: unknown[]): void {956 console.error("[ERROR]", new Date().toISOString(), ...args);957 }958959 info(...args: unknown[]): void {960 console.info("[INFO]", new Date().toISOString(), ...args);961 }962963 log(...args: unknown[]): void {964 console.log("[LOG]", new Date().toISOString(), ...args);965 }966967 warn(...args: unknown[]): void {968 console.warn("[WARN]", new Date().toISOString(), ...args);969 }970}971972const server = new FastMCP({973 name: "My Server",974 version: "1.0.0",975 logger: new CustomLogger(),976});977```978979See `src/examples/custom-logger.ts` for examples with Winston, Pino, and file-based logging.980981#### Logging982983Tools can log messages to the client using the `log` object in the context object:984985```js986server.addTool({987 name: "download",988 description: "Download a file",989 parameters: z.object({990 url: z.string(),991 }),992 execute: async (args, { log }) => {993 log.info("Downloading file...", {994 url,995 });996997 // ...998999 log.info("Downloaded file");10001001 return "done";1002 },1003});1004```10051006The `log` object has the following methods:10071008- `debug(message: string, data?: SerializableValue)`1009- `error(message: string, data?: SerializableValue)`1010- `info(message: string, data?: SerializableValue)`1011- `warn(message: string, data?: SerializableValue)`10121013#### Errors10141015The errors that are meant to be shown to the user should be thrown as `UserError` instances:10161017```js1018import { UserError } from "fastmcp";10191020server.addTool({1021 name: "download",1022 description: "Download a file",1023 parameters: z.object({1024 url: z.string(),1025 }),1026 execute: async (args) => {1027 if (args.url.startsWith("https://example.com")) {1028 throw new UserError("This URL is not allowed");1029 }10301031 return "done";1032 },1033});1034```10351036#### Progress10371038Tools can report progress by calling `reportProgress` in the context object:10391040```js1041server.addTool({1042 name: "download",1043 description: "Download a file",1044 parameters: z.object({1045 url: z.string(),1046 }),1047 execute: async (args, { reportProgress }) => {1048 await reportProgress({1049 progress: 0,1050 total: 100,1051 });10521053 // ...10541055 await reportProgress({1056 progress: 100,1057 total: 100,1058 });10591060 return "done";1061 },1062});1063```10641065#### Streaming Output10661067FastMCP supports streaming partial results from tools while they're still executing, enabling responsive UIs and real-time feedback. This is particularly useful for:10681069- Long-running operations that generate content incrementally1070- Progressive generation of text, images, or other media1071- Operations where users benefit from seeing immediate partial results10721073To enable streaming for a tool, add the `streamingHint` annotation and use the `streamContent` method:10741075```js1076server.addTool({1077 name: "generateText",1078 description: "Generate text incrementally",1079 parameters: z.object({1080 prompt: z.string(),1081 }),1082 annotations: {1083 streamingHint: true, // Signals this tool uses streaming1084 readOnlyHint: true,1085 },1086 execute: async (args, { streamContent }) => {1087 // Send initial content immediately1088 await streamContent({ type: "text", text: "Starting generation...\n" });10891090 // Simulate incremental content generation1091 const words = "The quick brown fox jumps over the lazy dog.".split(" ");1092 for (const word of words) {1093 await streamContent({ type: "text", text: word + " " });1094 await new Promise((resolve) => setTimeout(resolve, 300)); // Simulate delay1095 }10961097 // When using streamContent, you can:1098 // 1. Return void (if all content was streamed)1099 // 2. Return a final result (which will be appended to streamed content)11001101 // Option 1: All content was streamed, so return void1102 return;11031104 // Option 2: Return final content that will be appended1105 // return "Generation complete!";1106 },1107});1108```11091110Streaming works with all content types (text, image, audio) and can be combined with progress reporting:11111112```js1113server.addTool({1114 name: "processData",1115 description: "Process data with streaming updates",1116 parameters: z.object({1117 datasetSize: z.number(),1118 }),1119 annotations: {1120 streamingHint: true,1121 },1122 execute: async (args, { streamContent, reportProgress }) => {1123 const total = args.datasetSize;11241125 for (let i = 0; i < total; i++) {1126 // Report numeric progress1127 await reportProgress({ progress: i, total });11281129 // Stream intermediate results1130 if (i % 10 === 0) {1131 await streamContent({1132 type: "text",1133 text: `Processed ${i} of ${total} items...\n`,1134 });1135 }11361137 await new Promise((resolve) => setTimeout(resolve, 50));1138 }11391140 return "Processing complete!";1141 },1142});1143```11441145#### Tool Annotations11461147As of the MCP Specification (2025-03-26), tools can include annotations that provide richer context and control by adding metadata about a tool's behavior:11481149```typescript1150server.addTool({1151 name: "fetch-content",1152 description: "Fetch content from a URL",1153 parameters: z.object({1154 url: z.string(),1155 }),1156 annotations: {1157 title: "Web Content Fetcher", // Human-readable title for UI display1158 readOnlyHint: true, // Tool doesn't modify its environment1159 openWorldHint: true, // Tool interacts with external entities1160 },1161 execute: async (args) => {1162 return await fetchWebpageContent(args.url);1163 },1164});1165```11661167The available annotations are:11681169| Annotation | Type | Default | Description |1170| :---------------- | :------ | :------ | :----------------------------------------------------------------------------------------------------------------------------------- |1171| `title` | string | - | A human-readable title for the tool, useful for UI display |1172| `readOnlyHint` | boolean | `false` | If true, indicates the tool does not modify its environment |1173| `destructiveHint` | boolean | `true` | If true, the tool may perform destructive updates (only meaningful when `readOnlyHint` is false) |1174| `idempotentHint` | boolean | `false` | If true, calling the tool repeatedly with the same arguments has no additional effect (only meaningful when `readOnlyHint` is false) |1175| `openWorldHint` | boolean | `true` | If true, the tool may interact with an "open world" of external entities |11761177These annotations help clients and LLMs better understand how to use the tools and what to expect when calling them.11781179### Resources11801181[Resources](https://modelcontextprotocol.io/docs/concepts/resources) represent any kind of data that an MCP server wants to make available to clients. This can include:11821183- File contents1184- Screenshots and images1185- Log files1186- And more11871188Each resource is identified by a unique URI and can contain either text or binary data.11891190```ts1191server.addResource({1192 uri: "file:///logs/app.log",1193 name: "Application Logs",1194 mimeType: "text/plain",1195 async load() {1196 return {1197 text: await readLogFile(),1198 };1199 },1200});1201```12021203> [!NOTE]1204>1205> `load` can return multiple resources. This could be used, for example, to return a list of files inside a directory when the directory is read.1206>1207> ```ts1208> async load() {1209> return [1210> {1211> text: "First file content",1212> },1213> {1214> text: "Second file content",1215> },1216> ];1217> }1218> ```12191220You can also return binary contents in `load`:12211222```ts1223async load() {1224 return {1225 blob: 'base64-encoded-data'1226 };1227}1228```12291230### Resource templates12311232You can also define resource templates:12331234```ts1235server.addResourceTemplate({1236 uriTemplate: "file:///logs/{name}.log",1237 name: "Application Logs",1238 mimeType: "text/plain",1239 arguments: [1240 {1241 name: "name",1242 description: "Name of the log",1243 required: true,1244 },1245 ],1246 async load({ name }) {1247 return {1248 text: `Example log content for ${name}`,1249 };1250 },1251});1252```12531254#### Resource template argument auto-completion12551256Provide `complete` functions for resource template arguments to enable automatic completion:12571258```ts1259server.addResourceTemplate({1260 uriTemplate: "file:///logs/{name}.log",1261 name: "Application Logs",1262 mimeType: "text/plain",1263 arguments: [1264 {1265 name: "name",1266 description: "Name of the log",1267 required: true,1268 complete: async (value) => {1269 if (value === "Example") {1270 return {1271 values: ["Example Log"],1272 };1273 }12741275 return {1276 values: [],1277 };1278 },1279 },1280 ],1281 async load({ name }) {1282 return {1283 text: `Example log content for ${name}`,1284 };1285 },1286});1287```12881289### Embedded Resources12901291FastMCP provides a convenient `embedded()` method that simplifies including resources in tool responses. This feature reduces code duplication and makes it easier to reference resources from within tools.12921293#### Basic Usage12941295```js1296server.addTool({1297 name: "get_user_data",1298 description: "Retrieve user information",1299 parameters: z.object({1300 userId: z.string(),1301 }),1302 execute: async (args) => {1303 return {1304 content: [1305 {1306 type: "resource",1307 resource: await server.embedded(`user://profile/${args.userId}`),1308 },1309 ],1310 };1311 },1312});1313```13141315#### Working with Resource Templates13161317The `embedded()` method works seamlessly with resource templates:13181319```js1320// Define a resource template1321server.addResourceTemplate({1322 uriTemplate: "docs://project/{section}",1323 name: "Project Documentation",1324 mimeType: "text/markdown",1325 arguments: [1326 {1327 name: "section",1328 required: true,1329 },1330 ],1331 async load(args) {1332 const docs = {1333 "getting-started": "# Getting Started\n\nWelcome to our project!",1334 "api-reference": "# API Reference\n\nAuthentication is required.",1335 };1336 return {1337 text: docs[args.section] || "Documentation not found",1338 };1339 },1340});13411342// Use embedded resources in a tool1343server.addTool({1344 name: "get_documentation",1345 description: "Retrieve project documentation",1346 parameters: z.object({1347 section: z.enum(["getting-started", "api-reference"]),1348 }),1349 execute: async (args) => {1350 return {1351 content: [1352 {1353 type: "resource",1354 resource: await server.embedded(`docs://project/${args.section}`),1355 },1356 ],1357 };1358 },1359});1360```13611362#### Working with Direct Resources13631364It also works with directly defined resources:13651366```js1367// Define a direct resource1368server.addResource({1369 uri: "system://status",1370 name: "System Status",1371 mimeType: "text/plain",1372 async load() {1373 return {1374 text: "System operational",1375 };1376 },1377});13781379// Use in a tool1380server.addTool({1381 name: "get_system_status",1382 description: "Get current system status",1383 parameters: z.object({}),1384 execute: async () => {1385 return {1386 content: [1387 {1388 type: "resource",1389 resource: await server.embedded("system://status"),1390 },1391 ],1392 };1393 },1394});1395```13961397### Prompts13981399[Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.14001401```ts1402server.addPrompt({1403 name: "git-commit",1404 description: "Generate a Git commit message",1405 arguments: [1406 {1407 name: "changes",1408 description: "Git diff or description of changes",1409 required: true,1410 },1411 ],1412 load: async (args) => {1413 return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;1414 },1415});1416```14171418#### Prompt argument auto-completion14191420Prompts can provide auto-completion for their arguments:14211422```js1423server.addPrompt({1424 name: "countryPoem",1425 description: "Writes a poem about a country",1426 load: async ({ name }) => {1427 return `Hello, ${name}!`;1428 },1429 arguments: [1430 {1431 name: "name",1432 description: "Name of the country",1433 required: true,1434 complete: async (value) => {1435 if (value === "Germ") {1436 return {1437 values: ["Germany"],1438 };1439 }14401441 return {1442 values: [],1443 };1444 },1445 },1446 ],1447});1448```14491450#### Prompt argument auto-completion using `enum`14511452If you provide an `enum` array for an argument, the server will automatically provide completions for the argument.14531454```js1455server.addPrompt({1456 name: "countryPoem",1457 description: "Writes a poem about a country",1458 load: async ({ name }) => {1459 return `Hello, ${name}!`;1460 },1461 arguments: [1462 {1463 name: "name",1464 description: "Name of the country",1465 required: true,1466 enum: ["Germany", "France", "Italy"],1467 },1468 ],1469});1470```14711472### Authentication14731474FastMCP supports OAuth 2.1 authentication with pre-configured providers, allowing you to secure your server with minimal setup.14751476#### OAuth with Pre-configured Providers14771478Use the `auth` option with a provider to enable OAuth authentication:14791480```ts1481import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";14821483const server = new FastMCP({1484 auth: new GoogleProvider({1485 baseUrl: "https://your-server.com",1486 clientId: process.env.GOOGLE_CLIENT_ID!,1487 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,1488 }),1489 name: "My Server",1490 version: "1.0.0",1491});14921493server.addTool({1494 canAccess: requireAuth,1495 description: "Get user profile",1496 execute: async (_args, { session }) => {1497 const { accessToken } = getAuthSession(session);1498 const response = await fetch(1499 "https://www.googleapis.com/oauth2/v2/userinfo",1500 {1501 headers: { Authorization: `Bearer ${accessToken}` },1502 },1503 );1504 return JSON.stringify(await response.json());1505 },1506 name: "get-profile",1507});1508```15091510**Available Providers:**15111512| Provider | Import | Use Case |1513| :--------------- | :-------- | :--------------------- |1514| `GoogleProvider` | `fastmcp` | Google OAuth |1515| `GitHubProvider` | `fastmcp` | GitHub OAuth |1516| `AzureProvider` | `fastmcp` | Azure/Entra ID |1517| `OAuthProvider` | `fastmcp` | Any OAuth 2.0 provider |15181519**Generic OAuth Provider** (for SAP, Auth0, Okta, etc.):15201521```ts1522import { FastMCP, OAuthProvider } from "fastmcp";15231524const server = new FastMCP({1525 auth: new OAuthProvider({1526 authorizationEndpoint: process.env.OAUTH_AUTH_ENDPOINT!,1527 baseUrl: "https://your-server.com",1528 clientId: process.env.OAUTH_CLIENT_ID!,1529 clientSecret: process.env.OAUTH_CLIENT_SECRET!,1530 scopes: ["openid", "profile"],1531 tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT!,1532 }),1533 name: "My Server",1534 version: "1.0.0",1535});1536```15371538#### Tool Authorization15391540Control tool access using the `canAccess` property with built-in helper functions:15411542```ts1543import {1544 requireAuth,1545 requireScopes,1546 requireRole,1547 requireAll,1548 requireAny,1549 getAuthSession,1550} from "fastmcp";15511552// Require any authenticated user1553server.addTool({1554 canAccess: requireAuth,1555 name: "user-tool",1556 // ...1557});15581559// Require specific OAuth scopes1560server.addTool({1561 canAccess: requireScopes("read:user", "write:data"),1562 name: "scoped-tool",1563 // ...1564});15651566// Require specific role1567server.addTool({1568 canAccess: requireRole("admin"),1569 name: "admin-tool",1570 // ...1571});15721573// Combine with AND logic1574server.addTool({1575 canAccess: requireAll(requireAuth, requireRole("admin")),1576 name: "admin-only",1577 // ...1578});15791580// Combine with OR logic1581server.addTool({1582 canAccess: requireAny(requireRole("admin"), requireRole("moderator")),1583 name: "staff-tool",1584 // ...1585});1586```15871588**Custom Authorization:**15891590For custom logic, pass a function directly:15911592```typescript1593server.addTool({1594 name: "custom-auth-tool",1595 canAccess: (auth) =>1596 auth?.role === "admin" && auth?.department === "engineering",1597 execute: async () => "Access granted!",1598});1599```16001601**Extracting Session Data:**16021603Use `getAuthSession` for type-safe access to the OAuth session in your tool execute functions:16041605```typescript1606import { getAuthSession, GoogleSession } from "fastmcp";16071608server.addTool({1609 canAccess: requireAuth,1610 name: "get-profile",1611 execute: async (_args, { session }) => {1612 // Type-safe destructuring (throws if not authenticated)1613 const { accessToken } = getAuthSession(session);16141615 // Or with provider-specific typing:1616 // const { accessToken } = getAuthSession<GoogleSession>(session);16171618 const response = await fetch("https://api.example.com/user", {1619 headers: { Authorization: `Bearer ${accessToken}` },1620 });1621 return JSON.stringify(await response.json());1622 },1623});1624```16251626> **Note:** You can also access `session.accessToken` directly, but you must handle the case where `session` is undefined. The `getAuthSession` helper throws a clear error if the session is not authenticated, making it safer when used with `canAccess: requireAuth`.16271628#### Custom Authentication16291630For non-OAuth scenarios (API keys, custom tokens), use the `authenticate` option:16311632```ts1633const server = new FastMCP({1634 name: "My Server",1635 version: "1.0.0",1636 authenticate: (request) => {1637 const apiKey = request.headers["x-api-key"];16381639 if (apiKey !== "123") {1640 throw new Response(null, {1641 status: 401,1642 statusText: "Unauthorized",1643 });1644 }16451646 return { id: 1, role: "user" };1647 },1648});16491650server.addTool({1651 name: "sayHello",1652 execute: async (args, { session }) => {1653 return `Hello, ${session.id}!`;1654 },1655});1656```16571658#### OAuth Proxy16591660The `auth` option uses FastMCP's built-in **OAuth Proxy** that acts as a secure intermediary between MCP clients and upstream OAuth providers. The proxy handles the complete OAuth 2.1 authorization flow, including Dynamic Client Registration (DCR), PKCE, consent management, and token management with encryption and token swap patterns enabled by default.16611662**Key Features:**16631664- 🔐 **Secure by Default**: Automatic encryption (AES-256-GCM) and token swap pattern1665- 🚀 **Zero Configuration**: Auto-generates keys and handles OAuth flows automatically1666- 🔌 **Pre-configured Providers**: Built-in support for Google, GitHub, and Azure1667- 🎯 **RFC Compliant**: Implements DCR (RFC 7591), PKCE, and OAuth 2.11668- 🔑 **Optional JWKS**: Support for RS256/ES256 token verification (via optional `jose` dependency)16691670**Quick Start:**16711672```ts1673import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";16741675const server = new FastMCP({1676 auth: new GoogleProvider({1677 baseUrl: "https://your-server.com",1678 clientId: process.env.GOOGLE_CLIENT_ID!,1679 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,1680 }),1681 name: "My Server",1682 version: "1.0.0",1683});16841685server.addTool({1686 canAccess: requireAuth,1687 name: "protected-tool",1688 execute: async (_args, { session }) => {1689 const { accessToken } = getAuthSession(session);1690 // Use accessToken to call upstream APIs1691 return "Authenticated!";1692 },1693});1694```16951696**Advanced Configuration:**16971698For more control over OAuth behavior, you can use the `oauth` option directly:16991700```ts1701import { FastMCP } from "fastmcp";1702import { GoogleProvider } from "fastmcp/auth";17031704const authProxy = new GoogleProvider({1705 clientId: process.env.GOOGLE_CLIENT_ID,1706 clientSecret: process.env.GOOGLE_CLIENT_SECRET,1707 baseUrl: "https://your-server.com",1708 scopes: ["openid", "profile", "email"],1709});17101711const server = new FastMCP({1712 name: "My Server",1713 oauth: {1714 enabled: true,1715 authorizationServer: authProxy.getAuthorizationServerMetadata(),1716 proxy: authProxy,1717 },1718});1719```17201721**Documentation:**17221723- [OAuth Proxy Features](docs/oauth-proxy-features.md) - Complete feature list and capabilities1724- [OAuth Proxy Implementation Guide](docs/oauth-proxy-guide.md) - Setup and configuration1725- [Python vs TypeScript Comparison](docs/oauth-python-typescript.md) - Feature comparison17261727#### OAuth Discovery Endpoints17281729FastMCP also supports OAuth discovery endpoints for direct integration with OAuth providers, supporting both **MCP Specification 2025-03-26** and **MCP Specification 2025-06-18**. This provides standard discovery endpoints that comply with RFC 8414 (OAuth 2.0 Authorization Server Metadata) and RFC 9470 (OAuth 2.0 Protected Resource Metadata):17301731```ts1732import { FastMCP, DiscoveryDocumentCache } from "fastmcp";1733import { buildGetJwks } from "get-jwks";1734import fastJwt from "fast-jwt";17351736// Create a cache for discovery documents (reuse across requests)1737const discoveryCache = new DiscoveryDocumentCache({1738 ttl: 3600000, // Cache for 1 hour (default)1739});17401741const server = new FastMCP({1742 name: "My Server",1743 version: "1.0.0",1744 oauth: {1745 enabled: true,1746 authorizationServer: {1747 issuer: "https://auth.example.com",1748 authorizationEndpoint: "https://auth.example.com/oauth/authorize",1749 tokenEndpoint: "https://auth.example.com/oauth/token",1750 jwksUri: "https://auth.example.com/.well-known/jwks.json",1751 responseTypesSupported: ["code"],1752 },1753 protectedResource: {1754 resource: "mcp://my-server",1755 authorizationServers: ["https://auth.example.com"],1756 },1757 },1758 authenticate: async (request) => {1759 const authHeader = request.headers.authorization;17601761 if (!authHeader?.startsWith("Bearer ")) {1762 throw new Response(null, {1763 status: 401,1764 statusText: "Missing or invalid authorization header",1765 });1766 }17671768 const token = authHeader.slice(7); // Remove 'Bearer ' prefix17691770 // Validate OAuth JWT access token using OpenID Connect discovery1771 try {1772 // Fetch and cache the discovery document1773 const discoveryUrl =1774 "https://auth.example.com/.well-known/openid-configuration";1775 // Alternative: Use OAuth authorization server metadata endpoint1776 // const discoveryUrl = 'https://auth.example.com/.well-known/oauth-authorization-server';17771778 const config = (await discoveryCache.get(discoveryUrl)) as {1779 jwks_uri: string;1780 issuer: string;1781 };1782 const jwksUri = config.jwks_uri;1783 const issuer = config.issuer;17841785 // Create JWKS client for token verification using discovered endpoint1786 const getJwks = buildGetJwks({1787 jwksUrl: jwksUri,1788 cache: true,1789 rateLimit: true,1790 });17911792 // Create JWT verifier with JWKS and discovered issuer1793 const verify = fastJwt.createVerifier({1794 key: async (token) => {1795 const { header } = fastJwt.decode(token, { complete: true });1796 const jwk = await getJwks.getJwk({1797 kid: header.kid,1798 alg: header.alg,1799 });1800 return jwk;1801 },1802 algorithms: ["RS256", "ES256"],1803 issuer: issuer,1804 audience: "mcp://my-server",1805 });18061807 // Verify the JWT token1808 const payload = await verify(token);18091810 return {1811 userId: payload.sub,1812 scope: payload.scope,1813 email: payload.email,1814 // Include other claims as needed1815 };1816 } catch (error) {1817 throw new Response(null, {1818 status: 401,1819 statusText: "Invalid OAuth token",1820 });1821 }1822 },1823});1824```18251826This configuration automatically exposes OAuth discovery endpoints:18271828- `/.well-known/oauth-authorization-server` - Authorization server metadata (RFC 8414)1829- `/.well-known/oauth-protected-resource` - Protected resource metadata (RFC 9728)1830- `/.well-known/oauth-protected-resource<endpoint>` - Protected resource metadata at sub-path (MCP 2025-11-25)18311832**Discovery Mechanism (MCP Specification 2025-11-25):**18331834Clients discover protected resource metadata using the following search order:183518361. **WWW-Authenticate header** - Primary method (handled automatically by mcp-proxy)18372. **Sub-path well-known** - `/.well-known/oauth-protected-resource<endpoint>` (e.g., `/.well-known/oauth-protected-resource/mcp`)18383. **Root well-known** - `/.well-known/oauth-protected-resource` (fallback)18391840Both the sub-path and root endpoints return identical metadata, ensuring compatibility with all MCP client implementations.18411842For JWT token validation, you can use libraries like [`get-jwks`](https://github.com/nearform/get-jwks) and [`@fastify/jwt`](https://github.com/fastify/fastify-jwt) for OAuth JWT tokens.18431844#### Passing Headers Through Context18451846If you are exposing your MCP server via HTTP, you may wish to allow clients to supply sensitive keys via headers, which can then be passed along to APIs that your tools interact with, allowing each client to supply their own API keys. This can be done by capturing the HTTP headers in the `authenticate` section and storing them in the session to be referenced by the tools later.18471848```ts1849import { FastMCP } from "fastmcp";1850import { IncomingHttpHeaders } from "http";18511852// Define the session data type1853interface SessionData {1854 headers: IncomingHttpHeaders;1855 [key: string]: unknown; // Add index signature to satisfy Record<string, unknown>1856}18571858// Create a server instance1859const server = new FastMCP({1860 name: "My Server",1861 version: "1.0.0",1862 authenticate: async (request: any): Promise<SessionData> => {1863 // Authentication logic1864 return {1865 headers: request.headers,1866 };1867 },1868});18691870// Tool to display HTTP headers1871server.addTool({1872 name: "headerTool",1873 description: "Reads HTTP headers from the request",1874 execute: async (args: any, context: any) => {1875 const session = context.session as SessionData;1876 const headers = session?.headers ?? {};18771878 const getHeaderString = (header: string | string[] | undefined) =>1879 Array.isArray(header) ? header.join(", ") : (header ?? "N/A");18801881 const userAgent = getHeaderString(headers["user-agent"]);1882 const authorization = getHeaderString(headers["authorization"]);1883 return `User-Agent: ${userAgent}\nAuthorization: ${authorization}\nAll Headers: ${JSON.stringify(headers, null, 2)}`;1884 },1885});18861887// Start the server1888server.start({1889 transportType: "httpStream",1890 httpStream: {1891 port: 8080,1892 },1893});1894```18951896A client that would connect to this may look something like this:18971898```ts1899import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";1900import { Client } from "@modelcontextprotocol/sdk/client/index.js";19011902const transport = new StreamableHTTPClientTransport(1903 new URL(`http://localhost:8080/mcp`),1904 {1905 requestInit: {1906 headers: {1907 Authorization: "Test 123",1908 },1909 },1910 },1911);19121913const client = new Client({1914 name: "example-client",1915 version: "1.0.0",1916});19171918(async () => {1919 await client.connect(transport);19201921 // Call a tool1922 const result = await client.callTool({1923 name: "headerTool",1924 arguments: {1925 arg1: "value",1926 },1927 });19281929 console.log("Tool result:", result);1930})().catch(console.error);1931```19321933What would show up in the console after the client runs is something like this:19341935```1936Tool result: {1937 content: [1938 {1939 type: 'text',1940 text: 'User-Agent: node\n' +1941 'Authorization: Test 123\n' +1942 'All Headers: {\n' +1943 ' "host": "localhost:8080",\n' +1944 ' "connection": "keep-alive",\n' +1945 ' "authorization": "Test 123",\n' +1946 ' "content-type": "application/json",\n' +1947 ' "accept": "application/json, text/event-stream",\n' +1948 ' "accept-language": "*",\n' +1949 ' "sec-fetch-mode": "cors",\n' +1950 ' "user-agent": "node",\n' +1951 ' "accept-encoding": "gzip, deflate",\n' +1952 ' "content-length": "163"\n' +1953 '}'1954 }1955 ]1956}1957```19581959#### Session ID and Request ID Tracking19601961FastMCP automatically exposes session and request IDs to tool handlers through the context parameter. This enables per-session state management and request tracking.19621963**Session ID** (`context.sessionId`):19641965- Available only for HTTP-based transports (HTTP Stream, SSE)1966- Extracted from the `Mcp-Session-Id` header1967- Remains constant across multiple requests from the same client1968- Useful for maintaining per-session state, counters, or user-specific data19691970**Request ID** (`context.requestId`):19711972- Available for all transports when provided by the client1973- Unique for each individual request1974- Useful for request tracing and debugging19751976```ts1977import { FastMCP } from "fastmcp";1978import { z } from "zod";19791980const server = new FastMCP({1981 name: "Session Counter Server",1982 version: "1.0.0",1983});19841985// Per-session counter storage1986const sessionCounters = new Map<string, number>();19871988server.addTool({1989 name: "increment_counter",1990 description: "Increment a per-session counter",1991 parameters: z.object({}),1992 execute: async (args, context) => {1993 if (!context.sessionId) {1994 return "Session ID not available (requires HTTP transport)";1995 }19961997 const counter = sessionCounters.get(context.sessionId) || 0;1998 const newCounter = counter + 1;1999 sessionCounters.set(context.sessionId, newCounter);20002001 return `Counter for session ${context.sessionId}: ${newCounter}`;2002 },2003});20042005server.addTool({2006 name: "show_ids",2007 description: "Display session and request IDs",2008 parameters: z.object({}),2009 execute: async (args, context) => {2010 return `Session ID: ${context.sessionId || "N/A"}2011Request ID: ${context.requestId || "N/A"}`;2012 },2013});20142015server.start({2016 transportType: "httpStream",2017 httpStream: {2018 port: 8080,2019 },2020});2021```20222023**Use Cases:**20242025- **Per-session state management**: Maintain counters, caches, or temporary data unique to each client session2026- **User authentication and authorization**: Track authenticated users across requests2027- **Session-specific resource management**: Allocate and manage resources per session2028- **Multi-tenant implementations**: Isolate data and operations by session2029- **Request tracing**: Track individual requests for debugging and monitoring20302031**Example:**20322033See [`src/examples/session-id-counter.ts`](src/examples/session-id-counter.ts) for a complete example demonstrating session-based counter management.20342035**Notes:**20362037- Session IDs are automatically generated by the MCP transport layer2038- In stateless mode, session IDs are not persisted across requests2039- For stdio transport, `sessionId` will be `undefined` as there's no HTTP session concept20402041### Providing Instructions20422043You can provide instructions to the server using the `instructions` option:20442045```ts2046const server = new FastMCP({2047 name: "My Server",2048 version: "1.0.0",2049 instructions:2050 'Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM\'s understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt.',2051});2052```20532054### Sessions20552056The `session` object is an instance of `FastMCPSession` and it describes active client sessions.20572058```ts2059server.sessions;2060```20612062We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.20632064### Typed server events20652066You can listen to events emitted by the server using the `on` method:20672068```ts2069server.on("connect", (event) => {2070 console.log("Client connected:", event.session);2071});20722073server.on("disconnect", (event) => {2074 console.log("Client disconnected:", event.session);2075});2076```20772078## `FastMCPSession`20792080`FastMCPSession` represents a client session and provides methods to interact with the client.20812082Refer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance.20832084### `requestSampling`20852086`requestSampling` creates a [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) request and returns the response.20872088```ts2089await session.requestSampling({2090 messages: [2091 {2092 role: "user",2093 content: {2094 type: "text",2095 text: "What files are in the current directory?",2096 },2097 },2098 ],2099 systemPrompt: "You are a helpful file system assistant.",2100 includeContext: "thisServer",2101 maxTokens: 100,2102});2103```21042105#### Options21062107`requestSampling` accepts an optional second parameter for request options:21082109```ts2110await session.requestSampling(2111 {2112 messages: [2113 {2114 role: "user",2115 content: {2116 type: "text",2117 text: "What files are in the current directory?",2118 },2119 },2120 ],2121 systemPrompt: "You are a helpful file system assistant.",2122 includeContext: "thisServer",2123 maxTokens: 100,2124 },2125 {2126 // Progress callback - called when progress notifications are received2127 onprogress: (progress) => {2128 console.log(`Progress: ${progress.progress}/${progress.total}`);2129 },21302131 // Abort signal for cancelling the request2132 signal: abortController.signal,21332134 // Request timeout in milliseconds (default: DEFAULT_REQUEST_TIMEOUT_MSEC)2135 timeout: 30000,21362137 // Whether progress notifications reset the timeout (default: false)2138 resetTimeoutOnProgress: true,21392140 // Maximum total timeout regardless of progress (no default)2141 maxTotalTimeout: 60000,2142 },2143);2144```21452146**Options:**21472148- `onprogress?: (progress: Progress) => void` - Callback for progress notifications from the remote end2149- `signal?: AbortSignal` - Abort signal to cancel the request2150- `timeout?: number` - Request timeout in milliseconds2151- `resetTimeoutOnProgress?: boolean` - Whether progress notifications reset the timeout2152- `maxTotalTimeout?: number` - Maximum total timeout regardless of progress notifications21532154### `clientCapabilities`21552156The `clientCapabilities` property contains the client capabilities.21572158```ts2159session.clientCapabilities;2160```21612162### `loggingLevel`21632164The `loggingLevel` property describes the logging level as set by the client.21652166```ts2167session.loggingLevel;2168```21692170### `roots`21712172The `roots` property contains the roots as set by the client.21732174```ts2175session.roots;2176```21772178### `server`21792180The `server` property contains an instance of MCP server that is associated with the session.21812182```ts2183session.server;2184```21852186### Typed session events21872188You can listen to events emitted by the session using the `on` method:21892190```ts2191session.on("rootsChanged", (event) => {2192 console.log("Roots changed:", event.roots);2193});21942195session.on("error", (event) => {2196 console.error("Error:", event.error);2197});2198```21992200## Running Your Server22012202### Test with `mcp-cli`22032204The fastest way to test and debug your server is with `fastmcp dev`:22052206```bash2207npx fastmcp dev server.js2208npx fastmcp dev server.ts2209```22102211This will run your server with [`mcp-cli`](https://github.com/wong2/mcp-cli) for testing and debugging your MCP server in the terminal.22122213### Inspect with `MCP Inspector`22142215Another way is to use the official [`MCP Inspector`](https://modelcontextprotocol.io/docs/tools/inspector) to inspect your server with a Web UI:22162217```bash2218npx fastmcp inspect server.ts2219```22202221## FAQ22222223### How to use with Claude Desktop?22242225Follow the guide https://modelcontextprotocol.io/quickstart/user and add the following configuration:22262227```json2228{2229 "mcpServers": {2230 "my-mcp-server": {2231 "command": "npx",2232 "args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],2233 "env": {2234 "YOUR_ENV_VAR": "value"2235 }2236 }2237 }2238}2239```22402241### How to run FastMCP behind a proxy?22422243Refer to this [issue](https://github.com/punkpeye/fastmcp/issues/25#issuecomment-3004568732) for an example of using FastMCP with `express` and `http-proxy-middleware`.22442245## Showcase22462247> [!NOTE]2248>2249> If you've developed a server using FastMCP, please [submit a PR](https://github.com/punkpeye/fastmcp) to showcase it here!22502251> [!NOTE]2252>2253> If you are looking for a boilerplate repository to build your own MCP server, check out [fastmcp-boilerplate](https://github.com/punkpeye/fastmcp-boilerplate).22542255- [apinetwork/piapi-mcp-server](https://github.com/apinetwork/piapi-mcp-server) - generate media using Midjourney/Flux/Kling/LumaLabs/Udio/Chrip/Trellis2256- [domdomegg/computer-use-mcp](https://github.com/domdomegg/computer-use-mcp) - controls your computer2257- [LiterallyBlah/Dradis-MCP](https://github.com/LiterallyBlah/Dradis-MCP) – manages projects and vulnerabilities in Dradis2258- [Meeting-Baas/meeting-mcp](https://github.com/Meeting-Baas/meeting-mcp) - create meeting bots, search transcripts, and manage recording data2259- [drumnation/unsplash-smart-mcp-server](https://github.com/drumnation/unsplash-smart-mcp-server) – enables AI agents to seamlessly search, recommend, and deliver professional stock photos from Unsplash2260- [ssmanji89/halopsa-workflows-mcp](https://github.com/ssmanji89/halopsa-workflows-mcp) - HaloPSA Workflows integration with AI assistants2261- [aiamblichus/mcp-chat-adapter](https://github.com/aiamblichus/mcp-chat-adapter) – provides a clean interface for LLMs to use chat completion2262- [eyaltoledano/claude-task-master](https://github.com/eyaltoledano/claude-task-master) – advanced AI project/task manager powered by FastMCP2263- [cswkim/discogs-mcp-server](https://github.com/cswkim/discogs-mcp-server) - connects to the Discogs API for interacting with your music collection2264- [Panzer-Jack/feuse-mcp](https://github.com/Panzer-Jack/feuse-mcp) - Frontend Useful MCP Tools - Essential utilities for web developers to automate API integration and code generation2265- [sunra-ai/sunra-clients](https://github.com/sunra-ai/sunra-clients/tree/main/mcp-server) - Sunra.ai is a generative media platform built for developers, providing high-performance AI model inference capabilities.2266- [foxtrottwist/shortcuts-mcp](https://github.com/foxtrottwist/shortcuts-mcp) - connects Claude to macOS Shortcuts for system automation, app integration, and interactive workflows22672268## Acknowledgements22692270- FastMCP is inspired by the [Python implementation](https://github.com/jlowin/fastmcp) by [Jonathan Lowin](https://github.com/jlowin).2271- Parts of codebase were adopted from [LiteMCP](https://github.com/wong2/litemcp).2272- Parts of codebase were adopted from [Model Context protocolでSSEをやってみる](https://dev.classmethod.jp/articles/mcp-sse/).2273
Full transparency — inspect the skill content before installing.