Introducing @platformatic/mcp
Production-Ready MCP Server with Horizontal Scaling

Today we're excited to announce @platformatic/mcp, a production-ready Fastify adapter for the Model Context Protocol (MCP) that brings enterprise-grade scalability and type safety to MCP server implementations.
Why @platformatic/mcp?
The Model Context Protocol revolutionizes how AI applications connect to data sources and tools. However, most existing MCP implementations are designed for single-instance development scenarios. As organizations scale their AI applications, they need MCP servers that can handle production workloads with high availability, horizontal scaling, and robust type safety.
@platformatic/mcp fills this gap by providing:
Multiple Transport Support: HTTP/SSE and stdio transports for flexible communication
Horizontal Scaling: Redis-backed session management and message broadcasting
High Availability: Session persistence with automatic reconnection and failover
Type Safety: Complete TypeScript definitions powered by TypeBox
Production Ready: Built on Fastify's battle-tested foundation
Key Features
🚀 Horizontal Scaling with Redis
Unlike traditional MCP servers that are limited to single instances, @platformatic/mcp supports true horizontal scaling:
import mcp from '@platformatic/mcp'
const app = fastify()
await app.register(mcp, {
redis: {
host: 'localhost',
port: 6379
},
enableSSE: true
})
With Redis configuration, messages sent from any server instance reach all connected clients across the cluster. Session state is shared and persists across server restarts.
🔒 Complete Type Safety with TypeBox
@platformatic/mcp leverages TypeBox for runtime type validation and compile-time type safety. The complete MCP protocol is fully typed, including:
- JSON-RPC 2.0 message types (requests, responses, notifications)
- MCP protocol lifecycle (initialization, capabilities, ping)
- Core features: resources, prompts, tools, logging, sampling
- Content types (text, image, audio, embedded resources)
import { Type } from '@sinclair/typebox'
app.mcpAddTool({
name: 'file_read',
description: 'Read file contents',
inputSchema: Type.Object({
path: Type.String(),
encoding: Type.Optional(Type.String())
})
}, async (params) => {
// params are fully typed based on schema
const { path, encoding } = params
// ...
})
📡 Server-Sent Events (SSE) with Session Management
Real-time streaming communication with robust session management:
- Message history and replay using Last-Event-ID
- Heartbeat mechanism for connection health monitoring
- Cross-instance message broadcasting
- Automatic session cleanup with configurable TTL
Session Reconnection & Message Replay
One of @platformatic/mcp's most powerful features is its ability to handle client reconnections seamlessly. When a client reconnects after a network interruption, the server automatically replays missed messages using the SSE Last-Event-ID header. This ensures no data is lost during temporary disconnections.

This mechanism works across server instances in a clustered deployment. If a client reconnects to a different server instance, the session state and message history are retrieved from Redis, ensuring continuity regardless of which server handles the reconnection.
🖥️ Stdio Transport Support
@platformatic/mcp includes a complete stdio transport implementation that enables seamless communication with command-line tools, text editors, and local applications:
import fastify from 'fastify'
import mcpPlugin, { runStdioServer } from '@platformatic/mcp'
const app = fastify({ logger: false })
await app.register(mcpPlugin, {
serverInfo: { name: 'stdio-server', version: '1.0.0' },
capabilities: { tools: {}, resources: {}, prompts: {} }
})
// Register your tools, resources, and prompts here
app.mcpAddTool({
name: 'echo',
description: 'Echo back the input text',
inputSchema: { type: 'object', properties: { text: { type: 'string' } } }
}, async (args) => ({
content: [{ type: 'text', text: `Echo: ${args.text}` }]
}))
await app.ready()
await runStdioServer(app)
The stdio transport is particularly valuable for:
- IDE/Editor integrations with native MCP support
- Command-line tools that need structured MCP communication
- Local development without HTTP overhead
- MCP Inspector integration for testing and debugging
🏗️ Dual Backend Architecture
Seamless backend selection based on your deployment needs:
- Memory backends: Perfect for development and single-instance deployments
- Redis backends: Production-ready with cross-instance communication and persistence
The plugin automatically selects the appropriate backend based on configuration—no code changes required.
Production-Ready Architecture
@platformatic/mcp is designed from the ground up for production environments with horizontal scaling, high availability, and fault tolerance.

