UNPKG

@mseep/atlas-mcp-server

Version:

A Model Context Protocol (MCP) server for ATLAS, a Neo4j-powered task management system for LLM Agents - implementing a three-tier architecture (Projects, Tasks, Knowledge) to manage complex workflows.

126 lines (125 loc) 4.33 kB
import { McpError, BaseErrorCode } from '../types/errors.js'; import { logger } from './logger.js'; class RateLimiter { constructor(config) { this.config = config; this.limits = new Map(); } check(key) { const now = Date.now(); const entry = this.limits.get(key); if (!entry || now >= entry.resetTime) { // Reset or create new entry this.limits.set(key, { count: 1, resetTime: now + this.config.windowMs }); return; } if (entry.count >= this.config.maxRequests) { const waitTime = Math.ceil((entry.resetTime - now) / 1000); throw new McpError(BaseErrorCode.RATE_LIMITED, `Rate limit exceeded. Please try again in ${waitTime} seconds.`, { waitTime }); } entry.count++; } } // Create default rate limiter instance export const rateLimiter = new RateLimiter({ windowMs: 15 * 60 * 1000, // 15 minutes maxRequests: 100 // 100 requests per window }); let securityConfig = { authRequired: false // Default to optional authentication }; export const configureSecurity = (config) => { securityConfig = { ...securityConfig, ...config }; }; export const checkPermission = (context = {}, requiredPermission) => { // Skip authentication check if not required if (!securityConfig.authRequired) { return; } const user = context.user; if (!user?.id) { throw new McpError(BaseErrorCode.UNAUTHORIZED, 'Authentication required', { requiredPermission }); } if (!user.permissions?.includes(requiredPermission)) { throw new McpError(BaseErrorCode.UNAUTHORIZED, `Missing required permission: ${requiredPermission}`, { requiredPermission }); } }; // Input sanitization utilities export const sanitizeInput = { // Remove potentially dangerous characters from strings string: (input) => { return input.replace(/[<>]/g, ''); }, // Sanitize URLs url: (input) => { try { const url = new URL(input); // Only allow http and https protocols if (!['http:', 'https:'].includes(url.protocol)) { throw new Error('Invalid protocol'); } return url.toString(); } catch (error) { throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid URL format', { input }); } }, // Sanitize file paths path: (input) => { // Remove path traversal attempts return input.replace(/\.\./g, ''); } }; // Request tracing export const createRequestContext = () => { const requestId = crypto.randomUUID(); const timestamp = new Date().toISOString(); return { requestId, timestamp }; }; // Middleware creator for tools export const createToolMiddleware = (toolName) => { return async (handler, input, context = {}) => { const requestContext = createRequestContext(); const contextWithRequest = { ...context, requestContext }; try { // Rate limiting rateLimiter.check(`${toolName}:${context.user?.id || 'anonymous'}`); logger.info(`Tool execution started: ${toolName}`, { requestId: requestContext.requestId, timestamp: requestContext.timestamp, input }); const result = await handler(input, contextWithRequest); logger.info(`Tool execution completed: ${toolName}`, { requestId: requestContext.requestId, timestamp: requestContext.timestamp }); return result; } catch (error) { logger.error(`Tool execution failed: ${toolName}`, { requestId: requestContext.requestId, timestamp: requestContext.timestamp, error }); if (error instanceof McpError) { return error.toResponse(); } // Handle unknown errors const unknownError = error; return new McpError(BaseErrorCode.INTERNAL_ERROR, 'An unexpected error occurred', { message: unknownError.message || String(error) }).toResponse(); } }; };