@riktajs/mcp
v0.3.0
Published
Model Context Protocol (MCP) integration for Rikta Framework - Connect AI assistants to your backend
Maintainers
Readme
@riktajs/mcp
Model Context Protocol (MCP) integration for Rikta Framework. Connect AI assistants like Claude and GPT to your Rikta backend with decorator-based APIs.
Features
- 🤖 Decorator-based API - Define MCP tools, resources, and prompts with
@MCPTool,@MCPResource,@MCPPrompt - 🔍 Auto-discovery - Automatically discovers MCP handlers from
@Injectableclasses - 📝 Zod Integration - Full Zod support for input schema validation (consistent with Rikta ecosystem)
- 📡 SSE Support - Server-Sent Events for real-time notifications
- 🔄 Horizontal Scaling - Redis support for multi-instance deployments
- 🔒 Type Safe - Complete TypeScript definitions
Installation
npm install @riktajs/mcp zodNote:
@platformatic/mcpis included as a dependency and will be installed automatically.
Quick Start
import { Rikta, Injectable } from '@riktajs/core';
import { registerMCPServer, MCPTool, MCPResource, z } from '@riktajs/mcp';
import { promises as fs } from 'fs';
@Injectable()
class FileService {
@MCPTool({
name: 'list_files',
description: 'List files in a directory',
inputSchema: z.object({
path: z.string().describe('Directory path to list'),
showHidden: z.boolean().optional().default(false),
}),
})
async listFiles(params: { path: string; showHidden?: boolean }) {
const files = await fs.readdir(params.path);
const filtered = params.showHidden
? files
: files.filter(f => !f.startsWith('.'));
return {
content: [{
type: 'text',
text: filtered.join('\n'),
}],
};
}
@MCPResource({
uriPattern: 'file://read',
name: 'Read File',
description: 'Read file contents by path',
mimeType: 'text/plain',
})
async readFile(uri: string) {
const url = new URL(uri);
const filePath = url.searchParams.get('path')!;
const content = await fs.readFile(filePath, 'utf-8');
return {
contents: [{
uri,
text: content,
mimeType: 'text/plain',
}],
};
}
}
// Bootstrap application
const app = await Rikta.create({ port: 3000 });
// Register MCP server
await registerMCPServer(app, {
serverInfo: { name: 'file-server', version: '1.0.0' },
instructions: 'A file system server for reading and listing files',
enableSSE: true,
});
await app.listen();
// MCP available at http://localhost:3000/mcpDecorators
@MCPTool
Marks a method as an MCP tool that AI assistants can invoke.
@Injectable()
class CalculatorService {
@MCPTool({
name: 'calculate',
description: 'Perform a calculation',
inputSchema: z.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
a: z.number(),
b: z.number(),
}),
})
async calculate(params: { operation: string; a: number; b: number }) {
let result: number;
switch (params.operation) {
case 'add': result = params.a + params.b; break;
case 'subtract': result = params.a - params.b; break;
case 'multiply': result = params.a * params.b; break;
case 'divide': result = params.a / params.b; break;
default: throw new Error('Unknown operation');
}
return {
content: [{ type: 'text', text: `Result: ${result}` }],
};
}
}@MCPResource
Marks a method as an MCP resource provider.
@Injectable()
class DatabaseService {
@MCPResource({
uriPattern: 'db://users',
name: 'Users Database',
description: 'Query users from the database',
mimeType: 'application/json',
})
async getUsers(uri: string) {
const url = new URL(uri);
const limit = parseInt(url.searchParams.get('limit') || '10');
const users = await this.userRepository.find({ take: limit });
return {
contents: [{
uri,
text: JSON.stringify(users, null, 2),
mimeType: 'application/json',
}],
};
}
}@MCPPrompt
Marks a method as an MCP prompt template.
@Injectable()
class PromptService {
@MCPPrompt({
name: 'code_review',
description: 'Generate a code review prompt',
arguments: [
{ name: 'language', description: 'Programming language', required: true },
{ name: 'code', description: 'Code to review', required: true },
{ name: 'focus', description: 'Specific areas to focus on' },
],
})
async codeReview(args: { language: string; code: string; focus?: string }) {
const focusArea = args.focus ? `\nFocus on: ${args.focus}` : '';
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please review this ${args.language} code:${focusArea}\n\n\`\`\`${args.language}\n${args.code}\n\`\`\``,
},
},
],
};
}
}Zod Schema Examples
The package uses Zod for schema validation, consistent with the rest of the Rikta ecosystem:
import { z } from '@riktajs/mcp'; // Re-exported for convenience
// Simple string with description
const pathSchema = z.string().describe('File path to read');
// Object with optional fields
const optionsSchema = z.object({
path: z.string().describe('Directory path'),
recursive: z.boolean().optional().default(false),
maxDepth: z.number().min(1).max(10).optional(),
});
// Enum for fixed values
const operationSchema = z.enum(['read', 'write', 'delete']);
// Array of objects
const filesSchema = z.array(z.object({
name: z.string(),
size: z.number(),
}));HTTP Context
All MCP handlers (tools, resources, prompts) receive an optional context parameter that provides access to the HTTP request and reply objects from Fastify:
import { Injectable } from '@riktajs/core';
import { MCPTool, MCPHandlerContext, z } from '@riktajs/mcp';
@Injectable()
class AuthenticatedService {
@MCPTool({
name: 'get_user_data',
description: 'Get data for the authenticated user',
inputSchema: z.object({
includePrivate: z.boolean().optional(),
}),
})
async getUserData(
params: { includePrivate?: boolean },
context?: MCPHandlerContext
) {
// Access HTTP headers
const authToken = context?.request?.headers.authorization;
// Access query parameters
const sessionId = context?.request?.query.sessionId;
// Set custom response headers
if (context?.reply) {
context.reply.header('X-Request-ID', generateId());
}
// Access request IP
const clientIp = context?.request?.ip;
// Log request details
context?.request?.log.info('Processing user data request');
return {
content: [{
type: 'text',
text: `User data for session ${sessionId}`,
}],
};
}
}Context Properties
The MCPHandlerContext interface provides:
request?: FastifyRequest- The Fastify request objectheaders- HTTP headersquery- Query parametersbody- Request bodyparams- Route parametersip- Client IP addresslog- Request loggeruser- Authenticated user (if using auth)
reply?: FastifyReply- The Fastify reply objectheader()- Set response headersstatus()- Set status codelog- Reply logger
sessionId?: string- SSE session ID (when using SSE)sendNotification?()- Send SSE notifications
Common Use Cases
Authentication
@MCPTool({ name: 'protected_action', description: 'Requires auth' })
async protectedAction(params: any, context?: MCPHandlerContext) {
const token = context?.request?.headers.authorization;
if (!token) {
return {
content: [{ type: 'text', text: 'Unauthorized' }],
isError: true,
};
}
// Verify token and proceed...
}Custom Headers
@MCPResource({ uriPattern: 'data://export', name: 'Export', description: 'Export data' })
async exportData(uri: string, context?: MCPHandlerContext) {
const data = await this.getData();
// Set download headers
if (context?.reply) {
context.reply.header('Content-Disposition', 'attachment; filename="data.json"');
context.reply.header('Content-Type', 'application/json');
}
return {
contents: [{ uri, text: JSON.stringify(data), mimeType: 'application/json' }],
};
}Request Logging
@MCPTool({ name: 'analyze', description: 'Analyze data' })
async analyze(params: any, context?: MCPHandlerContext) {
const logger = context?.request?.log;
logger?.info({ params }, 'Starting analysis');
const result = await this.performAnalysis(params);
logger?.info({ result }, 'Analysis complete');
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
}Configuration
Basic Configuration
await registerMCPServer(app, {
serverInfo: {
name: 'my-mcp-server',
version: '1.0.0',
},
instructions: 'Instructions for AI assistants on how to use this server',
enableSSE: true,
path: '/mcp', // Custom endpoint path
});Redis Configuration (Horizontal Scaling)
await registerMCPServer(app, {
serverInfo: { name: 'scaled-server', version: '1.0.0' },
redis: {
host: 'localhost',
port: 6379,
password: 'your-password', // optional
db: 0, // optional
},
sessionTTL: 3600, // Session TTL in seconds
});Full Configuration Options
interface MCPServerOptions {
// Server identification
serverInfo?: { name: string; version: string };
// Instructions for AI assistants
instructions?: string;
// Enable SSE (Server-Sent Events)
enableSSE?: boolean; // default: true
// Custom endpoint path
path?: string; // default: '/mcp'
// Redis for horizontal scaling
redis?: {
host: string;
port?: number; // default: 6379
password?: string;
db?: number; // default: 0
};
// Session configuration
sessionTTL?: number; // default: 3600
// Heartbeat configuration
heartbeat?: boolean; // default: true
heartbeatInterval?: number; // default: 30000ms
}Endpoints
After registering the MCP server, the following endpoints are available:
| Method | Path | Description |
|--------|------|-------------|
| POST | /mcp | JSON-RPC endpoint for MCP requests |
| GET | /mcp | SSE endpoint for real-time notifications |
Testing with MCP Inspector
You can test your MCP server using the official MCP Inspector:
# Start your server
npm run dev
# In another terminal, run the inspector
npx @modelcontextprotocol/inspector http://localhost:3000/mcpTesting with curl
# Initialize connection
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": { "name": "test", "version": "1.0.0" },
"capabilities": {}
}
}'
# Call a tool
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_files",
"arguments": { "path": "." }
}
}'
# Read a resource
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "resources/read",
"params": {
"uri": "file://read?path=package.json"
}
}'API Reference
registerMCPServer
Main function to register MCP server with a Rikta application.
async function registerMCPServer(
app: RiktaApplication,
options?: MCPServerOptions
): Promise<void>mcpRegistry
Access the MCP registry for advanced use cases.
import { mcpRegistry } from '@riktajs/mcp';
// Get all registered handlers
const tools = mcpRegistry.getTools();
const resources = mcpRegistry.getResources();
const prompts = mcpRegistry.getPrompts();
// Get stats
const stats = mcpRegistry.getStats();
// { tools: 5, resources: 2, prompts: 3 }Zod Re-exports
For convenience, z from Zod is re-exported from this package:
import { z } from '@riktajs/mcp';
const schema = z.object({
name: z.string(),
age: z.number(),
});Utility Functions
import { zodToMCPSchema, isZodSchema } from '@riktajs/mcp';
// Check if a value is a Zod schema
if (isZodSchema(schema)) {
// Convert to JSON Schema for MCP
const jsonSchema = zodToMCPSchema(schema);
}License
MIT
