mushcode-mcp-server
Version:
A specialized Model Context Protocol server for MUSHCODE development assistance. Provides AI-powered code generation, validation, optimization, and examples for MUD development.
211 lines • 8.95 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import cors from 'cors';
import { MushcodeProtocolHandler } from './protocol.js';
import { ListToolsRequestSchema, CallToolRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
export class NetworkMCPServer {
app;
server;
protocolHandler;
configManager;
port;
constructor(configManager, port = 3001) {
this.configManager = configManager;
this.port = port;
this.app = express();
this.protocolHandler = new MushcodeProtocolHandler(configManager);
this.setupExpress();
}
setupExpress() {
// Enable CORS for cross-origin requests
this.app.use(cors({
origin: process.env['ALLOWED_ORIGINS']?.split(',') || '*',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Cache-Control', 'X-API-Key'],
}));
// Parse JSON bodies
this.app.use(express.json({ limit: '10mb' }));
// Basic API key authentication middleware (if API_KEY is set)
if (process.env['API_KEY']) {
this.app.use('/api', (req, res, next) => {
const apiKey = req.headers['x-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
if (!apiKey || apiKey !== process.env['API_KEY']) {
return res.status(401).json({
success: false,
error: 'Invalid or missing API key',
timestamp: new Date().toISOString()
});
}
return next();
});
}
// Rate limiting middleware (basic implementation)
if (process.env['ENABLE_RATE_LIMITING'] === 'true') {
const rateLimitMap = new Map();
const windowMs = parseInt(process.env['RATE_LIMIT_WINDOW_MS'] || '60000');
const maxRequests = parseInt(process.env['RATE_LIMIT_MAX_REQUESTS'] || '100');
this.app.use('/api', (req, res, next) => {
const clientId = req.ip || 'unknown';
const now = Date.now();
const windowStart = now - windowMs;
// Clean old entries
if (rateLimitMap.has(clientId)) {
const existingRequests = rateLimitMap.get(clientId);
if (existingRequests) {
const requests = existingRequests.filter((time) => time > windowStart);
rateLimitMap.set(clientId, requests);
}
}
const requests = rateLimitMap.get(clientId) || [];
if (requests.length >= maxRequests) {
const oldestRequest = requests[0];
if (oldestRequest) {
return res.status(429).json({
success: false,
error: 'Rate limit exceeded',
retryAfter: Math.ceil((oldestRequest + windowMs - now) / 1000),
timestamp: new Date().toISOString()
});
}
}
requests.push(now);
rateLimitMap.set(clientId, requests);
return next();
});
}
// Health check endpoint
this.app.get('/health', (_req, res) => {
res.json({
status: 'healthy',
server: this.configManager.getConfig().server.name,
version: this.configManager.getConfig().server.version,
timestamp: new Date().toISOString()
});
});
// MCP over Server-Sent Events endpoint
this.app.get('/sse', async (_req, res) => {
const transport = new SSEServerTransport('/sse', res);
// Create a new server instance for this connection
const config = this.configManager.getConfig();
const server = new Server({
name: config.server.name,
version: config.server.version,
}, {
capabilities: {
tools: {
listChanged: false,
},
},
});
// Set up the same handlers as the protocol handler
await this.setupMCPHandlers(server);
try {
await server.connect(transport);
console.log('New SSE MCP connection established');
}
catch (error) {
console.error('Failed to establish SSE MCP connection:', error);
res.status(500).json({ error: 'Failed to establish MCP connection' });
}
});
// REST API endpoint for direct tool calls (alternative to MCP)
this.app.post('/api/tools/:toolName', async (req, res) => {
try {
const { toolName } = req.params;
const { arguments: args } = req.body;
if (!toolName) {
return res.status(400).json({
success: false,
error: 'Tool name is required',
timestamp: new Date().toISOString()
});
}
// Get the registry from protocol handler
const registry = this.protocolHandler.getRegistry();
const result = await registry.callTool(toolName, args || {});
return res.json({
success: true,
result: result,
timestamp: new Date().toISOString()
});
}
catch (error) {
console.error('Tool execution error:', error);
return res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString()
});
}
});
// List available tools
this.app.get('/api/tools', async (_req, res) => {
try {
const registry = this.protocolHandler.getRegistry();
const tools = registry.getTools();
return res.json({
success: true,
tools: tools,
timestamp: new Date().toISOString()
});
}
catch (error) {
console.error('Failed to list tools:', error);
return res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString()
});
}
});
}
async setupMCPHandlers(server) {
// Import the same handler setup logic from the protocol handler
// This is a simplified version - in practice, you'd want to extract
// the handler setup into a shared utility
const registry = this.protocolHandler.getRegistry();
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: registry.getTools() };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const result = await registry.callTool(name, args || {});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
});
}
async start() {
// Initialize the protocol handler tools first
await this.protocolHandler.registerDefaultTools();
return new Promise((resolve, reject) => {
this.server = this.app.listen(this.port, () => {
console.log(`Network MCP Server running on port ${this.port}`);
console.log(`Health check: http://localhost:${this.port}/health`);
console.log(`MCP over SSE: http://localhost:${this.port}/sse`);
console.log(`REST API: http://localhost:${this.port}/api/tools`);
resolve();
});
this.server.on('error', (error) => {
reject(error);
});
});
}
async stop() {
if (this.server) {
return new Promise((resolve) => {
this.server.close(() => {
console.log('Network MCP Server stopped');
resolve();
});
});
}
}
}
//# sourceMappingURL=network-transport.js.map