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

Search for a command to run...
Production-Ready MCP Server with Horizontal Scaling

No comments yet. Be the first to comment.
Nitro is a server toolkit used by many JavaScript applications. You can use it on its own for APIs or full-stack servers, or combine it with Vite to get a familiar frontend workflow with a Nitro serve

All durable execution engines give the same versioning advice: pin your runs. Temporal pins runs to worker builds. Vercel's Workflow SDK pins each run to the deployment that started it. Azure Durable

Eve organizes AI agents in a simple way: use Markdown for instructions and skills, TypeScript for tools, and separate files for channels, schedules, and subagents. Behind this setup, Eve relies on the

Picture an online store launching a new product and sending out a mailing list campaign. Thousands of users click the same link at once. The product page, built with a Node.js app like Next.js, needs

How Platformatic ICC Outperforms AWS ECS Target Tracking and Step 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.
@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
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.
@platformatic/mcp leverages TypeBox for runtime type validation and compile-time type safety. The complete MCP protocol is fully typed, including:
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
// ...
})
Real-time streaming communication with robust session management:
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.
@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:
Seamless backend selection based on your deployment needs:
The plugin automatically selects the appropriate backend based on configuration—no code changes required.
@platformatic/mcp is designed from the ground up for production environments with horizontal scaling, high availability, and fault tolerance.

In a production deployment, multiple @platformatic/mcp instances work together seamlessly:
Sessions include:
Install @platformatic/mcp:
npm install fastify @platformatic/mcp @sinclair/typebox
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.
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
})
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
}
}
})
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
}
}
})
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'
}]
}
}
})
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
}
}
})
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)
}
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:
For direct API testing, you can also use curl:
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 }
}
}'
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"
}
}'
curl -N -H "Accept: text/event-stream" \
"http://localhost:3000/mcp?mcp-session-id=test-session"
This complete example demonstrates:
Every aspect of fastify-mcp-server is designed with TypeScript in mind:
@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
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!