Documentation Index Fetch the complete documentation index at: https://mintlify.com/punkpeye/fastmcp/llms.txt
Use this file to discover all available pages before exploring further.
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.
Quick Start
Add custom routes using the addRoute method or by accessing the underlying Hono app:
import { FastMCP } from "fastmcp" ;
const server = new FastMCP ({
name: "My Server" ,
version: "1.0.0" ,
});
// Add REST API endpoints
server . addRoute ( "GET" , "/api/users" , async ( req , res ) => {
res . json ({ users: [] });
});
// Handle path parameters
server . addRoute ( "GET" , "/api/users/:id" , async ( req , res ) => {
res . json ({
userId: req . params . id ,
query: req . query , // Access query parameters
});
});
// Handle POST requests with body parsing
server . addRoute ( "POST" , "/api/users" , async ( req , res ) => {
const body = await req . json ();
res . status ( 201 ). json ({ created: body });
});
server . start ({
transportType: "httpStream" ,
httpStream: { port: 8080 },
});
Using Hono’s Native API
For advanced use cases, access the underlying Hono app directly:
import { FastMCP } from "fastmcp" ;
const server = new FastMCP ({
name: "My Server" ,
version: "1.0.0" ,
});
// Get the Hono app instance
const app = server . getApp ();
// Use Hono's native methods
app . get ( "/api/users" , async ( c ) => {
const userList = await getUsersFromDatabase ();
return c . json ({
count: userList . length ,
users: userList ,
});
});
app . post ( "/api/users" , async ( c ) => {
const body = await c . req . json ();
const newUser = await createUser ( body );
return c . json ( newUser , 201 );
});
Path Parameters and Wildcards
Custom routes support path parameters and wildcard patterns:
Path Parameters
Wildcards
Query Parameters
server . addRoute ( "GET" , "/api/users/:id" , async ( req , res ) => {
const userId = req . params . id ;
res . json ({ userId });
});
server . addRoute ( "GET" , "/api/posts/:postId/comments/:commentId" , async ( req , res ) => {
res . json ({
postId: req . params . postId ,
commentId: req . params . commentId ,
});
});
// Serve static files
server . addRoute (
"GET" ,
"/public/*" ,
async ( req , res ) => {
res . send ( `File: ${ req . url } ` );
},
{ public: true },
);
// Catch-all route
server . addRoute ( "GET" , "/*" , async ( req , res ) => {
res . status ( 404 ). json ({ error: "Not found" });
});
server . addRoute ( "GET" , "/api/search" , async ( req , res ) => {
const query = req . query . q ;
const page = parseInt ( req . query . page || "1" );
const limit = parseInt ( req . query . limit || "10" );
res . json ({
query ,
page ,
limit ,
results: [],
});
});
Public Routes
By default, custom routes require authentication (if configured). Make routes public by adding the { public: true } option:
import { FastMCP } from "fastmcp" ;
const server = new FastMCP ({
name: "My Server" ,
version: "1.0.0" ,
authenticate : ( request ) => {
const apiKey = request . headers [ "x-api-key" ];
if ( apiKey !== "secret" ) {
throw new Response ( null , { status: 401 });
}
return { userId: 1 };
},
});
// Public route - no authentication required
server . addRoute (
"GET" ,
"/.well-known/openid-configuration" ,
async ( req , res ) => {
res . json ({
issuer: "https://example.com" ,
authorization_endpoint: "https://example.com/auth" ,
token_endpoint: "https://example.com/token" ,
});
},
{ public: true },
);
// Private route - requires authentication
server . addRoute ( "GET" , "/api/users" , async ( req , res ) => {
// req.auth contains authenticated user data
res . json ({ users: [] });
});
Public routes are perfect for:
OAuth discovery endpoints (.well-known/*)
Health checks and status pages
Static assets and documentation
Webhook endpoints from external services
Real-World Examples
REST API
const users = new Map ();
// List users
app . get ( "/api/users" , async ( c ) => {
const userList = Array . from ( users . values ());
return c . json ({
count: userList . length ,
users: userList ,
});
});
// Get user by ID
app . get ( "/api/users/:id" , async ( c ) => {
const id = c . req . param ( "id" );
const user = users . get ( id );
if ( ! user ) {
return c . json ({ error: "User not found" }, 404 );
}
return c . json ( user );
});
// Create user
app . post ( "/api/users" , async ( c ) => {
const body = await c . req . json ();
const id = String ( users . size + 1 );
const newUser = { id , ... body };
users . set ( id , newUser );
return c . json ( newUser , 201 );
});
// Update user
app . put ( "/api/users/:id" , async ( c ) => {
const id = c . req . param ( "id" );
const user = users . get ( id );
if ( ! user ) {
return c . json ({ error: "User not found" }, 404 );
}
const body = await c . req . json ();
const updatedUser = { ... user , ... body , id };
users . set ( id , updatedUser );
return c . json ( updatedUser );
});
// Delete user
app . delete ( "/api/users/:id" , async ( c ) => {
const id = c . req . param ( "id" );
if ( ! users . has ( id )) {
return c . json ({ error: "User not found" }, 404 );
}
users . delete ( id );
return c . body ( null , 204 );
});
Admin Interface
app . get ( "/admin" , async ( c ) => {
const auth = await getAuth ( c );
if ( ! auth || auth . role !== "admin" ) {
return c . json ({ error: "Admin access required" }, 403 );
}
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Admin Dashboard</title>
<style>
body { font-family: sans-serif; margin: 40px; }
.stats { background: #f0f0f0; padding: 20px; border-radius: 8px; }
</style>
</head>
<body>
<h1>Admin Dashboard</h1>
<div class="stats">
<div>Total Users: ${ users . size } </div>
<div>Server Time: ${ new Date (). toISOString () } </div>
</div>
</body>
</html>
` ;
return c . html ( html );
});
Webhooks
app . post ( "/webhook/github" , async ( c ) => {
const payload = await c . req . json ();
const event = c . req . header ( "x-github-event" );
console . log ( `GitHub webhook received: ${ event } ` , payload );
// Process webhook (e.g., trigger MCP tools)
return c . json ({ event , received: true });
});
app . post ( "/webhook/stripe" , async ( c ) => {
const signature = c . req . header ( "stripe-signature" );
const body = await c . req . text ();
// Verify webhook signature
// Process payment event
return c . json ({ received: true });
});
File Upload
app . post ( "/upload" , async ( c ) => {
const auth = await getAuth ( c );
if ( ! auth ) {
return c . json ({ error: "Authentication required" }, 401 );
}
try {
const body = await c . req . text ();
const size = Buffer . byteLength ( body );
return c . json ({
message: "File received" ,
size: ` ${ size } bytes` ,
});
} catch ( error ) {
return c . json (
{
error: error instanceof Error ? error . message : "Upload failed" ,
},
500 ,
);
}
});
Custom routes can interact with MCP tools and share data:
import { FastMCP } from "fastmcp" ;
import { z } from "zod" ;
const server = new FastMCP ({
name: "My Server" ,
version: "1.0.0" ,
});
const app = server . getApp ();
const users = new Map ();
// REST API endpoint
app . get ( "/api/users" , async ( c ) => {
const userList = Array . from ( users . values ());
return c . json ({ users: userList });
});
// MCP tool that uses the same data
server . addTool ({
description: "List all users from the REST API" ,
execute : async () => {
const userList = Array . from ( users . values ());
return {
content: [
{
text: `Found ${ userList . length } users: \n ${ userList
. map (( u ) => `- ${ u . name } ( ${ u . email } )` )
. join ( " \n " ) } ` ,
type: "text" ,
},
],
};
},
name: "list_users" ,
parameters: z . object ({}),
});
// MCP tool that creates users
server . addTool ({
description: "Create a new user via the REST API" ,
execute : async ({ email , name }) => {
const id = String ( users . size + 1 );
const newUser = { email , id , name };
users . set ( id , newUser );
return {
content: [
{
text: `User created successfully: \n ID: ${ id } \n Name: ${ name } \n Email: ${ email } ` ,
type: "text" ,
},
],
};
},
name: "create_user" ,
parameters: z . object ({
email: z . string (). email (),
name: z . string (),
}),
});
Supported HTTP Methods
Custom routes support all standard HTTP methods:
GET - Retrieve data
POST - Create new resources
PUT - Update existing resources
DELETE - Delete resources
PATCH - Partial updates
OPTIONS - CORS preflight requests
Request and Response Helpers
Request
server . addRoute ( "POST" , "/api/example" , async ( req , res ) => {
// Path parameters
const id = req . params . id ;
// Query parameters
const query = req . query . search ;
// Headers
const authHeader = req . headers . authorization ;
// Parse JSON body
const body = await req . json ();
// Parse text body
const text = await req . text ();
// Full URL
const url = req . url ;
});
Response
server . addRoute ( "GET" , "/api/example" , async ( req , res ) => {
// JSON response
res . json ({ message: "Hello" });
// Text response
res . send ( "Hello, world!" );
// HTML response
res . send ( "<h1>Hello</h1>" );
// Custom status code
res . status ( 201 ). json ({ created: true });
// Custom headers
res . setHeader ( "X-Custom-Header" , "value" );
res . json ({ message: "Hello" });
});
Complete Example
Here’s a complete example from the FastMCP repository showing custom routes with authentication, public endpoints, and MCP integration:
src/examples/custom-routes.ts
import { FastMCP } from "fastmcp" ;
import { z } from "zod" ;
const server = new FastMCP ({
authenticate : async ( req ) => {
const authHeader = req . headers . authorization ;
if ( authHeader === "Bearer admin-token" ) {
return { role: "admin" , userId: "admin" };
} else if ( authHeader === "Bearer user-token" ) {
return { role: "user" , userId: "user1" };
}
throw new Error ( "Invalid or missing authentication" );
},
name: "custom-routes-example" ,
version: "1.0.0" ,
});
const app = server . getApp ();
const users = new Map ();
// Public routes
app . get ( "/status" , async ( c ) => {
return c . json ({
status: "healthy" ,
timestamp: new Date (). toISOString (),
});
});
// Private routes (require authentication)
app . get ( "/api/users" , async ( c ) => {
const auth = await getAuth ( c );
if ( ! auth ) {
return c . json ({ error: "Authentication required" }, 401 );
}
const userList = Array . from ( users . values ());
return c . json ({
authenticated_as: auth . userId ,
users: userList ,
});
});
server . start ({
transportType: "httpStream" ,
httpStream: { port: 8080 },
});
Next Steps
Authentication Learn how to protect custom routes with authentication
Edge Runtime Deploy custom routes to Cloudflare Workers