@fractal-mcp/mcp
v0.11.4
Published
Fractal MCP server components and utilities
Readme
@fractal-mcp/mcp
Express server utilities and MCP server implementation for hosting Fractal MCP (Model Context Protocol) servers over HTTP with authentication, component tools, and session management.
Installation
npm install @fractal-mcp/mcpOverview
The @fractal-mcp/mcp package provides a complete solution for hosting MCP servers with Fractal-specific enhancements. It includes:
- FractalMCPServer - Enhanced MCP server with component tool support
- Express Integration - HTTP transport with authentication and session management
- Component Tools - Tools that return React components alongside data
- JWT Authentication - Secure authentication using Fractal's auth service
- Session Management - Automatic session handling for MCP clients
Quick Start
Basic MCP Server
import { FractalMCPServer, startExpressServer } from '@fractal-mcp/mcp';
// Create a Fractal MCP server
const server = new FractalMCPServer({
name: 'my-fractal-server',
version: '1.0.0'
});
// Add a simple tool
server.tool({
name: 'greet',
description: 'Greet a user',
inputSchema: {
name: { type: 'string' }
},
handler: async ({ name }) => {
return `Hello, ${name}!`;
}
});
// Start the server
startExpressServer(server, 3000);
console.log('Server running at http://localhost:3000/mcp');Component Tool Example
import { FractalMCPServer, startExpressServer } from '@fractal-mcp/mcp';
const server = new FractalMCPServer({
name: 'component-server',
version: '1.0.0'
});
// Add a component tool
server.componentTool({
name: 'weather-widget',
description: 'Display weather information',
inputSchema: {
location: { type: 'string' },
units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
componentPath: './components/WeatherWidget.tsx',
handler: async ({ location, units }) => {
// Fetch weather data
const weatherData = await fetchWeather(location, units);
return weatherData;
}
});
startExpressServer(server, 3000);API Reference
FractalMCPServer
Enhanced MCP server with support for regular tools and component tools.
Constructor
new FractalMCPServer(options: { name: string; version: string })Parameters:
options.name: string- Server name identifieroptions.version: string- Server version
Example:
const server = new FractalMCPServer({
name: 'my-server',
version: '2.1.0'
});tool(toolDefinition)
Register a standard MCP tool.
Parameters:
interface FractalTool {
name: string; // Tool identifier
description: string | undefined; // Human-readable description
inputSchema: z.ZodRawShape; // Zod schema for input validation
outputSchema?: z.ZodRawShape; // Optional output schema
handler: (args: any, extra: RequestHandlerExtra) => any; // Tool implementation
}Example:
server.tool({
name: 'calculate',
description: 'Perform mathematical calculations',
inputSchema: {
operation: { type: 'string', enum: ['add', 'subtract', 'multiply', 'divide'] },
a: { type: 'number' },
b: { type: 'number' }
},
outputSchema: {
result: { type: 'number' },
operation: { type: 'string' }
},
handler: async ({ operation, a, b }) => {
let result: number;
switch (operation) {
case 'add': result = a + b; break;
case 'subtract': result = a - b; break;
case 'multiply': result = a * b; break;
case 'divide': result = a / b; break;
default: throw new Error('Invalid operation');
}
return { result, operation };
}
});componentTool(toolDefinition)
Register a component tool that returns both data and a React component.
Parameters:
type FractalComponentTool<TInput, TData> =
| FractalStaticComponentTool<TInput, TData>
| FractalDynamicComponentTool<TInput, TData>;Static Component Tool:
interface FractalStaticComponentTool<TInput, TData> {
name: string;
description: string;
inputSchema: z.ZodRawShape;
outputSchema?: z.ZodRawShape;
price?: number; // Optional pricing information
// Component specification (choose one):
componentPath: string; // Path to unbundled React component
componentBundlePath: string; // Path to bundled component directory
componentBundleHtmlPath: string; // Path to complete HTML file
handler: (args: TInput, extra: RequestHandlerExtra) => Promise<TData>;
}Dynamic Component Tool:
interface FractalDynamicComponentTool<TInput, TData> {
name: string;
description: string;
inputSchema: z.ZodRawShape;
outputSchema?: z.ZodRawShape;
price?: number;
componentHandler: (args: TInput, extra: RequestHandlerExtra) => Promise<WithComponentBundle<TData>>;
}Static Component Tool Example:
server.componentTool({
name: 'user-profile',
description: 'Display user profile information',
inputSchema: {
userId: { type: 'string' }
},
componentPath: './components/UserProfile.tsx',
price: 0.01, // Optional pricing
handler: async ({ userId }) => {
const user = await getUserById(userId);
return {
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar
};
}
});Dynamic Component Tool Example:
server.componentTool({
name: 'dynamic-chart',
description: 'Generate charts based on data type',
inputSchema: {
dataType: { type: 'string', enum: ['bar', 'line', 'pie'] },
data: { type: 'array' }
},
componentHandler: async ({ dataType, data }) => {
// Generate different components based on data type
let componentPath: string;
switch (dataType) {
case 'bar': componentPath = './components/BarChart.tsx'; break;
case 'line': componentPath = './components/LineChart.tsx'; break;
case 'pie': componentPath = './components/PieChart.tsx'; break;
}
// Bundle the component dynamically
const bundle = await getComponentBundle({ componentPath });
return {
data: { chartData: data, type: dataType },
component: bundle
};
}
});connect(transport)
Connect the server to a transport layer.
Parameters:
transport: Transport- MCP transport instance
Returns: Promise<void>
introspect()
Get detailed information about all registered tools.
Returns: Promise<IntrospectionResult>
interface IntrospectionResult {
tools: Array<{
name: string;
description: string | undefined;
inputSchema: any;
outputSchema: any;
}>;
componentTools: Array<{
name: string;
description: string;
inputSchema: any;
outputSchema: any;
}>;
}Example:
const info = await server.introspect();
console.log(`Server has ${info.tools.length} regular tools`);
console.log(`Server has ${info.componentTools.length} component tools`);Express Server Functions
startExpressServer(mcpServer, port?)
Start a complete Express server with authentication, CORS, and MCP handling.
Parameters:
mcpServer: IMcpConnectable- MCP server instanceport?: number- Port to listen on (default: 3000)
Returns: void
Features:
- JWT authentication via Authorization header
- CORS middleware for cross-origin requests
- Session management with
mcp-session-idheader - Health check endpoint at
/health - Hello endpoint at
/hellofor testing authentication
Example:
import { FractalMCPServer, startExpressServer } from '@fractal-mcp/mcp';
const server = new FractalMCPServer({ name: 'my-server', version: '1.0.0' });
// Add tools...
startExpressServer(server, 3000);
// Server available at:
// - http://localhost:3000/mcp (MCP endpoint)
// - http://localhost:3000/health (health check)
// - http://localhost:3000/hello (auth test)makeExpressApp(app, mcpServer)
Create MCP routes on an existing Express app (for custom setups).
Parameters:
app: express.Express- Existing Express applicationmcpServer: IMcpConnectable- MCP server instance
Returns: express.Express - The same app with MCP routes added
Example:
import express from 'express';
import { makeExpressApp, FractalMCPServer } from '@fractal-mcp/mcp';
const app = express();
const server = new FractalMCPServer({ name: 'custom', version: '1.0.0' });
// Add custom middleware
app.use('/api', myApiRoutes);
// Add MCP functionality
makeExpressApp(app, server);
app.listen(3000);defaultCorsMiddleware
Default CORS middleware for cross-origin requests.
Type: (req: Request, res: Response, next: NextFunction) => void
Example:
import express from 'express';
import { defaultCorsMiddleware } from '@fractal-mcp/mcp';
const app = express();
app.use(defaultCorsMiddleware);Component Tool Utilities
registerComponentTool(server, componentTool)
Register a component tool with an MCP server (used internally by FractalMCPServer).
Parameters:
server: McpServer- Standard MCP server instancecomponentTool: FractalComponentTool<any, any>- Component tool definition
Example:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { registerComponentTool } from '@fractal-mcp/mcp';
const server = new McpServer({ name: 'test', version: '1.0.0' });
registerComponentTool(server, {
name: 'my-component',
description: 'Test component',
inputSchema: { message: { type: 'string' } },
componentPath: './TestComponent.tsx',
handler: async ({ message }) => ({ message })
});getComponentBundle(componentTool)
Extract component bundle information from a component tool.
Parameters:
componentTool: FractalComponentTool<any, any>- Component tool definition
Returns: Promise<FractalComponentToolBundle | undefined>
type FractalComponentToolBundle =
| { html: string }
| { jsPath: string; cssPath?: string };Authentication
JWT Authentication
The server automatically validates JWT tokens from the Authorization header:
// Client request headers
{
"Authorization": "Bearer <jwt-token>",
"Content-Type": "application/json"
}Token Requirements:
- Algorithm: RS256
- Issuer: "fractal-auth"
- Audience: "fractal"
- Valid signature from Fractal's JWKS endpoint
Authentication Flow:
- Client sends request with JWT in Authorization header
- Server fetches public key from
https://auth.fractalmcp.com/.well-known/jwks.json - Server verifies token signature and claims
- On success, request proceeds with
req.authcontaining decoded payload - On failure, returns 401 with error details
Testing Authentication
# Test the hello endpoint
curl -H "Authorization: Bearer <your-jwt>" http://localhost:3000/hello
# Expected response:
{
"message": "Hello, user!",
"user": { /* decoded JWT payload */ }
}Session Management
Session Headers
mcp-session-id- Session identifier for maintaining client state- Sessions are automatically created on first
initializerequest - Subsequent requests with same session ID reuse the connection
Session Flow
- Initialize: Client sends initialize request without session ID
- Session Created: Server creates new session and returns session ID
- Subsequent Requests: Client includes session ID in all future requests
- Session Reuse: Server routes requests to existing session transport
Component Tool Development
Component Structure
Components receive data through window.__FRACTAL_DATA__:
// WeatherWidget.tsx
import React from 'react';
interface WeatherData {
location: string;
temperature: number;
conditions: string;
}
export default function WeatherWidget() {
const data = (window as any).__FRACTAL_DATA__ as WeatherData;
return (
<div className="weather-widget">
<h2>Weather in {data.location}</h2>
<p>{data.temperature}°F - {data.conditions}</p>
</div>
);
}Component Tool Types
Static Components:
- Component is known at registration time
- Data is generated by the handler
- Component path specified in tool definition
Dynamic Components:
- Component is determined at runtime
- Both data and component returned by handler
- More flexible but requires runtime bundling
Bundling Options
Development (componentPath):
{
componentPath: './components/MyWidget.tsx'
// Automatically bundles on each request
// Good for development, not recommended for production
}Production (componentBundlePath):
{
componentBundlePath: './dist/my-widget'
// Expects pre-bundled Component.jsx and index.css
// Recommended for production
}Custom HTML (componentBundleHtmlPath):
{
componentBundleHtmlPath: './dist/widget.html'
// Complete HTML file with embedded component
// Maximum control over output
}Error Handling
Tool Errors
server.tool({
name: 'risky-operation',
description: 'An operation that might fail',
inputSchema: { data: { type: 'string' } },
handler: async ({ data }) => {
try {
return await performRiskyOperation(data);
} catch (error) {
throw new Error(`Operation failed: ${error.message}`);
}
}
});Component Tool Errors
server.componentTool({
name: 'error-prone-widget',
description: 'Widget that handles errors gracefully',
inputSchema: { userId: { type: 'string' } },
componentPath: './components/UserWidget.tsx',
handler: async ({ userId }) => {
try {
const user = await fetchUser(userId);
return user;
} catch (error) {
// Component will receive error in data
throw new Error(`Failed to load user: ${error.message}`);
}
}
});Authentication Errors
// 401 Unauthorized responses
{
"error": "missing_token"
}
{
"error": "invalid_token",
"details": "Token has expired"
}Production Deployment
Environment Variables
# Optional: Override JWKS URL for custom auth
JWKS_URL=https://your-auth-service.com/.well-known/jwks.json
# Optional: Custom port
PORT=3000Pre-bundling Components
# Bundle components before deployment
npx @fractal-mcp/cli bundle -e ./components/Widget.tsx -d ./dist/widget
# Use bundled components in production
server.componentTool({
name: 'my-widget',
componentBundlePath: './dist/widget',
// ... rest of configuration
});Health Monitoring
# Health check endpoint
curl http://localhost:3000/health
# Response: {"status": "ok"}
# Use for load balancer health checksScaling Considerations
- Each session maintains its own transport
- Memory usage scales with concurrent sessions
- Consider session cleanup for long-running servers
- Use process managers (PM2, Docker) for production
Integration Examples
With Fractal Client
// Server setup
import { FractalMCPServer, startExpressServer } from '@fractal-mcp/mcp';
const server = new FractalMCPServer({ name: 'api-server', version: '1.0.0' });
server.componentTool({
name: 'data-table',
description: 'Display data in a table',
inputSchema: { query: { type: 'string' } },
componentPath: './components/DataTable.tsx',
handler: async ({ query }) => {
const results = await database.query(query);
return { results, query };
}
});
startExpressServer(server, 3001);
// Client usage
import { FractalSDK } from '@fractal-mcp/client';
const client = new FractalSDK({ apiKey: 'your-key' });
await client.connect();
const result = await client.executeTool('data-table', {
query: 'SELECT * FROM users LIMIT 10'
});
// result contains both data and rendered componentCustom Express Setup
import express from 'express';
import { makeExpressApp, FractalMCPServer, defaultCorsMiddleware } from '@fractal-mcp/mcp';
const app = express();
const server = new FractalMCPServer({ name: 'custom', version: '1.0.0' });
// Custom middleware
app.use(express.json({ limit: '10mb' }));
app.use(defaultCorsMiddleware);
// Custom routes
app.get('/status', (req, res) => {
res.json({ status: 'running', timestamp: Date.now() });
});
// Add MCP functionality
makeExpressApp(app, server);
// Custom error handling
app.use((error, req, res, next) => {
console.error('Server error:', error);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000);Dependencies
@fractal-mcp/bundle- Component bundling utilities@modelcontextprotocol/sdk- MCP protocol implementationexpress- HTTP server frameworkjsonwebtoken- JWT token verificationjwk-to-pem- JWK to PEM conversion for RSA keys
Requirements
- Node.js 18+
- Valid Fractal JWT tokens for authentication
- React components for component tools
