@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
JavaScript
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();
}
};
};