A robust Fastify plugin that provides seamless integration with the Model Context Protocol (MCP) through streamable HTTP transport. This plugin enables your Fastify applications to act as MCP servers, allowing AI assistants and other clients to interact with your services using the standardized MCP protocol.
- Fastify MCP Server Plugin
The Model Context Protocol (MCP) is an open standard that enables AI assistants to securely connect to external data sources and tools. This plugin provides a streamable HTTP transport implementation for MCP servers built with Fastify, offering:
- High Performance: Built on top of Fastify's high-performance HTTP server
- Session Management: Automatic handling of MCP sessions with proper lifecycle management
- Event-Driven Architecture: Real-time session monitoring and error handling
- Type Safety: Full TypeScript support with comprehensive type definitions
- Production Ready: Robust error handling, graceful shutdown, and monitoring capabilities
- ✅ MCP Server Integration: Seamless integration with
@modelcontextprotocol/sdk
- ✅ Streamable HTTP Transport: Full support for MCP's streamable HTTP protocol
- ✅ Session Management: Automatic session creation, tracking, and cleanup
- ✅ Request Routing: Intelligent routing for different MCP request types
- ✅ Authentication: Optional Bearer token support for secure access
- ✅ Error Handling: Comprehensive error handling with proper MCP error responses
- ✅ Event System: Listen to session lifecycle events (creation, destruction, errors)
- ✅ Session Statistics: Real-time monitoring of active sessions
- ✅ Graceful Shutdown: Proper cleanup of all sessions during server shutdown
- ✅ Configurable Endpoints: Customizable MCP endpoint paths
- ✅ TypeScript Support: Full type safety and IntelliSense support
npm install fastify-mcp-server @modelcontextprotocol/sdk
To quickly see the plugin in action, you can run the following example:
npm run dev
npm run inspector
This will start a Fastify server with the MCP plugin enabled, allowing you to interact with it via the MCP inspector or any MCP-compatible client.
import Fastify from 'fastify';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import FastifyMcpServer, { getMcpDecorator } from 'fastify-mcp-server';
const app = Fastify({ logger: true });
// Create MCP server instance
const mcp = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});
// Define MCP tools
mcp.tool('hello-world', () => ({
content: [{ type: 'text', text: 'Hello from MCP!' }]
}));
// Register the plugin
await app.register(FastifyMcpServer, {
server: mcp.server,
endpoint: '/mcp', // optional, defaults to '/mcp'
});
// Get MCP decorator for advanced features
const mcpServer = getMcpDecorator(app);
// Start the server
await app.listen({ host: '127.0.0.1', port: 3000 });
type FastifyMcpServerOptions = {
server: Server; // MCP Server instance from @modelcontextprotocol/sdk
endpoint?: string; // Custom endpoint path (default: '/mcp')
bearerMiddlewareOptions?: {
verifier: OAuthTokenVerifier; // Custom verifier for Bearer tokens
requiredScopes?: string[]; // Optional scopes required for access
resourceMetadataUrl?: string; // Optional URL for resource metadata
};
authorizationServerOAuthMetadata?: OAuthMetadata; // OAuth metadata for authorization server
protectedResourceOAuthMetadata?: OAuthProtectedResourceMetadata; // OAuth metadata for protected resource
}
The plugin decorates your Fastify instance with an MCP server that provides several useful methods:
const mcpServer = getMCPDecorator(app);
// Get session statistics
const stats = mcpServer.getStats();
console.log(`Active sessions: ${stats.activeSessions}`);
// Access session manager for event handling
const sessionManager = mcpServer.getSessionManager();
// Graceful shutdown
await mcpServer.shutdown();
Monitor session lifecycle with event listeners:
const sessionManager = mcpServer.getSessionManager();
// Session created
sessionManager.on('sessionCreated', (sessionId: string) => {
console.log(`New MCP session: ${sessionId}`);
});
// Session destroyed
sessionManager.on('sessionDestroyed', (sessionId: string) => {
console.log(`MCP session ended: ${sessionId}`);
});
// Transport errors
sessionManager.on('transportError', (sessionId: string, error: Error) => {
console.error(`Error in session ${sessionId}:`, error);
});
The plugin exposes three HTTP endpoints for MCP communication:
- Purpose: Create new sessions or send requests to existing sessions
- Headers:
content-type: application/json
mcp-session-id: <session-id>
(optional, for existing sessions)
- Body: MCP request payload
- Purpose: Retrieve streaming responses
- Headers:
mcp-session-id: <session-id>
(required)
- Response: Server-sent events stream
- Purpose: Terminate sessions
- Headers:
mcp-session-id: <session-id>
(required)
Sessions are managed through a dedicated SessionManager
class that:
- Creates new transport instances with unique session IDs
- Tracks active sessions in memory
- Handles session lifecycle events
- Provides graceful cleanup on shutdown
- Emits events for monitoring and logging
sessionManager.on('transportError', (sessionId, error) => {
console.error(`Transport error: ${error.message}`);
});
// Periodic health check
setInterval(() => {
const stats = mcpServer.getStats();
console.log(`Health Check - Active Sessions: ${stats.activeSessions}`);
// Alert if too many sessions
if (stats.activeSessions > 100) {
console.warn('High session count detected');
}
}, 30000);
import closeWithGrace from 'close-with-grace';
closeWithGrace({ delay: 500 }, async ({ signal, err }) => {
if (err) {
app.log.error({ err }, 'server closing with error');
} else {
app.log.info(`${signal} received, server closing`);
}
// Shutdown MCP sessions before closing Fastify
await mcpServer.shutdown();
await app.close();
});
You can secure your MCP endpoints using Bearer token authentication. The plugin provides a bearerMiddlewareOptions
option, which enables validation of Bearer tokens in the Authorization
header for all MCP requests.
Pass the bearerMiddlewareOptions
option when registering the plugin. It accepts BearerAuthMiddlewareOptions
from the SDK:
import type { BearerAuthMiddlewareOptions } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
await app.register(FastifyMcpServer, {
server: mcp.server,
bearerMiddlewareOptions: {
verifier: myVerifier, // implements verifyAccessToken(token)
requiredScopes: ['mcp:read', 'mcp:write'], // optional
resourceMetadataUrl: 'https://example.com/.well-known/oauth-resource', // optional,
}
});
- verifier: An object with a
verifyAccessToken(token)
method that returns the decoded token info or throws on failure. It must implements theOAuthTokenVerifier
interface from the SDK. - requiredScopes: (Optional) Array of scopes required for access.
- resourceMetadataUrl: (Optional) URL included in the
WWW-Authenticate
header for 401 responses.
The plugin uses a Fastify preHandler
hook applied in the context of the MCP registered routes (see addBearerPreHandlerHook
) to:
- Extract the Bearer token from the
Authorization
header (Authorization: Bearer TOKEN
). - Validate the token using your verifier.
- Check for required scopes and token expiration.
- Attach the decoded auth info to the request object (
req.raw.auth
). - Respond with proper OAuth2 error codes and
WWW-Authenticate
headers on failure.
You can access the validated authentication information in your MCP tools via the authInfo
parameter:
mcp.tool('example-auth-tool', 'Demo to display the validated access token in authInfo object', ({ authInfo }) => {
return {
content: [
{
type: 'text',
// Just a bad example, do not expose sensitive information in your LLM responses! :-)
text: `Authenticated with token: ${authInfo.token}, scopes: ${authInfo.scopes.join(', ')}, expires at: ${new Date(authInfo.expiresAt).toISOString()}`
}
]
};
});
If authentication fails, the response will include a WWW-Authenticate
header:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Token has expired"
Content-Type: application/json
{"error":"invalid_token","error_description":"Token has expired"}
{
"inputs": [
{
"type": "promptString",
"id": "bearer_token",
"description": "Enter your MCP Bearer Token",
"password": true
}
],
"servers": {
"my-mcp-server": {
"url": "http://localhost:9080/mcp",
"headers": {
"Authorization": "Bearer ${input:bearer_token}"
}
}
}
}
The plugin can automatically register standard OAuth 2.0 metadata endpoints under the .well-known
path, which are useful for interoperability with OAuth clients and resource servers. You can test metadata discovery with the MCP inspector in the Authentication
tab.
To enable these endpoints, provide the authorizationServerOAuthMetadata
and/or protectedResourceOAuthMetadata
options when registering the plugin:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import FastifyMcpServer from 'fastify-mcp-server';
const mcp = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});
const authorizationServerMetadata = {
issuer: 'https://your-domain.com',
authorization_endpoint: 'https://your-domain.com/oauth/authorize',
token_endpoint: 'https://your-domain.com/oauth/token',
// ...other OAuth metadata fields
};
const protectedResourceMetadata = {
resource: 'https://your-domain.com/.well-known/oauth-protected-resource',
// ...other resource metadata fields
};
await app.register(FastifyMcpServer, {
server: mcp.server,
authorizationServerOAuthMetadata: authorizationServerMetadata, // Registers /.well-known/oauth-authorization-server
protectedResourceOAuthMetadata: protectedResourceMetadata, // Registers /.well-known/oauth-protected-resource
});
GET /.well-known/oauth-authorization-server
— Returns the OAuth authorization server metadata.GET /.well-known/oauth-protected-resource
— Returns the OAuth protected resource metadata.
These endpoints are registered only if the corresponding metadata options are provided.
# Clone the repository
git clone https://github.com/flaviodelgrosso/fastify-mcp-server.git
cd fastify-mcp-server
# Install dependencies
npm install
# Run development server with hot reload
npm run dev
npm run dev
- Run development server with hot reloadnpm run build
- Build TypeScript to JavaScriptnpm test
- Run test suite with 100% coveragenpm run test:lcov
- Generate LCOV coverage reportnpm run release
- Create a new release
The project maintains 100% test coverage. Run tests with:
npm test
Contributions are welcome! Please read our contributing guidelines and ensure:
- Tests pass with 100% coverage
- Code follows the established style (enforced by Biome)
- Commits follow conventional commit format
- Changes are properly documented
ISC © Flavio Del Grosso
- Model Context Protocol - Official MCP specification and servers
- Fastify - Fast and low overhead web framework
- MCP SDK - TypeScript SDK for MCP