{"id":24283536,"url":"https://github.com/punkpeye/fastmcp","last_synced_at":"2026-08-27T15:46:17.463Z","repository":{"id":269902250,"uuid":"908799323","full_name":"punkpeye/fastmcp","owner":"punkpeye","description":"A TypeScript framework for building MCP servers.","archived":false,"fork":false,"pushed_at":"2026-08-21T04:40:50.000Z","size":1335,"stargazers_count":3252,"open_issues_count":8,"forks_count":303,"subscribers_count":15,"default_branch":"main","last_synced_at":"2026-08-22T07:19:15.788Z","etag":null,"topics":["mcp","sse"],"latest_commit_sha":null,"homepage":"https://glama.ai/mcp/servers","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/punkpeye.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2024-12-27T02:12:31.000Z","updated_at":"2026-08-21T05:50:51.000Z","dependencies_parsed_at":"2026-08-12T13:53:34.851Z","dependency_job_id":null,"html_url":"https://github.com/punkpeye/fastmcp","commit_stats":null,"previous_names":["punkpeye/fastmcp"],"tags_count":192,"template":false,"template_full_name":null,"purl":"pkg:github/punkpeye/fastmcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punkpeye%2Ffastmcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punkpeye%2Ffastmcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punkpeye%2Ffastmcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punkpeye%2Ffastmcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/punkpeye","download_url":"https://codeload.github.com/punkpeye/fastmcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punkpeye%2Ffastmcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36823464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-08-06T04:43:03.162Z","status":"online","status_checked_at":"2026-08-22T02:00:06.114Z","response_time":51,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mcp","sse"],"created_at":"2025-01-16T04:01:39.436Z","updated_at":"2026-08-27T15:46:17.448Z","avatar_url":"https://github.com/punkpeye.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","Frameworks","Developer Tools","MCP Middleware \u0026 Orchestration","Other Tools and Integrations","📚 Projects (1974 total)","Frameworks for Servers","SDKs","フレームワーク","MCP Ecosystem","Table of Contents","🌐 Web Development","🌐 Web Development - Frontend","A01_文本生成_文本对话","框架","MCP Frameworks and libraries","Frameworks (5 servers)","MCP Server 开发","Agent Communication and Protocols","Frameworks \u0026 Libraries","Agent-to-Agent Protocols"],"sub_categories":["📋 Project Management","MCP SDKs","How to Submit","MCP Servers","Community","📂 \u003ca name=\"browser-automation\"\u003e\u003c/a\u003eブラウザ自動化","Core \u0026 Frameworks","Gaming","JavaScript/TypeScript","大语言对话模型及数据","TypeScript","🛠️ \u003ca name=\"other-tools-and-integrations\"\u003e\u003c/a\u003eOther Tools and Integrations","**1. 使用 LLM 构建 MCP 服务器**","Knowledge Management"],"readme":"# FastMCP\n\nA TypeScript framework for building [MCP](https://glama.ai/mcp) servers capable of handling client sessions.\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e FastMCP implements the legacy, handshake-based MCP revisions (`2025-11-25` and earlier). It does not support the current specification, [`2026-07-28`](https://modelcontextprotocol.io/specification/2026-07-28/), which made the protocol stateless — no `initialize` handshake and no `Mcp-Session-Id`. For a framework targeting the current spec, use [ViteMCP](https://github.com/vitemcp/server).\n\n## Features\n\n- Simple Tool, Resource, Prompt definition\n- [Authentication](#authentication)\n- [Passing headers through context](#passing-headers-through-context)\n- [Session ID and Request ID tracking](#session-id-and-request-id-tracking)\n- [Sessions](#sessions)\n- [Image content](#returning-an-image)\n- [Audio content](#returning-an-audio)\n- [Embedded](#embedded-resources)\n- [Logging](#logging)\n- [Error handling](#errors)\n- [HTTP Streaming](#http-streaming) (with SSE compatibility)\n- [HTTPS Support](#https-support) for secure connections\n- [Custom HTTP routes](#custom-http-routes) for REST APIs, webhooks, and admin interfaces\n- [Edge Runtime Support](#edge-runtime-support) for Cloudflare Workers, Deno Deploy, and more\n- [Stateless mode](#stateless-mode) for serverless deployments\n- CORS (enabled by default)\n- [Progress notifications](#progress)\n- [Streaming output](#streaming-output)\n- [Typed server events](#typed-server-events)\n- [Prompt argument auto-completion](#prompt-argument-auto-completion)\n- [Sampling](#requestsampling)\n- [Elicitation](#elicitation)\n- [Configurable ping behavior](#configurable-ping-behavior)\n- [Health-check endpoint](#health-check-endpoint)\n- [Roots](#roots-management)\n- [In-memory transport](#unit-testing-with-an-in-memory-transport) for unit testing without binding a port\n- CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)\n\n## When to use FastMCP over the official SDK?\n\nFastMCP is built on top of the official SDK.\n\nThe official SDK provides foundational blocks for building MCPs, but leaves many implementation details to you:\n\n- [Initiating and configuring](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L664-L744) all the server components\n- [Handling of connections](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L760-L850)\n- [Handling of tools](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L1303-L1498)\n- [Handling of responses](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L989-L1060)\n- [Handling of resources](https://github.com/punkpeye/fastmcp/blob/06c2af7a3d7e3d8c638deac1964ce269ce8e518b/src/FastMCP.ts#L1151-L1242)\n- 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)\n- 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 blocks\n\nFastMCP eliminates this complexity by providing an opinionated framework that:\n\n- Handles all the boilerplate automatically\n- Provides simple, intuitive APIs for common tasks\n- Includes built-in best practices and error handling\n- Lets you focus on your MCP's core functionality\n\n**When to choose FastMCP:** You want to build MCP servers quickly without dealing with low-level implementation details.\n\n**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.\n\n## Installation\n\n```bash\nnpm install fastmcp\n```\n\n## Quickstart\n\n\u003e [!NOTE]\n\u003e\n\u003e There are many real-world examples of using FastMCP in the wild. See the [Showcase](#showcase) for examples.\n\n```ts\nimport { FastMCP } from \"fastmcp\";\nimport { z } from \"zod\"; // Or any validation library that supports Standard Schema\n\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n});\n\nserver.addTool({\n  name: \"add\",\n  description: \"Add two numbers\",\n  parameters: z.object({\n    a: z.number(),\n    b: z.number(),\n  }),\n  execute: async (args) =\u003e {\n    return String(args.a + args.b);\n  },\n});\n\nserver.start({\n  transportType: \"stdio\",\n});\n```\n\n_That's it!_ You have a working MCP server.\n\nYou can test the server in terminal with:\n\n```bash\ngit clone https://github.com/punkpeye/fastmcp.git\ncd fastmcp\n\npnpm install\npnpm build\n\n# Test the addition server example using CLI:\nnpx fastmcp dev src/examples/addition.ts\n# Test the addition server example using MCP Inspector:\nnpx fastmcp inspect src/examples/addition.ts\n```\n\nIf you are looking for a boilerplate repository to build your own MCP server, check out [fastmcp-boilerplate](https://github.com/punkpeye/fastmcp-boilerplate).\n\n### Remote Server Options\n\nFastMCP supports multiple transport options for remote communication, allowing an MCP hosted on a remote machine to be accessed over the network.\n\n#### HTTP Streaming\n\n[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.\n\nYou can run the server with HTTP streaming support:\n\n```ts\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    port: 8080,\n  },\n});\n```\n\nThis will start the server and listen for HTTP streaming connections on `http://localhost:8080/mcp`.\n\n\u003e **Note:** You can also customize the endpoint path using the `httpStream.endpoint` option (default is `/mcp`).\n\n\u003e **Note:** To serve HTTP streaming and built-in OAuth routes under an issuer path, set `httpStream.basePath` (for example, `/issuer1`). This exposes authorization server metadata at `/.well-known/oauth-authorization-server/issuer1` per RFC 8414.\n\n\u003e **Note:** This also starts an SSE server on `http://localhost:8080/sse`.\n\nYou can connect to these servers using the appropriate client transport.\n\nFor HTTP streaming connections:\n\n```ts\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n\nconst client = new Client(\n  {\n    name: \"example-client\",\n    version: \"1.0.0\",\n  },\n  {\n    capabilities: {},\n  },\n);\n\nconst transport = new StreamableHTTPClientTransport(\n  new URL(`http://localhost:8080/mcp`),\n);\n\nawait client.connect(transport);\n```\n\nFor SSE connections:\n\n```ts\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\n\nconst client = new Client(\n  {\n    name: \"example-client\",\n    version: \"1.0.0\",\n  },\n  {\n    capabilities: {},\n  },\n);\n\nconst transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));\n\nawait client.connect(transport);\n```\n\n##### HTTPS Support\n\nFastMCP supports HTTPS for secure connections by providing SSL certificate options:\n\n```ts\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    port: 8443,\n    sslCert: \"./path/to/cert.pem\",\n    sslKey: \"./path/to/key.pem\",\n    sslCa: \"./path/to/ca.pem\", // Optional: for client certificate authentication\n  },\n});\n```\n\nThis will start the server with HTTPS on `https://localhost:8443/mcp`.\n\n**SSL Options:**\n\n- `sslCert` - Path to SSL certificate file\n- `sslKey` - Path to SSL private key file\n- `sslCa` - (Optional) Path to CA certificate for mutual TLS authentication\n\n**For testing**, you can generate self-signed certificates:\n\n```bash\nopenssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj \"/CN=localhost\"\n```\n\n**For production**, obtain certificates from a trusted CA like Let's Encrypt.\n\nSee the [https-server example](src/examples/https-server.ts) for a complete demonstration.\n\n##### CORS Configuration\n\nBy default, FastMCP enables CORS with a standard set of allowed headers. You can customize the CORS behavior by passing a `cors` option:\n\n```ts\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    port: 8080,\n    cors: {\n      origin: \"http://localhost:3000\",\n      allowedHeaders: [\n        \"Content-Type\",\n        \"Authorization\",\n        \"Accept\",\n        \"Mcp-Session-Id\",\n        \"Mcp-Protocol-Version\",\n        \"Last-Event-Id\",\n        \"X-Custom-Header\",\n      ],\n      credentials: true,\n    },\n  },\n});\n```\n\nThe `cors` option accepts:\n\n- `true` (default) - enable CORS with default settings\n- `false` - disable CORS entirely\n- An object with these fields:\n  - `origin` - a string, array of strings, or a function `(origin: string) =\u003e boolean`\n  - `allowedHeaders` - a string or array of strings\n  - `methods` - array of allowed HTTP methods\n  - `exposedHeaders` - array of headers to expose\n  - `credentials` - boolean to allow credentials\n  - `maxAge` - preflight cache duration in seconds\n\nThe `CorsOptions` type is exported from `fastmcp` for convenience.\n\n#### Custom HTTP Routes\n\nFastMCP 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.\n\n```ts\nconst app = server.getApp();\n\n// Add REST API endpoints with Hono's native API\napp.get(\"/api/users\", async (c) =\u003e {\n  return c.json({ users: [] });\n});\n\n// Handle path parameters\napp.get(\"/api/users/:id\", async (c) =\u003e {\n  return c.json({\n    userId: c.req.param(\"id\"),\n    query: c.req.query(), // Access query parameters\n  });\n});\n\n// Handle POST requests with body parsing\napp.post(\"/api/users\", async (c) =\u003e {\n  const body = await c.req.json();\n  return c.json({ created: body }, 201);\n});\n\n// Serve HTML content\napp.get(\"/admin\", async (c) =\u003e {\n  return c.html(\"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eAdmin Panel\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\");\n});\n\n// Handle webhooks\napp.post(\"/webhook/github\", async (c) =\u003e {\n  const payload = await c.req.json();\n  const event = c.req.header(\"x-github-event\");\n\n  // Process webhook...\n  return c.json({ received: true });\n});\n```\n\nCustom routes use the underlying [Hono](https://hono.dev/) app returned by `server.getApp()` and support:\n\n- Hono's HTTP methods: `get`, `post`, `put`, `delete`, `patch`, `options`, and more\n- Path parameters (`:param`) and wildcards (`*`)\n- Query string parsing\n- JSON, text, form, and other body helpers from `c.req`\n- Custom status codes and headers\n- Middleware and route groups through Hono\n\nRoutes are matched in the order they are registered, allowing you to define specific routes before catch-all patterns.\n\n##### Public and Protected Routes\n\nCustom Hono routes are public unless you add your own route middleware or authentication checks. For protected custom routes, put your auth logic in a reusable helper and call it from both FastMCP's `authenticate` option and your Hono route handlers:\n\n```ts\nimport type { IncomingMessage } from \"node:http\";\nimport type { Context } from \"hono\";\nimport { FastMCP } from \"fastmcp\";\n\nasync function authenticateRequest(request: IncomingMessage) {\n  const apiKey = request.headers[\"x-api-key\"];\n  return apiKey === \"123\" ? { userId: \"123\" } : undefined;\n}\n\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  authenticate: authenticateRequest,\n});\n\nconst app = server.getApp();\n\nasync function requireAuth(c: Context) {\n  const auth = await authenticateRequest(c.env.incoming);\n\n  if (!auth) {\n    return c.json({ error: \"Authentication required\" }, 401);\n  }\n\n  return auth;\n}\n\n// Public route - no authentication required\napp.get(\"/.well-known/openid-configuration\", async (c) =\u003e {\n  return c.json({\n    issuer: \"https://example.com\",\n    authorization_endpoint: \"https://example.com/auth\",\n    token_endpoint: \"https://example.com/token\",\n  });\n});\n\n// Private route - requires authentication\napp.get(\"/api/users\", async (c) =\u003e {\n  const auth = await requireAuth(c);\n  if (auth instanceof Response) {\n    return auth;\n  }\n\n  return c.json({ users: [] });\n});\n\n// Public static files\napp.get(\"/public/*\", async (c) =\u003e {\n  return c.text(`File: ${c.req.path}`);\n});\n```\n\nPublic routes are perfect for:\n\n- OAuth discovery endpoints (`.well-known/*`)\n- Health checks and status pages\n- Static assets and documentation\n- Webhook endpoints from external services\n- Public APIs that don't require user authentication\n\nSee the [custom-routes example](src/examples/custom-routes.ts) for a complete demonstration.\n\n#### Edge Runtime Support\n\nFastMCP supports edge runtimes like Cloudflare Workers, enabling deployment of MCP servers to the edge with minimal latency worldwide.\n\n##### Choosing Between FastMCP and EdgeFastMCP\n\n| Use Case                        | Class         | Import                                       |\n| ------------------------------- | ------------- | -------------------------------------------- |\n| Node.js, Express, Bun           | `FastMCP`     | `import { FastMCP } from \"fastmcp\"`          |\n| Cloudflare Workers, Deno Deploy | `EdgeFastMCP` | `import { EdgeFastMCP } from \"fastmcp/edge\"` |\n\n| Feature              | FastMCP                        | EdgeFastMCP                            |\n| -------------------- | ------------------------------ | -------------------------------------- |\n| Runtime              | Node.js                        | Edge (V8 isolates)                     |\n| Start method         | `server.start({ port })`       | `export default server`                |\n| Transport            | stdio, httpStream, SSE         | HTTP Streamable only                   |\n| Sessions             | Stateful or stateless          | Stateless only                         |\n| File system          | Yes                            | No                                     |\n| OAuth/Authentication | Built-in `authenticate` option | Use Hono middleware (built-in planned) |\n| Custom routes        | `server.getApp()`              | `server.getApp()`                      |\n\n\u003e **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`.\n\u003e\n\u003e In the meantime, use Hono middleware:\n\u003e\n\u003e ```ts\n\u003e const app = server.getApp();\n\u003e app.use(\"/api/*\", async (c, next) =\u003e {\n\u003e   if (c.req.header(\"authorization\") !== \"Bearer secret\") {\n\u003e     return c.json({ error: \"Unauthorized\" }, 401);\n\u003e   }\n\u003e   await next();\n\u003e });\n\u003e ```\n\n##### Cloudflare Workers\n\nTo deploy FastMCP to Cloudflare Workers, use the `EdgeFastMCP` class from the `/edge` subpath:\n\n```ts\nimport { EdgeFastMCP } from \"fastmcp/edge\";\nimport { z } from \"zod\";\n\nconst server = new EdgeFastMCP({\n  name: \"My Edge Server\",\n  version: \"1.0.0\",\n  description: \"MCP server running on Cloudflare Workers\",\n});\n\n// Add tools, resources, prompts as usual\nserver.addTool({\n  name: \"greet\",\n  description: \"Greet someone\",\n  parameters: z.object({\n    name: z.string(),\n  }),\n  execute: async ({ name }) =\u003e {\n    return `Hello, ${name}! Served from the edge.`;\n  },\n});\n\n// Export the server as the default (required for Cloudflare Workers)\nexport default server;\n```\n\n##### Edge Runtime Differences\n\nWhen running on edge runtimes:\n\n- **Stateless by default**: Each request is handled independently\n- **No filesystem access**: Use fetch APIs for external data\n- **V8 Isolates**: Fast cold starts and efficient resource usage\n- **Global deployment**: Automatic distribution to edge locations\n\n##### Custom Routes on Edge\n\nYou can access the underlying Hono app to add custom HTTP routes:\n\n```ts\nconst app = server.getApp();\n\n// Add a landing page\napp.get(\"/\", (c) =\u003e c.html(\"\u003ch1\u003eWelcome to my MCP server\u003c/h1\u003e\"));\n\n// Add REST API endpoints\napp.get(\"/api/status\", (c) =\u003e c.json({ status: \"ok\" }));\n```\n\n##### Deployment\n\nConfigure your `wrangler.toml`:\n\n```toml\nname = \"my-mcp-server\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2024-01-01\"\n```\n\nDeploy with:\n\n```bash\nwrangler deploy\n```\n\nSee the [edge-cloudflare-worker example](src/examples/edge-cloudflare-worker.ts) for a complete demonstration.\n\n#### Stateless Mode\n\nFastMCP 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.\n\nIn stateless mode:\n\n- No sessions are tracked on the server\n- Each request creates a temporary session that's discarded after the response\n- Reduced memory usage and better scalability\n- Perfect for stateless deployment environments\n\nYou can enable stateless mode by adding the `stateless: true` option:\n\n```ts\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    port: 8080,\n    stateless: true,\n  },\n});\n```\n\n\u003e **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.\n\nYou can also enable stateless mode using CLI arguments or environment variables:\n\n```bash\n# Via CLI argument\nnpx fastmcp dev src/server.ts --transport http-stream --port 8080 --stateless true\n\n# Via environment variable\nFASTMCP_STATELESS=true npx fastmcp dev src/server.ts\n```\n\nThe `/ready` health check endpoint will indicate when the server is running in stateless mode:\n\n```json\n{\n  \"mode\": \"stateless\",\n  \"ready\": 1,\n  \"status\": \"ready\",\n  \"total\": 1\n}\n```\n\n## Core Concepts\n\n### Tools\n\n[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.\n\nFastMCP 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.\n\n**Zod Example:**\n\n```typescript\nimport { z } from \"zod\";\n\nserver.addTool({\n  name: \"fetch-zod\",\n  description: \"Fetch the content of a url (using Zod)\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return await fetchWebpageContent(args.url);\n  },\n});\n```\n\n**ArkType Example:**\n\n```typescript\nimport { type } from \"arktype\";\n\nserver.addTool({\n  name: \"fetch-arktype\",\n  description: \"Fetch the content of a url (using ArkType)\",\n  parameters: type({\n    url: \"string\",\n  }),\n  execute: async (args) =\u003e {\n    return await fetchWebpageContent(args.url);\n  },\n});\n```\n\n**Valibot Example:**\n\nValibot requires the peer dependency @valibot/to-json-schema.\n\n```typescript\nimport * as v from \"valibot\";\n\nserver.addTool({\n  name: \"fetch-valibot\",\n  description: \"Fetch the content of a url (using Valibot)\",\n  parameters: v.object({\n    url: v.string(),\n  }),\n  execute: async (args) =\u003e {\n    return await fetchWebpageContent(args.url);\n  },\n});\n```\n\n**Plain JSON Schema Example:**\n\nIf you already have a JSON Schema — from an OpenAPI document, a config file, or\nanother server — `jsonSchemaAdapter` wraps it so it can be used directly, with\nno schema library in between.\n\nIt requires the peer dependency `ajv`, which does the validation, plus\n`ajv-formats` if your schema uses `format` keywords such as `email` or `uri`.\nBoth are imported the first time a tool is called, so servers that don't use\nthis pay nothing for it.\n\n```bash\nnpm install ajv ajv-formats\n```\n\n```typescript\nimport { jsonSchemaAdapter } from \"fastmcp\";\n\nserver.addTool({\n  name: \"fetch-json-schema\",\n  description: \"Fetch the content of a url (using plain JSON Schema)\",\n  parameters: jsonSchemaAdapter({\n    type: \"object\",\n    properties: {\n      url: { type: \"string\", format: \"uri\" },\n    },\n    required: [\"url\"],\n  }),\n  execute: async (args) =\u003e {\n    const { url } = args as { url: string };\n    return await fetchWebpageContent(url);\n  },\n});\n```\n\nWorks for `outputSchema` too. Note that FastMCP advertises every tool schema\nwith `additionalProperties: false`, whatever your schema said — the same\ntreatment Zod and Valibot schemas get.\n\nUnlike the schema libraries above, a plain JSON Schema carries no TypeScript\ntypes, so `execute` receives `unknown` arguments. Cast or narrow them yourself.\n\n#### Tools Without Parameters\n\nWhen creating tools that don't require parameters, you have two options:\n\n1. Omit the parameters property entirely:\n\n   ```typescript\n   server.addTool({\n     name: \"sayHello\",\n     description: \"Say hello\",\n     // No parameters property\n     execute: async () =\u003e {\n       return \"Hello, world!\";\n     },\n   });\n   ```\n\n2. Explicitly define empty parameters:\n\n   ```typescript\n   import { z } from \"zod\";\n\n   server.addTool({\n     name: \"sayHello\",\n     description: \"Say hello\",\n     parameters: z.object({}), // Empty object\n     execute: async () =\u003e {\n       return \"Hello, world!\";\n     },\n   });\n   ```\n\n\u003e [!NOTE]\n\u003e\n\u003e Both approaches are fully compatible with all MCP clients, including Cursor. FastMCP automatically generates the proper schema in both cases.\n\n#### Structured Tool Output\n\nTools can declare an `outputSchema` and return structured data. FastMCP exposes that value as MCP `structuredContent`, while also returning a JSON text fallback for clients that only render text content.\n\n```typescript\nserver.addTool({\n  name: \"get-weather\",\n  description: \"Get weather for a city\",\n  parameters: z.object({\n    city: z.string(),\n  }),\n  outputSchema: z.object({\n    temperature: z.number(),\n    humidity: z.number(),\n  }),\n  execute: async ({ city }) =\u003e {\n    const weather = await getWeather(city);\n\n    return {\n      temperature: weather.temperature,\n      humidity: weather.humidity,\n    };\n  },\n});\n```\n\nYou can also return explicit text content and structured content together:\n\n```typescript\nserver.addTool({\n  name: \"get-weather\",\n  description: \"Get weather for a city\",\n  parameters: z.object({\n    city: z.string(),\n  }),\n  outputSchema: z.object({\n    temperature: z.number(),\n    humidity: z.number(),\n  }),\n  execute: async ({ city }) =\u003e {\n    const weather = await getWeather(city);\n\n    return {\n      content: [\n        {\n          type: \"text\",\n          text: `${city}: ${weather.temperature}F`,\n        },\n      ],\n      structuredContent: {\n        temperature: weather.temperature,\n        humidity: weather.humidity,\n      },\n    };\n  },\n});\n```\n\nWhen `outputSchema` is provided, FastMCP validates `structuredContent` before sending the tool result. Invalid structured output is returned to the client as a tool error instead of silently violating the advertised schema.\n\n#### Tool Authorization\n\nYou 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.\n\n```typescript\nserver.addTool({\n  name: \"admin-tool\",\n  description: \"An admin-only tool\",\n  canAccess: (auth) =\u003e auth?.role === \"admin\",\n  execute: async () =\u003e \"Welcome, admin!\",\n});\n```\n\n#### Returning a string\n\n`execute` can return a string:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return \"Hello, world!\";\n  },\n});\n```\n\nThe latter is equivalent to:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        {\n          type: \"text\",\n          text: \"Hello, world!\",\n        },\n      ],\n    };\n  },\n});\n```\n\n#### Returning a list\n\nIf you want to return a list of messages, you can return an object with a `content` property:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        { type: \"text\", text: \"First message\" },\n        { type: \"text\", text: \"Second message\" },\n      ],\n    };\n  },\n});\n```\n\n#### Returning an image\n\nUse the `imageContent` to create a content object for an image:\n\n```js\nimport { imageContent } from \"fastmcp\";\n\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return imageContent({\n      url: \"https://example.com/image.png\",\n    });\n\n    // or...\n    // return imageContent({\n    //   path: \"/path/to/image.png\",\n    // });\n\n    // or...\n    // return imageContent({\n    //   buffer: Buffer.from(\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\", \"base64\"),\n    // });\n\n    // or...\n    // return {\n    //   content: [\n    //     await imageContent(...)\n    //   ],\n    // };\n  },\n});\n```\n\nThe `imageContent` function takes the following options:\n\n- `url`: The URL of the image.\n- `timeoutMs`: Optional timeout for a URL download in milliseconds (defaults to 30 seconds).\n- `path`: The path to the image file.\n- `buffer`: The image data as a buffer.\n\nOnly one of `url`, `path`, or `buffer` must be specified.\n\nThe above example is equivalent to:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        {\n          type: \"image\",\n          data: \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\",\n          mimeType: \"image/png\",\n        },\n      ],\n    };\n  },\n});\n```\n\n#### Configurable Ping Behavior\n\nFastMCP includes a configurable ping mechanism to maintain connection health. The ping behavior can be customized through server options:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  ping: {\n    // Explicitly enable or disable pings (defaults vary by transport)\n    enabled: true,\n    // Configure ping interval in milliseconds (default: 5000ms)\n    intervalMs: 10000,\n    // Set log level for ping-related messages (default: 'debug')\n    logLevel: \"debug\",\n  },\n});\n```\n\nBy default, ping behavior is optimized for each transport type:\n\n- Enabled for SSE and HTTP streaming connections (which benefit from keep-alive)\n- Disabled for `stdio` connections (where pings are typically unnecessary)\n\nThis configurable approach helps reduce log verbosity and optimize performance for different usage scenarios.\n\n#### Keeping Long Tool Calls Alive (`streamKeepalive`)\n\nPings travel on the standalone server-to-client stream, so they never keep an\nindividual tool call's connection open — and in `stateless` mode that stream does\nnot exist at all. A tool that runs for minutes without producing output leaves\nits response connection silent, and an idle-connection timeout in front of the\nserver (AWS ALB defaults to 60 seconds) closes it before the result is written.\n\nThis applies to **both** session modes. A stateful deployment keeps its\nstandalone stream warm with pings while the connection carrying the tool call\ngoes idle and is closed anyway; stateless has no standalone stream to begin\nwith.\n\n`streamKeepalive` writes periodically to the in-flight tool call's own response\nstream, which is the connection that would otherwise be closed. It is opt-in:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  streamKeepalive: {\n    // Opt in; disabled by default\n    enabled: true,\n    // Keep comfortably below the shortest idle timeout on the path\n    intervalMs: 20000,\n  },\n});\n```\n\nEach keepalive is a `notifications/message` (level configurable via `logLevel`,\ndefault `debug`) tagged with the logger `fastmcp-keepalive`, related to the tool\ncall being served. Keepalives start when a tool begins executing and stop when\nthe request stops waiting for it — on success, error, timeout, or client\ncancellation — so idle connections stay quiet. Because the messages are related\nto the request, this works in stateful and `stateless` mode alike, and needs no\nclient support beyond the standard logging notification.\n\nLimits worth knowing:\n\n- It has no effect with `httpStream.enableJsonResponse`, which buffers a single\n  JSON reply instead of streaming, so there is no open stream to write to.\n- It only covers tool calls. Other long-running requests (`resources/read`,\n  `prompts/get`) still write nothing until they finish.\n- On `stdio` there is no proxy to keep alive, so enabling it there only adds\n  notification traffic.\n\n#### Cancelling Long Tool Calls (`context.signal`)\n\nEvery `execute` receives an `AbortSignal` that fires once its result can no\nlonger reach anyone. Forward it to whatever does the real work so the work stops\nwith the call instead of outliving it:\n\n```ts\nserver.addTool({\n  name: \"fetch_report\",\n  parameters: z.object({ url: z.string() }),\n  timeoutMs: 30000,\n  execute: async (args, { signal }) =\u003e {\n    const response = await fetch(args.url, { signal });\n    return await response.text();\n  },\n});\n```\n\nIt aborts on any of three events:\n\n- **The client cancelled the call** — it sent `notifications/cancelled`, which is\n  what the MCP SDK emits when a caller aborts its own request.\n- **The session ended** — the transport closed, or the session was closed\n  explicitly. No cancellation notification is involved here.\n- **`timeoutMs` elapsed** — `signal.reason` is the same `UserError` the caller\n  receives, so a tool can tell a timeout apart from a cancellation.\n\nThe signal is never aborted after a call completes normally, so attaching\ncleanup to it is safe.\n\nLimits worth knowing:\n\n- **Nothing is killed for you.** FastMCP stops waiting for the tool, but the\n  promise `execute` returned keeps running until it settles. A tool that ignores\n  the signal still runs to completion — it just does so with nowhere to report.\n- **An HTTP client that vanishes mid-request is not detected.** The MCP SDK only\n  aborts a request's own signal for an explicit `notifications/cancelled`, and\n  `StreamableHTTPServerTransport` does not treat an abandoned response stream as\n  a session close. A caller that hangs up without terminating its session\n  (`DELETE`) leaves the tool running until it finishes or times out. Set\n  `timeoutMs` on anything expensive rather than relying on disconnect detection.\n- **`load` does not get one.** Resources, resource templates, and prompts\n  receive a smaller context without `signal`.\n\n### Health-check Endpoint\n\nWhen you run FastMCP with the `httpStream` transport you can optionally expose a\nsimple HTTP endpoint that returns a plain-text response useful for load-balancer\nor container orchestration liveness checks.\n\nEnable (or customise) the endpoint via the `health` key in the server options:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  health: {\n    // Enable / disable (default: true)\n    enabled: true,\n    // Body returned by the endpoint (default: 'ok')\n    message: \"healthy\",\n    // Path that should respond (default: '/health')\n    path: \"/healthz\",\n    // HTTP status code to return (default: 200)\n    status: 200,\n  },\n});\n\nawait server.start({\n  transportType: \"httpStream\",\n  httpStream: { port: 8080 },\n});\n```\n\nNow a request to `http://localhost:8080/healthz` will return:\n\n```\nHTTP/1.1 200 OK\ncontent-type: text/plain\n\nhealthy\n```\n\nThe endpoint is ignored when the server is started with the `stdio` transport.\n\n#### Roots Management\n\nFastMCP 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:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  roots: {\n    // Set to false to explicitly disable roots support\n    enabled: false,\n    // By default, roots support is enabled (true)\n  },\n});\n```\n\nThis provides the following benefits:\n\n- Better compatibility with different clients that may not support Roots\n- Reduced error logs when connecting to clients that don't implement roots capability\n- More explicit control over MCP server capabilities\n- Graceful degradation when roots functionality isn't available\n\nYou can listen for root changes in your server:\n\n```ts\nserver.on(\"connect\", (event) =\u003e {\n  const session = event.session;\n\n  // Access the current roots\n  console.log(\"Initial roots:\", session.roots);\n\n  // Listen for changes to the roots\n  session.on(\"rootsChanged\", (event) =\u003e {\n    console.log(\"Roots changed:\", event.roots);\n  });\n});\n```\n\nWhen a client doesn't support roots or when roots functionality is explicitly disabled, these operations will gracefully handle the situation without throwing errors.\n\n### Returning an audio\n\nUse the `audioContent` to create a content object for an audio:\n\n```js\nimport { audioContent } from \"fastmcp\";\n\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return audioContent({\n      url: \"https://example.com/audio.mp3\",\n    });\n\n    // or...\n    // return audioContent({\n    //   path: \"/path/to/audio.mp3\",\n    // });\n\n    // or...\n    // return audioContent({\n    //   buffer: Buffer.from(\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\", \"base64\"),\n    // });\n\n    // or...\n    // return {\n    //   content: [\n    //     await audioContent(...)\n    //   ],\n    // };\n  },\n});\n```\n\nThe `audioContent` function takes the following options:\n\n- `url`: The URL of the audio.\n- `timeoutMs`: Optional timeout for a URL download in milliseconds (defaults to 30 seconds).\n- `path`: The path to the audio file.\n- `buffer`: The audio data as a buffer.\n\nOnly one of `url`, `path`, or `buffer` must be specified.\n\nThe above example is equivalent to:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        {\n          type: \"audio\",\n          data: \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\",\n          mimeType: \"audio/mpeg\",\n        },\n      ],\n    };\n  },\n});\n```\n\n#### Return combination type\n\nYou can combine various types in this way and send them back to AI\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        {\n          type: \"text\",\n          text: \"Hello, world!\",\n        },\n        {\n          type: \"image\",\n          data: \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\",\n          mimeType: \"image/png\",\n        },\n        {\n          type: \"audio\",\n          data: \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\",\n          mimeType: \"audio/mpeg\",\n        },\n      ],\n    };\n  },\n\n  // or...\n  // execute: async (args) =\u003e {\n  //   const imgContent = await imageContent({\n  //     url: \"https://example.com/image.png\",\n  //   });\n  //   const audContent = await audioContent({\n  //     url: \"https://example.com/audio.mp3\",\n  //   });\n  //   return {\n  //     content: [\n  //       {\n  //         type: \"text\",\n  //         text: \"Hello, world!\",\n  //       },\n  //       imgContent,\n  //       audContent,\n  //     ],\n  //   };\n  // },\n});\n```\n\n#### Custom Logger\n\nFastMCP 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.\n\n```ts\nimport { FastMCP, Logger } from \"fastmcp\";\n\nclass CustomLogger implements Logger {\n  debug(...args: unknown[]): void {\n    console.log(\"[DEBUG]\", new Date().toISOString(), ...args);\n  }\n\n  error(...args: unknown[]): void {\n    console.error(\"[ERROR]\", new Date().toISOString(), ...args);\n  }\n\n  info(...args: unknown[]): void {\n    console.info(\"[INFO]\", new Date().toISOString(), ...args);\n  }\n\n  log(...args: unknown[]): void {\n    console.log(\"[LOG]\", new Date().toISOString(), ...args);\n  }\n\n  warn(...args: unknown[]): void {\n    console.warn(\"[WARN]\", new Date().toISOString(), ...args);\n  }\n}\n\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  logger: new CustomLogger(),\n});\n```\n\nSee `src/examples/custom-logger.ts` for examples with Winston, Pino, and file-based logging.\n\n#### Logging\n\nTools can log messages to the client using the `log` object in the context object:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args, { log }) =\u003e {\n    log.info(\"Downloading file...\", {\n      url,\n    });\n\n    // ...\n\n    log.info(\"Downloaded file\");\n\n    return \"done\";\n  },\n});\n```\n\nThe `log` object has the following methods:\n\n- `debug(message: string, data?: SerializableValue)`\n- `error(message: string, data?: SerializableValue)`\n- `info(message: string, data?: SerializableValue)`\n- `warn(message: string, data?: SerializableValue)`\n\n#### Errors\n\nThe errors that are meant to be shown to the user should be thrown as `UserError` instances:\n\n```js\nimport { UserError } from \"fastmcp\";\n\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    if (args.url.startsWith(\"https://example.com\")) {\n      throw new UserError(\"This URL is not allowed\");\n    }\n\n    return \"done\";\n  },\n});\n```\n\n#### Progress\n\nTools can report progress by calling `reportProgress` in the context object:\n\n```js\nserver.addTool({\n  name: \"download\",\n  description: \"Download a file\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  execute: async (args, { reportProgress }) =\u003e {\n    await reportProgress({\n      progress: 0,\n      total: 100,\n    });\n\n    // ...\n\n    await reportProgress({\n      progress: 100,\n      total: 100,\n    });\n\n    return \"done\";\n  },\n});\n```\n\n`reportProgress` accepts an optional human-readable `message` alongside the numeric fields, which clients can display next to the progress indicator:\n\n```js\nawait reportProgress({\n  progress: 40,\n  total: 100,\n  message: \"Downloading chunk 4 of 10…\",\n});\n```\n\nProgress notifications are only emitted when the client opts in by supplying a `progressToken` on the tool call; otherwise `reportProgress` is a no-op. Because `notifications/progress` is part of the MCP specification (the `message` field since revision 2025-03-26), this is the portable way to send incremental updates during a long-running tool call — see [Streaming Output](#streaming-output) below for the difference.\n\n#### Streaming Output\n\nFastMCP can stream partial results from tools while they're still executing, enabling responsive UIs and real-time feedback. This is particularly useful for:\n\n- Long-running operations that generate content incrementally\n- Progressive generation of text, images, or other media\n- Operations where users benefit from seeing immediate partial results\n\n\u003e [!IMPORTANT]\n\u003e `streamContent` is a **FastMCP extension, not part of the MCP specification**. It emits a `notifications/tool/streamContent` notification, which the MCP specification does not define — as of revision `2025-11-25` there is no standard mechanism for streaming tool output ([SEP-2998](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2998) is the in-progress proposal to add one).\n\u003e\n\u003e Clients discard notifications they have no handler registered for, silently and without error. A client only sees streamed content if it registers a handler for the method (or sets a `fallbackNotificationHandler`), and **no client is known to render it as tool output** — MCP Inspector, for example, logs it in its notifications pane via a fallback handler, but the tool result itself still shows only what `execute` returned. Streaming is therefore mainly useful when you also control the client — see [Consuming streamed content](#consuming-streamed-content) below. If you need incremental updates that work on any client, use [`reportProgress`](#progress) with a `message` instead.\n\nTo stream from a tool, use the `streamContent` method:\n\n```js\nserver.addTool({\n  name: \"generateText\",\n  description: \"Generate text incrementally\",\n  parameters: z.object({\n    prompt: z.string(),\n  }),\n  annotations: {\n    streamingHint: true, // Advisory only; see below\n    readOnlyHint: true,\n  },\n  execute: async (args, { streamContent }) =\u003e {\n    // Send initial content immediately\n    await streamContent({ type: \"text\", text: \"Starting generation...\\n\" });\n\n    // Simulate incremental content generation\n    const words = \"The quick brown fox jumps over the lazy dog.\".split(\" \");\n    for (const word of words) {\n      await streamContent({ type: \"text\", text: word + \" \" });\n      await new Promise((resolve) =\u003e setTimeout(resolve, 300)); // Simulate delay\n    }\n\n    // Always return a final result. Returning nothing sends an empty tool\n    // result, so clients that ignore the streamed notifications see no output\n    // at all.\n    return \"The quick brown fox jumps over the lazy dog.\";\n  },\n});\n```\n\n\u003e [!WARNING]\n\u003e Returning `undefined` from `execute` produces a tool result with empty `content`. If you stream everything and return nothing, the tool call resolves to an empty result with no indication that anything was lost — including on clients that do log the notification. Return the complete result as well, and treat streamed content purely as a progressive-rendering enhancement.\n\nThe `streamingHint` annotation is advisory metadata. It is forwarded verbatim to clients in `tools/list`, but it does not enable or gate `streamContent`, and FastMCP itself never reads it. No client is known to act on it today, though [SEP-2998](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2998) proposes standardizing the same annotation name.\n\n##### Consuming streamed content\n\nA client sees these notifications only if it registers a handler for the method (or sets a `fallbackNotificationHandler`):\n\n```ts\nimport { z } from \"zod\";\n\nconst StreamContentNotificationSchema = z.object({\n  method: z.literal(\"notifications/tool/streamContent\"),\n  params: z.object({\n    content: z.array(z.any()),\n    toolName: z.string(),\n  }),\n});\n\nclient.setNotificationHandler(\n  StreamContentNotificationSchema,\n  (notification) =\u003e {\n    const { content, toolName } = notification.params;\n    // Render the partial content however you like.\n  },\n);\n```\n\nNote that notifications carry only `toolName`, not a request or progress token, so concurrent calls to the same tool on one session cannot be told apart.\n\nStreaming works with all content types (text, image, audio) and can be combined with progress reporting:\n\n```js\nserver.addTool({\n  name: \"processData\",\n  description: \"Process data with streaming updates\",\n  parameters: z.object({\n    datasetSize: z.number(),\n  }),\n  annotations: {\n    streamingHint: true,\n  },\n  execute: async (args, { streamContent, reportProgress }) =\u003e {\n    const total = args.datasetSize;\n\n    for (let i = 0; i \u003c total; i++) {\n      // Standard progress notification: reaches every spec-compliant client\n      await reportProgress({\n        progress: i,\n        total,\n        message: `Processed ${i} of ${total} items`,\n      });\n\n      // Richer partial content: only reaches clients that opt in\n      if (i % 10 === 0) {\n        await streamContent({\n          type: \"text\",\n          text: `Processed ${i} of ${total} items...\\n`,\n        });\n      }\n\n      await new Promise((resolve) =\u003e setTimeout(resolve, 50));\n    }\n\n    return \"Processing complete!\";\n  },\n});\n```\n\n#### Elicitation\n\nTools can request additional information from the user mid-execution via [elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation), using the `elicit` method in the context object. The client must advertise the matching `elicitation` capability mode — `elicitation: { form: {} }` for form requests (the default) and/or `elicitation: { url: {} }` for url requests.\n\n```js\nserver.addTool({\n  name: \"delete-file\",\n  description: \"Delete a file\",\n  parameters: z.object({\n    path: z.string(),\n  }),\n  execute: async (args, { elicit }) =\u003e {\n    const response = await elicit({\n      message: `Are you sure you want to delete ${args.path}?`,\n      requestedSchema: {\n        type: \"object\",\n        properties: {\n          confirmed: {\n            type: \"boolean\",\n          },\n        },\n        required: [\"confirmed\"],\n      },\n    });\n\n    if (response.action !== \"accept\" || !response.content?.confirmed) {\n      return \"Deletion cancelled.\";\n    }\n\n    // ...\n\n    return `Deleted ${args.path}`;\n  },\n});\n```\n\nThe response `action` is `\"accept\"`, `\"decline\"`, or `\"cancel\"`; on accept, `content` holds the user's answers matching `requestedSchema`. Elicitation is also available outside of tools via [`session.requestElicitation`](#requestelicitation).\n\n#### Tool Annotations\n\nAs 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:\n\n```typescript\nserver.addTool({\n  name: \"fetch-content\",\n  description: \"Fetch content from a URL\",\n  parameters: z.object({\n    url: z.string(),\n  }),\n  annotations: {\n    title: \"Web Content Fetcher\", // Human-readable title for UI display\n    readOnlyHint: true, // Tool doesn't modify its environment\n    openWorldHint: true, // Tool interacts with external entities\n  },\n  execute: async (args) =\u003e {\n    return await fetchWebpageContent(args.url);\n  },\n});\n```\n\nThe available annotations are:\n\n| Annotation        | Type    | Default | Description                                                                                                                          |\n| :---------------- | :------ | :------ | :----------------------------------------------------------------------------------------------------------------------------------- |\n| `title`           | string  | -       | A human-readable title for the tool, useful for UI display                                                                           |\n| `readOnlyHint`    | boolean | `false` | If true, indicates the tool does not modify its environment                                                                          |\n| `destructiveHint` | boolean | `true`  | If true, the tool may perform destructive updates (only meaningful when `readOnlyHint` is false)                                     |\n| `idempotentHint`  | boolean | `false` | If true, calling the tool repeatedly with the same arguments has no additional effect (only meaningful when `readOnlyHint` is false) |\n| `openWorldHint`   | boolean | `true`  | If true, the tool may interact with an \"open world\" of external entities                                                             |\n\nThese annotations help clients and LLMs better understand how to use the tools and what to expect when calling them.\n\n### Resources\n\n[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:\n\n- File contents\n- Screenshots and images\n- Log files\n- And more\n\nEach resource is identified by a unique URI and can contain either text or binary data.\n\n```ts\nserver.addResource({\n  uri: \"file:///logs/app.log\",\n  name: \"Application Logs\",\n  mimeType: \"text/plain\",\n  async load() {\n    return {\n      text: await readLogFile(),\n    };\n  },\n});\n```\n\n\u003e [!NOTE]\n\u003e\n\u003e `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.\n\u003e\n\u003e ```ts\n\u003e async load() {\n\u003e   return [\n\u003e     {\n\u003e       text: \"First file content\",\n\u003e     },\n\u003e     {\n\u003e       text: \"Second file content\",\n\u003e     },\n\u003e   ];\n\u003e }\n\u003e ```\n\nYou can also return binary contents in `load`:\n\n```ts\nasync load() {\n  return {\n    blob: 'base64-encoded-data'\n  };\n}\n```\n\n`load` also receives `auth` (the value returned by your `authenticate` function, if any) and a `context` object as its second and third arguments. `context` mirrors the `client`, `log`, `session`, and `sessionId` fields available to `tool.execute` (see [Session ID and Request ID Tracking](#session-id-and-request-id-tracking)); `reportProgress` and `streamContent` are not included since they are tied to a tool call's progress token:\n\n```ts\nserver.addResource({\n  uri: \"file:///logs/app.log\",\n  name: \"Application Logs\",\n  mimeType: \"text/plain\",\n  async load(auth, context) {\n    context.log.info(\"loading application logs\", { requestedBy: auth?.userId });\n\n    return {\n      text: await readLogFile(),\n    };\n  },\n});\n```\n\n#### Subscribing to resource updates\n\nClients can subscribe to a resource with the MCP [`resources/subscribe`](https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions) method to be notified whenever its contents change. FastMCP advertises the `subscribe` capability automatically for any server that exposes resources, tracks each client's subscriptions, and lets you emit an update with `sendResourceUpdated`:\n\n```ts\nserver.addResource({\n  uri: \"file:///logs/app.log\",\n  name: \"Application Logs\",\n  mimeType: \"text/plain\",\n  async load() {\n    return { text: await readLogFile() };\n  },\n});\n\n// Whenever the underlying data changes, notify subscribed clients:\nawait server.sendResourceUpdated(\"file:///logs/app.log\");\n```\n\n`sendResourceUpdated` only notifies clients that have subscribed to the given URI, so it is safe to call whenever your data changes. FastMCP also advertises the `listChanged` capability for resources and prompts and emits `notifications/resources/list_changed` / `notifications/prompts/list_changed` automatically when you add or remove resources, resource templates, or prompts at runtime.\n\n### Resource templates\n\nYou can also define resource templates:\n\n```ts\nserver.addResourceTemplate({\n  uriTemplate: \"file:///logs/{name}.log\",\n  name: \"Application Logs\",\n  mimeType: \"text/plain\",\n  arguments: [\n    {\n      name: \"name\",\n      description: \"Name of the log\",\n      required: true,\n    },\n  ],\n  async load({ name }) {\n    return {\n      text: `Example log content for ${name}`,\n    };\n  },\n});\n```\n\nLike plain resources, `load` also receives `auth` and `context` as its second and third arguments (see [Resources](#resources)).\n\n#### Resource template argument auto-completion\n\nProvide `complete` functions for resource template arguments to enable automatic completion:\n\n```ts\nserver.addResourceTemplate({\n  uriTemplate: \"file:///logs/{name}.log\",\n  name: \"Application Logs\",\n  mimeType: \"text/plain\",\n  arguments: [\n    {\n      name: \"name\",\n      description: \"Name of the log\",\n      required: true,\n      complete: async (value) =\u003e {\n        if (value === \"Example\") {\n          return {\n            values: [\"Example Log\"],\n          };\n        }\n\n        return {\n          values: [],\n        };\n      },\n    },\n  ],\n  async load({ name }) {\n    return {\n      text: `Example log content for ${name}`,\n    };\n  },\n});\n```\n\n### Embedded Resources\n\nFastMCP 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.\n\n#### Basic Usage\n\n```js\nserver.addTool({\n  name: \"get_user_data\",\n  description: \"Retrieve user information\",\n  parameters: z.object({\n    userId: z.string(),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        {\n          type: \"resource\",\n          resource: await server.embedded(`user://profile/${args.userId}`),\n        },\n      ],\n    };\n  },\n});\n```\n\n#### Working with Resource Templates\n\nThe `embedded()` method works seamlessly with resource templates:\n\n```js\n// Define a resource template\nserver.addResourceTemplate({\n  uriTemplate: \"docs://project/{section}\",\n  name: \"Project Documentation\",\n  mimeType: \"text/markdown\",\n  arguments: [\n    {\n      name: \"section\",\n      required: true,\n    },\n  ],\n  async load(args) {\n    const docs = {\n      \"getting-started\": \"# Getting Started\\n\\nWelcome to our project!\",\n      \"api-reference\": \"# API Reference\\n\\nAuthentication is required.\",\n    };\n    return {\n      text: docs[args.section] || \"Documentation not found\",\n    };\n  },\n});\n\n// Use embedded resources in a tool\nserver.addTool({\n  name: \"get_documentation\",\n  description: \"Retrieve project documentation\",\n  parameters: z.object({\n    section: z.enum([\"getting-started\", \"api-reference\"]),\n  }),\n  execute: async (args) =\u003e {\n    return {\n      content: [\n        {\n          type: \"resource\",\n          resource: await server.embedded(`docs://project/${args.section}`),\n        },\n      ],\n    };\n  },\n});\n```\n\n#### Working with Direct Resources\n\nIt also works with directly defined resources:\n\n```js\n// Define a direct resource\nserver.addResource({\n  uri: \"system://status\",\n  name: \"System Status\",\n  mimeType: \"text/plain\",\n  async load() {\n    return {\n      text: \"System operational\",\n    };\n  },\n});\n\n// Use in a tool\nserver.addTool({\n  name: \"get_system_status\",\n  description: \"Get current system status\",\n  parameters: z.object({}),\n  execute: async () =\u003e {\n    return {\n      content: [\n        {\n          type: \"resource\",\n          resource: await server.embedded(\"system://status\"),\n        },\n      ],\n    };\n  },\n});\n```\n\n### Prompts\n\n[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.\n\n```ts\nserver.addPrompt({\n  name: \"git-commit\",\n  description: \"Generate a Git commit message\",\n  arguments: [\n    {\n      name: \"changes\",\n      description: \"Git diff or description of changes\",\n      required: true,\n    },\n  ],\n  load: async (args) =\u003e {\n    return `Generate a concise but descriptive commit message for these changes:\\n\\n${args.changes}`;\n  },\n});\n```\n\nLike resources, `load` also receives `auth` and `context` as its second and third arguments (see [Resources](#resources)):\n\n```ts\nserver.addPrompt({\n  name: \"git-commit\",\n  description: \"Generate a Git commit message\",\n  arguments: [\n    {\n      name: \"changes\",\n      description: \"Git diff or description of changes\",\n      required: true,\n    },\n  ],\n  load: async (args, auth, context) =\u003e {\n    context.log.debug(\"generating git commit prompt\", { user: auth?.userId });\n\n    return `Generate a concise but descriptive commit message for these changes:\\n\\n${args.changes}`;\n  },\n});\n```\n\n#### Prompt argument auto-completion\n\nPrompts can provide auto-completion for their arguments:\n\n```js\nserver.addPrompt({\n  name: \"countryPoem\",\n  description: \"Writes a poem about a country\",\n  load: async ({ name }) =\u003e {\n    return `Hello, ${name}!`;\n  },\n  arguments: [\n    {\n      name: \"name\",\n      description: \"Name of the country\",\n      required: true,\n      complete: async (value) =\u003e {\n        if (value === \"Germ\") {\n          return {\n            values: [\"Germany\"],\n          };\n        }\n\n        return {\n          values: [],\n        };\n      },\n    },\n  ],\n});\n```\n\n#### Prompt argument auto-completion using `enum`\n\nIf you provide an `enum` array for an argument, the server will automatically provide completions for the argument.\n\n```js\nserver.addPrompt({\n  name: \"countryPoem\",\n  description: \"Writes a poem about a country\",\n  load: async ({ name }) =\u003e {\n    return `Hello, ${name}!`;\n  },\n  arguments: [\n    {\n      name: \"name\",\n      description: \"Name of the country\",\n      required: true,\n      enum: [\"Germany\", \"France\", \"Italy\"],\n    },\n  ],\n});\n```\n\n### Authentication\n\nFastMCP supports OAuth 2.1 authentication with pre-configured providers, allowing you to secure your server with minimal setup.\n\n#### OAuth with Pre-configured Providers\n\nUse the `auth` option with a provider to enable OAuth authentication:\n\n```ts\nimport { FastMCP, getAuthSession, GoogleProvider, requireAuth } from \"fastmcp\";\n\nconst server = new FastMCP({\n  auth: new GoogleProvider({\n    baseUrl: \"https://your-server.com\",\n    clientId: process.env.GOOGLE_CLIENT_ID!,\n    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n  }),\n  name: \"My Server\",\n  version: \"1.0.0\",\n});\n\nserver.addTool({\n  canAccess: requireAuth,\n  description: \"Get user profile\",\n  execute: async (_args, { session }) =\u003e {\n    const { accessToken } = getAuthSession(session);\n    const response = await fetch(\n      \"https://www.googleapis.com/oauth2/v2/userinfo\",\n      {\n        headers: { Authorization: `Bearer ${accessToken}` },\n      },\n    );\n    return JSON.stringify(await response.json());\n  },\n  name: \"get-profile\",\n});\n```\n\n**Available Providers:**\n\n| Provider         | Import    | Use Case               |\n| :--------------- | :-------- | :--------------------- |\n| `GoogleProvider` | `fastmcp` | Google OAuth           |\n| `GitHubProvider` | `fastmcp` | GitHub OAuth           |\n| `AzureProvider`  | `fastmcp` | Azure/Entra ID         |\n| `OAuthProvider`  | `fastmcp` | Any OAuth 2.0 provider |\n\n**Generic OAuth Provider** (for SAP, Auth0, Okta, etc.):\n\n```ts\nimport { FastMCP, OAuthProvider } from \"fastmcp\";\n\nconst server = new FastMCP({\n  auth: new OAuthProvider({\n    authorizationEndpoint: process.env.OAUTH_AUTH_ENDPOINT!,\n    baseUrl: \"https://your-server.com\",\n    clientId: process.env.OAUTH_CLIENT_ID!,\n    clientSecret: process.env.OAUTH_CLIENT_SECRET!,\n    scopes: [\"openid\", \"profile\"],\n    tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT!,\n  }),\n  name: \"My Server\",\n  version: \"1.0.0\",\n});\n```\n\n#### Tool Authorization\n\nControl tool access using the `canAccess` property with built-in helper functions:\n\n```ts\nimport {\n  requireAuth,\n  requireScopes,\n  requireRole,\n  requireAll,\n  requireAny,\n  getAuthSession,\n} from \"fastmcp\";\n\n// Require any authenticated user\nserver.addTool({\n  canAccess: requireAuth,\n  name: \"user-tool\",\n  // ...\n});\n\n// Require specific OAuth scopes\nserver.addTool({\n  canAccess: requireScopes(\"read:user\", \"write:data\"),\n  name: \"scoped-tool\",\n  // ...\n});\n\n// Require specific role\nserver.addTool({\n  canAccess: requireRole(\"admin\"),\n  name: \"admin-tool\",\n  // ...\n});\n\n// Combine with AND logic\nserver.addTool({\n  canAccess: requireAll(requireAuth, requireRole(\"admin\")),\n  name: \"admin-only\",\n  // ...\n});\n\n// Combine with OR logic\nserver.addTool({\n  canAccess: requireAny(requireRole(\"admin\"), requireRole(\"moderator\")),\n  name: \"staff-tool\",\n  // ...\n});\n```\n\n**Custom Authorization:**\n\nFor custom logic, pass a function directly:\n\n```typescript\nserver.addTool({\n  name: \"custom-auth-tool\",\n  canAccess: (auth) =\u003e\n    auth?.role === \"admin\" \u0026\u0026 auth?.department === \"engineering\",\n  execute: async () =\u003e \"Access granted!\",\n});\n```\n\n**Extracting Session Data:**\n\nUse `getAuthSession` for type-safe access to the OAuth session in your tool execute functions:\n\n```typescript\nimport { getAuthSession, GoogleSession } from \"fastmcp\";\n\nserver.addTool({\n  canAccess: requireAuth,\n  name: \"get-profile\",\n  execute: async (_args, { session }) =\u003e {\n    // Type-safe destructuring (throws if not authenticated)\n    const { accessToken } = getAuthSession(session);\n\n    // Or with provider-specific typing:\n    // const { accessToken } = getAuthSession\u003cGoogleSession\u003e(session);\n\n    const response = await fetch(\"https://api.example.com/user\", {\n      headers: { Authorization: `Bearer ${accessToken}` },\n    });\n    return JSON.stringify(await response.json());\n  },\n});\n```\n\n\u003e **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`.\n\n#### Custom Authentication\n\nFor non-OAuth scenarios (API keys, custom tokens), use the `authenticate` option:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  authenticate: (request) =\u003e {\n    const apiKey = request.headers[\"x-api-key\"];\n\n    if (apiKey !== \"123\") {\n      throw new Response(null, {\n        status: 401,\n        statusText: \"Unauthorized\",\n      });\n    }\n\n    return { id: 1, role: \"user\" };\n  },\n});\n\nserver.addTool({\n  name: \"sayHello\",\n  execute: async (args, { session }) =\u003e {\n    return `Hello, ${session.id}!`;\n  },\n});\n```\n\n#### OAuth Proxy\n\nThe `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.\n\n**Key Features:**\n\n- 🔐 **Secure by Default**: Automatic encryption (AES-256-GCM) and token swap pattern\n- 🚀 **Zero Configuration**: Auto-generates keys and handles OAuth flows automatically\n- 🔌 **Pre-configured Providers**: Built-in support for Google, GitHub, and Azure\n- 🎯 **RFC Compliant**: Implements DCR (RFC 7591), PKCE, and OAuth 2.1\n- 🔑 **Optional JWKS**: Support for RS256/ES256 token verification (via optional `jose` dependency)\n\n**Quick Start:**\n\n```ts\nimport { FastMCP, getAuthSession, GoogleProvider, requireAuth } from \"fastmcp\";\n\nconst server = new FastMCP({\n  auth: new GoogleProvider({\n    baseUrl: \"https://your-server.com\",\n    clientId: process.env.GOOGLE_CLIENT_ID!,\n    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n  }),\n  name: \"My Server\",\n  version: \"1.0.0\",\n});\n\nserver.addTool({\n  canAccess: requireAuth,\n  name: \"protected-tool\",\n  execute: async (_args, { session }) =\u003e {\n    const { accessToken } = getAuthSession(session);\n    // Use accessToken to call upstream APIs\n    return \"Authenticated!\";\n  },\n});\n```\n\n**Advanced Configuration:**\n\nFor more control over OAuth behavior, you can use the `oauth` option directly:\n\n```ts\nimport { FastMCP } from \"fastmcp\";\nimport { GoogleProvider } from \"fastmcp/auth\";\n\nconst authProvider = new GoogleProvider({\n  baseUrl: \"https://your-server.com\",\n  clientId: process.env.GOOGLE_CLIENT_ID!,\n  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,\n  scopes: [\"openid\", \"profile\", \"email\"],\n});\n\nconst server = new FastMCP({\n  name: \"My Server\",\n  oauth: {\n    authorizationServer: authProvider\n      .getProxy()\n      .getAuthorizationServerMetadata(),\n    enabled: true,\n    proxy: authProvider.getProxy(),\n  },\n  version: \"1.0.0\",\n});\n```\n\n**Documentation:**\n\n- [OAuth Proxy Features](docs/oauth-proxy-features.md) - Complete feature list and capabilities\n- [OAuth Proxy Implementation Guide](docs/oauth-proxy-guide.md) - Setup and configuration\n- [Python vs TypeScript Comparison](docs/oauth-python-typescript.md) - Feature comparison\n\n#### OAuth Discovery Endpoints\n\nFastMCP 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):\n\n```ts\nimport { FastMCP } from \"fastmcp\";\nimport buildGetJwks from \"get-jwks\";\nimport fastJwt, { type DecodedJwt } from \"fast-jwt\";\n\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  oauth: {\n    enabled: true,\n    authorizationServer: {\n      issuer: \"https://auth.example.com\",\n      authorizationEndpoint: \"https://auth.example.com/oauth/authorize\",\n      tokenEndpoint: \"https://auth.example.com/oauth/token\",\n      jwksUri: \"https://auth.example.com/.well-known/jwks.json\",\n      responseTypesSupported: [\"code\"],\n    },\n    protectedResource: {\n      resource: \"mcp://my-server\",\n      authorizationServers: [\"https://auth.example.com\"],\n    },\n  },\n  authenticate: async (request) =\u003e {\n    const authHeader = request.headers.authorization;\n\n    if (!authHeader?.startsWith(\"Bearer \")) {\n      throw new Response(null, {\n        status: 401,\n        statusText: \"Missing or invalid authorization header\",\n      });\n    }\n\n    const token = authHeader.slice(7); // Remove 'Bearer ' prefix\n\n    // Validate OAuth JWT access token using OpenID Connect discovery\n    try {\n      // Create JWKS client for token verification\n      const getJwks = buildGetJwks();\n\n      // Create JWT verifier\n      const verify = fastJwt.createVerifier({\n        async key({ header }: DecodedJwt) {\n          const publicKey = await getJwks.getPublicKey({\n            kid: header.kid,\n            alg: header.alg,\n            domain: \"https://auth.example.com\",\n          });\n          return publicKey;\n        },\n        algorithms: [\"RS256\"],\n      });\n\n      // Verify the JWT token\n      const payload = await verify(token);\n\n      return {\n        userId: payload.sub,\n        scope: payload.scope,\n        email: payload.email,\n        // Include other claims as needed\n      };\n    } catch (error) {\n      throw new Response(null, {\n        status: 401,\n        statusText: \"Invalid OAuth token\",\n      });\n    }\n  },\n});\n```\n\nIf your MCP server is published below an issuer path, configure the HTTP\nstream base path as well:\n\n```ts\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    basePath: \"/issuer1\",\n    endpoint: \"/mcp\",\n    port: 8080,\n  },\n});\n```\n\nWith this configuration, FastMCP serves the issuer-path authorization server\nmetadata at `/.well-known/oauth-authorization-server/issuer1`, while protected\nresource metadata remains available for the MCP endpoint at\n`/.well-known/oauth-protected-resource/issuer1/mcp`.\n\nThis configuration automatically exposes OAuth discovery endpoints:\n\n- `/.well-known/oauth-authorization-server` - Authorization server metadata (RFC 8414)\n- `/.well-known/oauth-authorization-server\u003cbasePath\u003e` - Authorization server metadata when `httpStream.basePath` is set (RFC 8414 Section 3)\n- `/.well-known/oauth-protected-resource` - Protected resource metadata (RFC 9728)\n- `/.well-known/oauth-protected-resource\u003cendpoint\u003e` - Protected resource metadata at sub-path (MCP 2025-11-25)\n\n**Discovery Mechanism (MCP Specification 2025-11-25):**\n\nClients discover protected resource metadata using the following search order:\n\n1. **WWW-Authenticate header** - Primary method (handled automatically by mcp-proxy)\n2. **Sub-path well-known** - `/.well-known/oauth-protected-resource\u003cendpoint\u003e` (e.g., `/.well-known/oauth-protected-resource/mcp`)\n3. **Root well-known** - `/.well-known/oauth-protected-resource` (fallback)\n\nBoth the sub-path and root endpoints return identical metadata, ensuring compatibility with all MCP client implementations.\n\nFor JWT token validation, you can use libraries like [`get-jwks`](https://github.com/nearform/get-jwks) and [`fast-jwt`](https://github.com/nearform/fast-jwt) for OAuth JWT tokens.\n\n#### Passing Headers Through Context\n\nIf 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.\n\n```ts\nimport { FastMCP } from \"fastmcp\";\nimport { IncomingHttpHeaders } from \"http\";\n\n// Define the session data type\ninterface SessionData {\n  headers: IncomingHttpHeaders;\n  [key: string]: unknown; // Add index signature to satisfy Record\u003cstring, unknown\u003e\n}\n\n// Create a server instance\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  authenticate: async (request: any): Promise\u003cSessionData\u003e =\u003e {\n    // Authentication logic\n    return {\n      headers: request.headers,\n    };\n  },\n});\n\n// Tool to display HTTP headers\nserver.addTool({\n  name: \"headerTool\",\n  description: \"Reads HTTP headers from the request\",\n  execute: async (args: any, context: any) =\u003e {\n    const session = context.session as SessionData;\n    const headers = session?.headers ?? {};\n\n    const getHeaderString = (header: string | string[] | undefined) =\u003e\n      Array.isArray(header) ? header.join(\", \") : (header ?? \"N/A\");\n\n    const userAgent = getHeaderString(headers[\"user-agent\"]);\n    const authorization = getHeaderString(headers[\"authorization\"]);\n    return `User-Agent: ${userAgent}\\nAuthorization: ${authorization}\\nAll Headers: ${JSON.stringify(headers, null, 2)}`;\n  },\n});\n\n// Start the server\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    port: 8080,\n  },\n});\n```\n\nA client that would connect to this may look something like this:\n\n```ts\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\n\nconst transport = new StreamableHTTPClientTransport(\n  new URL(`http://localhost:8080/mcp`),\n  {\n    requestInit: {\n      headers: {\n        Authorization: \"Test 123\",\n      },\n    },\n  },\n);\n\nconst client = new Client({\n  name: \"example-client\",\n  version: \"1.0.0\",\n});\n\n(async () =\u003e {\n  await client.connect(transport);\n\n  // Call a tool\n  const result = await client.callTool({\n    name: \"headerTool\",\n    arguments: {\n      arg1: \"value\",\n    },\n  });\n\n  console.log(\"Tool result:\", result);\n})().catch(console.error);\n```\n\nWhat would show up in the console after the client runs is something like this:\n\n```\nTool result: {\n  content: [\n    {\n      type: 'text',\n      text: 'User-Agent: node\\n' +\n        'Authorization: Test 123\\n' +\n        'All Headers: {\\n' +\n        '  \"host\": \"localhost:8080\",\\n' +\n        '  \"connection\": \"keep-alive\",\\n' +\n        '  \"authorization\": \"Test 123\",\\n' +\n        '  \"content-type\": \"application/json\",\\n' +\n        '  \"accept\": \"application/json, text/event-stream\",\\n' +\n        '  \"accept-language\": \"*\",\\n' +\n        '  \"sec-fetch-mode\": \"cors\",\\n' +\n        '  \"user-agent\": \"node\",\\n' +\n        '  \"accept-encoding\": \"gzip, deflate\",\\n' +\n        '  \"content-length\": \"163\"\\n' +\n        '}'\n    }\n  ]\n}\n```\n\n#### Session ID and Request ID Tracking\n\nFastMCP automatically exposes session and request IDs to tool handlers through the context parameter. This enables per-session state management and request tracking.\n\n**Session ID** (`context.sessionId`):\n\n- Available only for HTTP-based transports (HTTP Stream, SSE)\n- Extracted from the `Mcp-Session-Id` header\n- Remains constant across multiple requests from the same client\n- Useful for maintaining per-session state, counters, or user-specific data\n\n**Request ID** (`context.requestId`):\n\n- Available for all transports when provided by the client\n- Unique for each individual request\n- Useful for request tracing and debugging\n\n```ts\nimport { FastMCP } from \"fastmcp\";\nimport { z } from \"zod\";\n\nconst server = new FastMCP({\n  name: \"Session Counter Server\",\n  version: \"1.0.0\",\n});\n\n// Per-session counter storage\nconst sessionCounters = new Map\u003cstring, number\u003e();\n\nserver.addTool({\n  name: \"increment_counter\",\n  description: \"Increment a per-session counter\",\n  parameters: z.object({}),\n  execute: async (args, context) =\u003e {\n    if (!context.sessionId) {\n      return \"Session ID not available (requires HTTP transport)\";\n    }\n\n    const counter = sessionCounters.get(context.sessionId) || 0;\n    const newCounter = counter + 1;\n    sessionCounters.set(context.sessionId, newCounter);\n\n    return `Counter for session ${context.sessionId}: ${newCounter}`;\n  },\n});\n\nserver.addTool({\n  name: \"show_ids\",\n  description: \"Display session and request IDs\",\n  parameters: z.object({}),\n  execute: async (args, context) =\u003e {\n    return `Session ID: ${context.sessionId || \"N/A\"}\nRequest ID: ${context.requestId || \"N/A\"}`;\n  },\n});\n\nserver.start({\n  transportType: \"httpStream\",\n  httpStream: {\n    port: 8080,\n  },\n});\n```\n\n**Use Cases:**\n\n- **Per-session state management**: Maintain counters, caches, or temporary data unique to each client session\n- **User authentication and authorization**: Track authenticated users across requests\n- **Session-specific resource management**: Allocate and manage resources per session\n- **Multi-tenant implementations**: Isolate data and operations by session\n- **Request tracing**: Track individual requests for debugging and monitoring\n\n**Example:**\n\nSee [`src/examples/session-id-counter.ts`](src/examples/session-id-counter.ts) for a complete example demonstrating session-based counter management.\n\n**Notes:**\n\n- Session IDs are automatically generated by the MCP transport layer\n- In stateless mode, session IDs are not persisted across requests\n- For stdio transport, `sessionId` will be `undefined` as there's no HTTP session concept\n\n### Providing Instructions\n\nYou can provide instructions to the server using the `instructions` option:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  instructions:\n    '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.',\n});\n```\n\n### Server icons and metadata\n\nYou can advertise icons and related metadata for your server. Clients that support icons can show them in their UI:\n\n```ts\nconst server = new FastMCP({\n  name: \"My Server\",\n  version: \"1.0.0\",\n  websiteUrl: \"https://example.com\",\n  icons: [\n    {\n      src: \"https://example.com/icon.png\",\n      mimeType: \"image/png\",\n      sizes: [\"48x48\"],\n    },\n  ],\n});\n```\n\nAn optional `title` is also passed through to MCP `initialize` (`serverInfo`), for clients that prefer a display name over the server `name`.\n\n### Sessions\n\nThe `session` object is an instance of `FastMCPSession` and it describes active client sessions.\n\n```ts\nserver.sessions;\n```\n\nWe allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.\n\n### Typed server events\n\nYou can listen to events emitted by the server using the `on` method:\n\n```ts\nserver.on(\"connect\", (event) =\u003e {\n  console.log(\"Client connected:\", event.session);\n});\n\nserver.on(\"disconnect\", (event) =\u003e {\n  console.log(\"Client disconnected:\", event.session);\n});\n```\n\n## `FastMCPSession`\n\n`FastMCPSession` represents a client session and provides methods to interact with the client.\n\nRefer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance.\n\n### `requestElicitation`\n\n`requestElicitation` creates an [elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation) request to collect additional information from the user via the client and returns the response. The client must advertise the matching `elicitation` capability mode — `elicitation: { form: {} }` for form requests (the default) and/or `elicitation: { url: {} }` for url requests.\n\n```ts\nawait session.requestElicitation({\n  message: \"What is your name?\",\n  requestedSchema: {\n    type: \"object\",\n    properties: {\n      name: { type: \"string\" },\n    },\n    required: [\"name\"],\n  },\n});\n```\n\nInside a tool, prefer the `elicit` method from the [context object](#elicitation).\n\n### `requestSampling`\n\n`requestSampling` creates a [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) request and returns the response.\n\n```ts\nawait session.requestSampling({\n  messages: [\n    {\n      role: \"user\",\n      content: {\n        type: \"text\",\n        text: \"What files are in the current directory?\",\n      },\n    },\n  ],\n  systemPrompt: \"You are a helpful file system assistant.\",\n  includeContext: \"thisServer\",\n  maxTokens: 100,\n});\n```\n\n#### Options\n\n`requestSampling` accepts an optional second parameter for request options:\n\n```ts\nawait session.requestSampling(\n  {\n    messages: [\n      {\n        role: \"user\",\n        content: {\n          type: \"text\",\n          text: \"What files are in the current directory?\",\n        },\n      },\n    ],\n    systemPrompt: \"You are a helpful file system assistant.\",\n    includeContext: \"thisServer\",\n    maxTokens: 100,\n  },\n  {\n    // Progress callback - called when progress notifications are received\n    onprogress: (progress) =\u003e {\n      console.log(`Progress: ${progress.progress}/${progress.total}`);\n    },\n\n    // Abort signal for cancelling the request\n    signal: abortController.signal,\n\n    // Request timeout in milliseconds (default: DEFAULT_REQUEST_TIMEOUT_MSEC)\n    timeout: 30000,\n\n    // Whether progress notifications reset the timeout (default: false)\n    resetTimeoutOnProgress: true,\n\n    // Maximum total timeout regardless of progress (no default)\n    maxTotalTimeout: 60000,\n  },\n);\n```\n\n**Options:**\n\n- `onprogress?: (progress: Progress) =\u003e void` - Callback for progress notifications from the remote end\n- `signal?: AbortSignal` - Abort signal to cancel the request\n- `timeout?: number` - Request timeout in milliseconds\n- `resetTimeoutOnProgress?: boolean` - Whether progress notifications reset the timeout\n- `maxTotalTimeout?: number` - Maximum total timeout regardless of progress notifications\n\n### `clientCapabilities`\n\nThe `clientCapabilities` property contains the client capabilities.\n\n```ts\nsession.clientCapabilities;\n```\n\n### `loggingLevel`\n\nThe `loggingLevel` property describes the logging level as set by the client.\n\n```ts\nsession.loggingLevel;\n```\n\n### `roots`\n\nThe `roots` property contains the roots as set by the client.\n\n```ts\nsession.roots;\n```\n\n### `server`\n\nThe `server` property contains an instance of MCP server that is associated with the session.\n\n```ts\nsession.server;\n```\n\n### Typed session events\n\nYou can listen to events emitted by the session using the `on` method:\n\n```ts\nsession.on(\"rootsChanged\", (event) =\u003e {\n  console.log(\"Roots changed:\", event.roots);\n});\n\nsession.on(\"error\", (event) =\u003e {\n  console.error(\"Error:\", event.error);\n});\n```\n\n## Running Your Server\n\n### Unit testing with an in-memory transport\n\n`server.connect(transport)` attaches the server to a transport you construct yourself, instead of letting `start()` create one. Paired with the SDK's `InMemoryTransport`, this lets you drive a server in-process — no port to bind, no subprocess to spawn — which is usually what you want for testing a `stdio` server:\n\n```ts\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\n\nasync function createTestClient(server: FastMCP) {\n  const [clientTransport, serverTransport] =\n    InMemoryTransport.createLinkedPair();\n\n  const client = new Client({ name: \"test-client\", version: \"0.0.0\" });\n\n  const [session] = await Promise.all([\n    server.connect(serverTransport),\n    client.connect(clientTransport),\n  ]);\n\n  return { client, session };\n}\n\ntest(\"adds two numbers\", async () =\u003e {\n  const { client } = await createTestClient(server);\n\n  expect(\n    await client.callTool({ arguments: { a: 2, b: 3 }, name: \"add\" }),\n  ).toEqual({\n    content: [{ text: \"5\", type: \"text\" }],\n  });\n\n  await client.close();\n});\n```\n\nThe session is built from the tools, resources and prompts registered on the instance, exactly as `start()` builds it, so your tests exercise the same wiring the real server uses — including `canAccess` filtering and the `connect`/`disconnect` events.\n\nPass session auth as the second argument, equivalent to what your `authenticate` function would return:\n\n```ts\nawait server.connect(serverTransport, { id: 7, role: \"admin\" });\n```\n\n`connect` returns the [`FastMCPSession`](#fastmcpsession), so you can assert on `session.clientCapabilities`, `session.roots`, and the rest. The transport's lifecycle belongs to you: `stop()` does not close transports passed to `connect`, so close the client (and the session, if you need `disconnect` to fire) when the test finishes.\n\n### Test with `mcp-cli`\n\nThe fastest way to test and debug your server is with `fastmcp dev`:\n\n```bash\nnpx fastmcp dev server.js\nnpx fastmcp dev server.ts\n```\n\nThis will run your server with [`mcp-cli`](https://github.com/wong2/mcp-cli) for testing and debugging your MCP server in the terminal.\n\nTo call a tool non-interactively (for example, in scripts or automated tests), pass `--tool` and optional JSON `--args`:\n\n```bash\nnpx fastmcp dev server.ts --tool add --args '{\"a\":1,\"b\":2}'\n```\n\nThis prints the tool result as JSON and exits, instead of opening the interactive inspector. `--watch` has no effect in this mode, since the server is started for a single call.\n\n### Inspect with `MCP Inspector`\n\nAnother way is to use the official [`MCP Inspector`](https://modelcontextprotocol.io/docs/tools/inspector) to inspect your server with a Web UI:\n\n```bash\nnpx fastmcp inspect server.ts\n```\n\n## FAQ\n\n### How to use with Claude Desktop?\n\nFollow the guide https://modelcontextprotocol.io/quickstart/user and add the following configuration:\n\n```json\n{\n  \"mcpServers\": {\n    \"my-mcp-server\": {\n      \"command\": \"npx\",\n      \"args\": [\"tsx\", \"/PATH/TO/YOUR_PROJECT/src/index.ts\"],\n      \"env\": {\n        \"YOUR_ENV_VAR\": \"value\"\n      }\n    }\n  }\n}\n```\n\n### How to run FastMCP behind a proxy?\n\nRefer to this [issue](https://github.com/punkpeye/fastmcp/issues/25#issuecomment-3004568732) for an example of using FastMCP with `express` and `http-proxy-middleware`.\n\n## Showcase\n\n\u003e [!NOTE]\n\u003e\n\u003e If you've developed a server using FastMCP, please [submit a PR](https://github.com/punkpeye/fastmcp) to showcase it here!\n\n\u003e [!NOTE]\n\u003e\n\u003e If you are looking for a boilerplate repository to build your own MCP server, check out [fastmcp-boilerplate](https://github.com/punkpeye/fastmcp-boilerplate).\n\n- [apinetwork/piapi-mcp-server](https://github.com/apinetwork/piapi-mcp-server) - generate media using Midjourney/Flux/Kling/LumaLabs/Udio/Chrip/Trellis\n- [domdomegg/computer-use-mcp](https://github.com/domdomegg/computer-use-mcp) - controls your computer\n- [LiterallyBlah/Dradis-MCP](https://github.com/LiterallyBlah/Dradis-MCP) – manages projects and vulnerabilities in Dradis\n- [Meeting-Baas/meeting-mcp](https://github.com/Meeting-Baas/meeting-mcp) - create meeting bots, search transcripts, and manage recording data\n- [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 Unsplash\n- [ssmanji89/halopsa-workflows-mcp](https://github.com/ssmanji89/halopsa-workflows-mcp) - HaloPSA Workflows integration with AI assistants\n- [aiamblichus/mcp-chat-adapter](https://github.com/aiamblichus/mcp-chat-adapter) – provides a clean interface for LLMs to use chat completion\n- [eyaltoledano/claude-task-master](https://github.com/eyaltoledano/claude-task-master) – advanced AI project/task manager powered by FastMCP\n- [cswkim/discogs-mcp-server](https://github.com/cswkim/discogs-mcp-server) - connects to the Discogs API for interacting with your music collection\n- [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 generation\n- [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.\n- [foxtrottwist/shortcuts-mcp](https://github.com/foxtrottwist/shortcuts-mcp) - connects Claude to macOS Shortcuts for system automation, app integration, and interactive workflows\n\n## Acknowledgements\n\n- FastMCP is inspired by the [Python implementation](https://github.com/jlowin/fastmcp) by [Jonathan Lowin](https://github.com/jlowin).\n- Parts of codebase were adopted from [LiteMCP](https://github.com/wong2/litemcp).\n- Parts of codebase were adopted from [Model Context protocolでSSEをやってみる](https://dev.classmethod.jp/articles/mcp-sse/).\n\nThis project is tested with BrowserStack.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpunkpeye%2Ffastmcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpunkpeye%2Ffastmcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpunkpeye%2Ffastmcp/lists"}