FastMCP

by punkpeye

Not rated
GitHub

About

A TypeScript framework for building MCP servers with client session handling.

Details

Author
punkpeye
Categories
Developer Tools, API

Setup

Install FastMCP in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/punkpeye/fastmcp

Follow the installation instructions in the repository README, then restart your MCP client.

When to use FastMCP over the official SDK?

FastMCP is built on top of the official SDK.

The official SDK provides foundational blocks for building MCPs, but leaves many implementation details to you:

- ](#inspect-with-mcp-inspector)Initiating and configuringall the server components
-
Handling of connections
-
Handling of tools
-
Handling of responses
-
Handling of resources
- Adding
prompts,resources,resource templates
- Embedding
resources,imageandaudiocontent blocks

FastMCP eliminates this complexity by providing an opinionated framework that:

- Handles all the boilerplate automatically
- Provides simple, intuitive APIs for common tasks
- Includes built-in best practices and error handling
- Lets you focus on your MCP's core functionality

When to choose FastMCP:You want to build MCP servers quickly without dealing with low-level implementation details.

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.

There are many real-world examples of using FastMCP in the wild. See theShowcasefor examples.

import { FastMCP } from "fastmcp"; import { z } from "zod"; // Or any validation library that supports Standard Schema const server = new FastMCP({ name: "My Server", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); server.start({ transportType: "stdio", });

That's it!You have a working MCP server.

You can test the server in terminal with:

git clone https://github.com/punkpeye/fastmcp.git cd fastmcp pnpm install pnpm build # Test the addition server example using CLI: npx fastmcp dev src/examples/addition.ts # Test the addition server example using MCP Inspector: npx fastmcp inspect src/examples/addition.ts

If you are looking for a boilerplate repository to build your own MCP server, check outfastmcp-boilerplate.

FastMCP supports multiple transport options for remote communication, allowing an MCP hosted on a remote machine to be accessed over the network.

HTTP streamingprovides a more efficient alternative to SSE in environments that support it, with potentially better performance for larger payloads.

You can run the server with HTTP streaming support:

server.start({ transportType: "httpStream", httpStream: { port: 8080, }, });

This will start the server and listen for HTTP streaming connections onhttp://localhost:8080/mcp.

Note:You can also customize the endpoint path using thehttpStream.endpointoption (default is/mcp).

Note:To serve HTTP streaming and built-in OAuth routes under an issuer path, sethttpStream.basePath(for example,/issuer1). This exposes authorization server metadata at/.well-known/oauth-authorization-server/issuer1per RFC 8414.

Note:This also starts an SSE server onhttp://localhost:8080/sse.

You can connect to these servers using the appropriate client transport.

import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new StreamableHTTPClientTransport( new URL(http://localhost:8080/mcp), ); await client.connect(transport);
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new SSEClientTransport(new URL(http://localhost:8080/sse)); await client.connect(transport);

FastMCP supports HTTPS for secure connections by providing SSL certificate options:

server.start({ transportType: "httpStream", httpStream: { port: 8443, sslCert: "./path/to/cert.pem", sslKey: "./path/to/key.pem", sslCa: "./path/to/ca.pem", // Optional: for client certificate authentication }, });

This will start the server with HTTPS onhttps://localhost:8443/mcp.

- sslCert- Path to SSL certificate file
- sslKey- Path to SSL private key file
- sslCa- (Optional) Path to CA certificate for mutual TLS authentication

For testing, you can generate self-signed certificates:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"

For production, obtain certificates from a trusted CA like Let's Encrypt.

See thehttps-server examplefor a complete demonstration.

By default, FastMCP enables CORS with a standard set of allowed headers. You can customize the CORS behavior by passing acorsoption:

server.start({ transportType: "httpStream", httpStream: { port: 8080, cors: { origin: "http://localhost:3000", allowedHeaders: [ "Content-Type", "Authorization", "Accept", "Mcp-Session-Id", "Mcp-Protocol-Version", "Last-Event-Id", "X-Custom-Header", ], credentials: true, }, }, });

- true(default) - enable CORS with default settings
- false- disable CORS entirely
- An object with these fields:

- origin- a string, array of strings, or a function(origin: string) => boolean
- allowedHeaders- a string or array of strings
- methods- array of allowed HTTP methods
- exposedHeaders- array of headers to expose
- credentials- boolean to allow credentials
- maxAge- preflight cache duration in seconds

TheCorsOptionstype is exported fromfastmcpfor convenience.

FastMCP 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.

const app = server.getApp(); // Add REST API endpoints with Hono's native API app.get("/api/users", async (c) => { return c.json({ users: [] }); }); // Handle path parameters app.get("/api/users/:id", async (c) => { return c.json({ userId: c.req.param("id"), query: c.req.query(), // Access query parameters }); }); // Handle POST requests with body parsing app.post("/api/users", async (c) => { const body = await c.req.json(); return c.json({ created: body }, 201); }); // Serve HTML content app.get("/admin", async (c) => { return c.html("<html><body><h1>Admin Panel</h1></body></html>"); }); // Handle webhooks app.post("/webhook/github", async (c) => { const payload = await c.req.json(); const event = c.req.header("x-github-event"); // Process webhook... return c.json({ received: true }); });

Custom routes use the underlyingHonoapp returned byserver.getApp()and support:

- Hono's HTTP methods:get,post,put,delete,patch,options, and more
- Path parameters (:param) and wildcards ()
- Query string parsing
- JSON, text, form, and other body helpers fromc.req
- Custom status codes and headers
- Middleware and route groups through Hono

Routes are matched in the order they are registered, allowing you to define specific routes before catch-all patterns.

Custom 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'sauthenticateoption and your Hono route handlers:

import type { IncomingMessage } from "node:http"; import type { Context } from "hono"; import { FastMCP } from "fastmcp"; async function authenticateRequest(request: IncomingMessage) { const apiKey = request.headers["x-api-key"]; return apiKey === "123" ? { userId: "123" } : undefined; } const server = new FastMCP({ name: "My Server", version: "1.0.0", authenticate: authenticateRequest, }); const app = server.getApp(); async function requireAuth(c: Context) { const auth = await authenticateRequest(c.env.incoming); if (!auth) { return c.json({ error: "Authentication required" }, 401); } return auth; } // Public route - no authentication required app.get("/.well-known/openid-configuration", async (c) => { return c.json({ issuer: "https://example.com", authorization_endpoint: "https://example.com/auth", token_endpoint: "https://example.com/token", }); }); // Private route - requires authentication app.get("/api/users", async (c) => { const auth = await requireAuth(c); if (auth instanceof Response) { return auth; } return c.json({ users: [] }); }); // Public static files app.get("/public/", async (c) => { return c.text(File: ${c.req.path}); });

- OAuth discovery endpoints (.well-known/)
- Health checks and status pages
- Static assets and documentation
- Webhook endpoints from external services
- Public APIs that don't require user authentication

See thecustom-routes examplefor a complete demonstration.

FastMCP supports edge runtimes like Cloudflare Workers, enabling deployment of MCP servers to the edge with minimal latency worldwide.

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 anauthenticateoption that accepts webRequestinstead of Node.jshttp.IncomingMessage.

const app = server.getApp(); app.use("/api/", async (c, next) => { if (c.req.header("authorization") !== "Bearer secret") { return c.json({ error: "Unauthorized" }, 401); } await next(); });

To deploy FastMCP to Cloudflare Workers, use theEdgeFastMCPclass from the/edgesubpath:

import { EdgeFastMCP } from "fastmcp/edge"; import { z } from "zod"; const server = new EdgeFastMCP({ name: "My Edge Server", version: "1.0.0", description: "MCP server running on Cloudflare Workers", }); // Add tools, resources, prompts as usual server.addTool({ name: "greet", description: "Greet someone", parameters: z.object({ name: z.string(), }), execute: async ({ name }) => { return Hello, ${name}! Served from the edge.; }, }); // Export the server as the default (required for Cloudflare Workers) export default server;

- Stateless by default: Each request is handled independently
- No filesystem access: Use fetch APIs for external data
- V8 Isolates: Fast cold starts and efficient resource usage
- Global deployment: Automatic distribution to edge locations

You can access the underlying Hono app to add custom HTTP routes:

const app = server.getApp(); // Add a landing page app.get("/", (c) => c.html("<h1>Welcome to my MCP server</h1>")); // Add REST API endpoints app.get("/api/status", (c) => c.json({ status: "ok" }));
name = "my-mcp-server" main = "src/index.ts" compatibility_date = "2024-01-01"

See theedge-cloudflare-worker examplefor a complete demonstration.

FastMCP 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.

- No sessions are tracked on the server
- Each request creates a temporary session that's discarded after the response
- Reduced memory usage and better scalability
- Perfect for stateless deployment environments

You can enable stateless mode by adding thestateless: trueoption:

server.start({ transportType: "httpStream", httpStream: { port: 8080, stateless: true, }, });

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.

You can also enable stateless mode using CLI arguments or environment variables:

# Via CLI argument npx fastmcp dev src/server.ts --transport http-stream --port 8080 --stateless true # Via environment variable FASTMCP_STATELESS=true npx fastmcp dev src/server.ts

The/readyhealth check endpoint will indicate when the server is running in stateless mode:

{ "mode": "stateless", "ready": 1, "status": "ready", "total": 1 }

Toolsin MCP allow servers to expose executable functions that can be invoked by clients and used by LLMs to perform actions.

FastMCP uses theStandard Schemaspecification 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.

import { z } from "zod"; server.addTool({ name: "fetch-zod", description: "Fetch the content of a url (using Zod)", parameters: z.object({ url: z.string(), }), execute: async (args) => { return await fetchWebpageContent(args.url); }, });
import { type } from "arktype"; server.addTool({ name: "fetch-arktype", description: "Fetch the content of a url (using ArkType)", parameters: type({ url: "string", }), execute: async (args) => { return await fetchWebpageContent(args.url); }, });

Valibot requires the peer dependency @valibot/to-json-schema.

import * as v from "valibot"; server.addTool({ name: "fetch-valibot", description: "Fetch the content of a url (using Valibot)", parameters: v.object({ url: v.string(), }), execute: async (args) => { return await fetchWebpageContent(args.url); }, });

If you already have a JSON Schema — from an OpenAPI document, a config file, or another server —jsonSchemaAdapterwraps it so it can be used directly, with no schema library in between.

It requires the peer dependencyajv, which does the validation, plusajv-formatsif your schema usesformatkeywords such asemailoruri. Both are imported the first time a tool is called, so servers that don't use this pay nothing for it.

import { jsonSchemaAdapter } from "fastmcp"; server.addTool({ name: "fetch-json-schema", description: "Fetch the content of a url (using plain JSON Schema)", parameters: jsonSchemaAdapter({ type: "object", properties: { url: { type: "string", format: "uri" }, }, required: ["url"], }), execute: async (args) => { const { url } = args as { url: string }; return await fetchWebpageContent(url); }, });

Works foroutputSchematoo. Note that FastMCP advertises every tool schema withadditionalProperties: false, whatever your schema said — the same treatment Zod and Valibot schemas get.

Unlike the schema libraries above, a plain JSON Schema carries no TypeScript types, soexecutereceivesunknownarguments. Cast or narrow them yourself.

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.