Horizontal Scaling Architecture
In a production deployment, multiple @platformatic/mcp instances work together seamlessly:
- Load Balancer: Distributes incoming connections across available server instances
- Server Instances: Each server can handle thousands of concurrent connections
- Redis Cluster: Provides shared session storage and message broadcasting
- Session Persistence: Clients can reconnect to any server instance and resume their session
- Message Broadcasting: Events from any server reach all relevant clients across the cluster
Session Management
Sessions include:
- Metadata storage with automatic TTL (1-hour expiration)
- Message history with configurable limits
- Automatic cleanup and trimming
Message Broadcasting
- Session-specific message routing
- Broadcast notifications across all instances
- Cross-instance message delivery
Getting Started
Install @platformatic/mcp:
npm install fastify @platformatic/mcp @sinclair/typebox
Step-by-Step Tutorial: Building a File System MCP Server
Let's build a complete file system MCP server that demonstrates tools, resources, and real-time notifications. This tutorial shows creating a production-ready server with TypeScript safety and SSE support.
Step 1: Set up the Server
import Fastify from 'fastify'
import { Type } from '@sinclair/typebox'
import mcpPlugin from '@platformatic/mcp'
const fastify = Fastify({
logger: { level: 'info' }
})
// Register the MCP plugin with SSE enabled
await fastify.register(mcpPlugin, {
serverInfo: {
name: 'file-listing-server',
version: '1.0.0'
},
capabilities: {
tools: {},
resources: {},
prompts: {}
},
instructions: 'A file system listing server that can list files and directories',
enableSSE: true
})
Step 2: Add a File Listing Tool
Create a tool that lists files in a directory with full TypeScript validation:
import { promises as fs } from 'fs'
import { join, relative } from 'path'
const ListFilesSchema = Type.Object({
path: Type.Optional(Type.String({
description: 'The directory path to list files from (defaults to current directory)',
default: '.'
})),
showHidden: Type.Optional(Type.Boolean({
description: 'Whether to show hidden files (files starting with .)',
default: false
}))
})
fastify.mcpAddTool({
name: 'list_files',
description: 'List files and directories in a given path',
inputSchema: ListFilesSchema
}, async (params) => {
const { path = '.', showHidden = false } = params
try {
const fullPath = join(process.cwd(), path)
const items = await fs.readdir(fullPath, { withFileTypes: true })
const filteredItems = items.filter(item => {
if (!showHidden && item.name.startsWith('.')) {
return false
}
return true
})
const fileList = filteredItems.map(item => ({
name: item.name,
type: item.isDirectory() ? 'directory' : 'file',
path: relative(process.cwd(), join(fullPath, item.name))
}))
return {
content: [{
type: 'text',
text: `Found ${fileList.length} items in ${path}:\n\n` +
fileList.map(item =>
`${item.type === 'directory' ? '📁' : '📄'} ${item.name} (${item.path})`
).join('\n')
}]
}
} catch (error) {
return {
content: [{
type: 'text',
text: `Error listing files: ${error.message}`
}],
isError: true
}
}
})
Step 3: Add a File Info Tool
Create a tool that provides detailed file information:
const GetFileInfoSchema = Type.Object({
path: Type.String({
description: 'The file or directory path to get info about'
})
})
fastify.mcpAddTool({
name: 'get_file_info',
description: 'Get detailed information about a file or directory',
inputSchema: GetFileInfoSchema
}, async (params) => {
const { path } = params
try {
const fullPath = join(process.cwd(), path)
const stats = await fs.stat(fullPath)
return {
content: [{
type: 'text',
text: `File info for ${path}:\n\n` +
`Type: ${stats.isDirectory() ? 'Directory' : 'File'}\n` +
`Size: ${stats.size} bytes\n` +
`Modified: ${stats.mtime.toISOString()}\n` +
`Created: ${stats.birthtime.toISOString()}\n` +
`Permissions: ${stats.mode.toString(8)}`
}]
}
} catch (error) {
return {
content: [{
type: 'text',
text: `Error getting file info: ${error.message}`
}],
isError: true
}
}
})
Step 4: Add a File Reading Resource
Create a resource that allows reading file contents:
const FileUriSchema = Type.String({
pattern: '^file://read\\?path=.+',
description: 'URI pattern for file reading with path parameter'
})
fastify.mcpAddResource({
uriPattern: 'file://read',
name: 'Read File',
description: 'Read the contents of a file',
mimeType: 'text/plain',
uriSchema: FileUriSchema
}, async (uri) => {
const url = new URL(uri)
const filePath = url.searchParams.get('path')
if (!filePath) {
return {
contents: [{
uri,
text: 'Error: No file path specified. Use ?path=<filepath>',
mimeType: 'text/plain'
}]
}
}
try {
const fullPath = join(process.cwd(), filePath)
const content = await fs.readFile(fullPath, 'utf-8')
return {
contents: [{
uri,
text: content,
mimeType: 'text/plain'
}]
}
} catch (error) {
return {
contents: [{
uri,
text: `Error reading file: ${error.message}`,
mimeType: 'text/plain'
}]
}
}
})
Step 5: Add Real-time File Watching
Create a tool that watches for file changes and sends SSE notifications:
import { watch } from 'fs'
const WatchFilesSchema = Type.Object({
path: Type.Optional(Type.String({
description: 'The directory path to watch for changes (defaults to current directory)',
default: '.'
})),
watchId: Type.String({
description: 'Unique identifier for this watch session'
})
})
fastify.mcpAddTool({
name: 'watch_files',
description: 'Watch for file changes in a directory and send notifications via SSE',
inputSchema: WatchFilesSchema
}, async (params, context) => {
const { path = '.', watchId } = params
const sessionId = context?.sessionId
try {
if (!sessionId) {
return {
content: [{
type: 'text',
text: 'Session ID is required for file watching. Make sure you are using SSE.'
}],
isError: true
}
}
const fullPath = join(process.cwd(), path)
const watcher = watch(fullPath, { recursive: true })
// Handle file change events
watcher.on('change', (eventType, filename) => {
if (filename) {
const notification = {
jsonrpc: '2.0' as const,
method: 'notifications/file_changed',
params: {
watchId,
event_type: eventType,
filename: filename.toString(),
full_path: join(fullPath, filename.toString()),
timestamp: new Date().toISOString()
}
}
// Send to specific session
fastify.mcpSendToSession(sessionId, notification)
}
})
return {
content: [{
type: 'text',
text: `Started watching '${path}' with ID '${watchId}'. File change notifications will be sent via SSE.`
}]
}
} catch (error) {
return {
content: [{
type: 'text',
text: `Error starting file watcher: ${error.message}`
}],
isError: true
}
}
})
Step 6: Start the Server
try {
const port = process.env.PORT ? Number(process.env.PORT) : 3000
await fastify.listen({ port })
console.log(`🚀 MCP File Listing Server started on port ${port}`)
console.log('📁 Available tools:')
console.log(' - list_files: List files in a directory')
console.log(' - get_file_info: Get detailed file information')
console.log(' - watch_files: Watch for file changes (requires SSE)')
console.log('📄 Available resources:')
console.log(' - file://read?path=<filepath>: Read file contents')
console.log('\nTo test the server:')
console.log(' - JSON-RPC requests: POST http://localhost:3000/mcp')
console.log(' - SSE notifications: GET http://localhost:3000/mcp')
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
Testing Your Server
Using the MCP Inspector (Recommended)
The easiest way to test your MCP server is with the official MCP Inspector:
# First, start your server (Node 22+ supports TypeScript natively)
node --experimental-strip-types server.ts
# In another terminal, run the inspector
npx @modelcontextprotocol/inspector http://localhost:3000/mcp
This opens an interactive web UI at http://localhost:6274 where you can:
- Initialize the MCP connection
- Browse available tools and their schemas
- Call tools with a user-friendly interface
- View resources and test URI patterns
- Monitor SSE events in real-time
- Inspect all MCP messages with detailed logging
Manual Testing with curl
For direct API testing, you can also use curl:
- Test tools with JSON-RPC:
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_files",
"arguments": { "path": ".", "showHidden": false }
}
}'
- Test resources:
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file://read?path=package.json"
}
}'
- Test SSE notifications:
curl -N -H "Accept: text/event-stream" \
"http://localhost:3000/mcp?mcp-session-id=test-session"
This complete example demonstrates:
- Type-safe tools with TypeBox validation
- Resource handling with URI patterns
- Real-time notifications via SSE
- Error handling with proper MCP responses
- Session management for stateful operations
Real-World Use Cases
- Multi-Tenant SaaS Applications: Deploy multiple MCP server instances behind a load balancer, with Redis ensuring session consistency across all instances.
- High-Availability AI Services: Implement automatic failover with session persistence—clients can reconnect to any server instance and resume their session.
- Enterprise AI Platforms: Scale MCP servers horizontally to handle thousands of concurrent AI agents with full type safety and monitoring.
Performance & Reliability
- Redis backend tests for reliability
- Automatic cleanup with configurable TTL
- Connection health monitoring with heartbeat
TypeScript-First Development
Every aspect of fastify-mcp-server is designed with TypeScript in mind:
- Complete MCP protocol type definitions
- Runtime validation with TypeBox
- IDE autocompletion and type checking
- Compile-time safety for all MCP operations
Community & Support
@platformatic/mcp is open source and follows Fastify's proven patterns and best practices.
GitHub: platformatic/mcp
License: Apache 2.0
Documentation: Complete API documentation and examples
What's Next
We're excited to see how the community uses @platformatic/mcp to build scalable, production-ready MCP servers. Whether building a simple development server or an enterprise-grade AI platform, @platformatic/mcp provides the foundation you need.
Get started today and join the growing community of developers building the future of AI connectivity with the Model Context Protocol.
@platformatic/mcp v1.0.0 is available now on npm. Try it out and let us know what you build!






