il2cpp-dump-analyzer-mcp
Version:
Agentic RAG system for analyzing IL2CPP dump.cs files from Unity games
300 lines • 11.5 kB
JavaScript
"use strict";
/**
* HTTP Transport implementation for IL2CPP MCP Server
* Provides HTTP-based MCP communication for remote access
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpTransport = void 0;
const node_http_1 = require("node:http");
const node_https_1 = require("node:https");
const node_fs_1 = require("node:fs");
const node_crypto_1 = require("node:crypto");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
/**
* Rate limiter for request throttling
*/
class RateLimiter {
constructor(requestsPerMinute) {
this.requestsPerMinute = requestsPerMinute;
this.requests = new Map();
this.windowMs = 60 * 1000; // 1 minute window
}
/**
* Check if request is allowed for the given client
*/
isAllowed(clientId) {
const now = Date.now();
const windowStart = now - this.windowMs;
// Get existing requests for this client
const clientRequests = this.requests.get(clientId) || [];
// Remove old requests outside the window
const validRequests = clientRequests.filter(timestamp => timestamp > windowStart);
// Check if limit exceeded
const allowed = validRequests.length < this.requestsPerMinute;
if (allowed) {
// Add current request
validRequests.push(now);
this.requests.set(clientId, validRequests);
}
return {
allowed,
info: {
remaining: Math.max(0, this.requestsPerMinute - validRequests.length),
limit: this.requestsPerMinute,
resetTime: new Date(now + this.windowMs),
windowStart: new Date(windowStart)
}
};
}
/**
* Clean up old entries
*/
cleanup() {
const now = Date.now();
const windowStart = now - this.windowMs;
for (const [clientId, requests] of this.requests.entries()) {
const validRequests = requests.filter(timestamp => timestamp > windowStart);
if (validRequests.length === 0) {
this.requests.delete(clientId);
}
else {
this.requests.set(clientId, validRequests);
}
}
}
}
/**
* HTTP Transport for MCP Server
*/
class HttpTransport {
constructor(options) {
this.options = options;
this.server = null;
this.transport = null;
this.rateLimiter = new RateLimiter(options.rateLimitRpm);
this.startTime = new Date();
this.metrics = {
totalRequests: 0,
activeConnections: 0,
requestsPerMinute: 0,
averageResponseTime: 0,
errorCount: 0,
bytesSent: 0,
bytesReceived: 0
};
// Start cleanup interval for rate limiter
setInterval(() => this.rateLimiter.cleanup(), 60000); // Clean up every minute
}
/**
* Start the HTTP server
*/
async start() {
try {
// Create MCP transport
this.transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
enableJsonResponse: false, // Use SSE streaming
onsessioninitialized: (sessionId) => {
if (this.options.enableLogging) {
console.log(`New MCP session initialized: ${sessionId}`);
}
}
});
// Create HTTP/HTTPS server
if (this.options.ssl?.enabled) {
const cert = (0, node_fs_1.readFileSync)(this.options.ssl.certPath);
const key = (0, node_fs_1.readFileSync)(this.options.ssl.keyPath);
this.server = (0, node_https_1.createServer)({ cert, key }, this.handleRequest.bind(this));
}
else {
this.server = (0, node_http_1.createServer)(this.handleRequest.bind(this));
}
// Note: Do not start the transport here - the MCP server will handle this
// when server.connect(transport) is called to avoid "Transport already started" error
// Start the server
await new Promise((resolve, reject) => {
this.server.listen(this.options.port, this.options.host, () => {
const protocol = this.options.ssl?.enabled ? 'https' : 'http';
console.log(`🌐 HTTP MCP Server listening on ${protocol}://${this.options.host}:${this.options.port}`);
resolve();
});
this.server.on('error', reject);
});
// Set up connection tracking
this.server.on('connection', () => {
this.metrics.activeConnections++;
});
this.server.on('close', () => {
this.metrics.activeConnections = Math.max(0, this.metrics.activeConnections - 1);
});
}
catch (error) {
throw new Error(`Failed to start HTTP transport: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Stop the HTTP server
*/
async stop() {
if (this.transport) {
await this.transport.close();
this.transport = null;
}
if (this.server) {
await new Promise((resolve) => {
this.server.close(() => resolve());
});
this.server = null;
}
}
/**
* Handle incoming HTTP requests
*/
async handleRequest(req, res) {
const startTime = Date.now();
try {
// Update metrics
this.metrics.totalRequests++;
// Get client information
const clientIp = req.socket.remoteAddress || 'unknown';
const userAgent = req.headers['user-agent'] || 'unknown';
const clientId = `${clientIp}:${userAgent}`;
// Log request if enabled
if (this.options.enableLogging) {
console.log(`${new Date().toISOString()} ${req.method} ${req.url} - ${clientIp}`);
}
// Set CORS headers if enabled
if (this.options.enableCors) {
this.setCorsHeaders(res);
}
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Rate limiting
const rateLimitResult = this.rateLimiter.isAllowed(clientId);
if (!rateLimitResult.allowed) {
res.setHeader('X-RateLimit-Limit', rateLimitResult.info.limit);
res.setHeader('X-RateLimit-Remaining', rateLimitResult.info.remaining);
res.setHeader('X-RateLimit-Reset', rateLimitResult.info.resetTime.toISOString());
res.writeHead(429, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Rate limit exceeded' }));
return;
}
// Authentication if API key is configured
if (this.options.apiKey) {
const authResult = this.authenticateRequest(req);
if (!authResult.valid) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Authentication failed' }));
return;
}
// Add auth info to request
req.auth = authResult.authInfo;
}
// Check request size
const contentLength = parseInt(req.headers['content-length'] || '0', 10);
if (contentLength > this.options.maxRequestSize) {
res.writeHead(413, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Request too large' }));
return;
}
// Parse request body for POST requests
let parsedBody = undefined;
if (req.method === 'POST') {
parsedBody = await this.parseRequestBody(req);
this.metrics.bytesReceived += contentLength;
}
// Handle the request with MCP transport
if (this.transport) {
await this.transport.handleRequest(req, res, parsedBody);
}
else {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Transport not initialized' }));
}
}
catch (error) {
this.metrics.errorCount++;
this.metrics.lastError = new Date();
console.error('HTTP request error:', error);
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Internal server error' }));
}
}
finally {
// Update response time metrics
const responseTime = Date.now() - startTime;
this.metrics.averageResponseTime =
(this.metrics.averageResponseTime * (this.metrics.totalRequests - 1) + responseTime) / this.metrics.totalRequests;
}
}
/**
* Set CORS headers
*/
setCorsHeaders(res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
res.setHeader('Access-Control-Max-Age', '86400'); // 24 hours
}
/**
* Authenticate request using API key
*/
authenticateRequest(req) {
const apiKey = req.headers['x-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
if (!apiKey || apiKey !== this.options.apiKey) {
return { valid: false };
}
return {
valid: true,
authInfo: {
apiKey: apiKey,
clientIp: req.socket.remoteAddress,
userAgent: req.headers['user-agent'],
timestamp: new Date()
}
};
}
/**
* Parse request body
*/
async parseRequestBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const parsed = body ? JSON.parse(body) : {};
resolve(parsed);
}
catch (error) {
reject(new Error('Invalid JSON in request body'));
}
});
req.on('error', reject);
});
}
/**
* Get transport metrics
*/
getMetrics() {
// Calculate requests per minute
const uptimeMinutes = (Date.now() - this.startTime.getTime()) / (1000 * 60);
this.metrics.requestsPerMinute = uptimeMinutes > 0 ? this.metrics.totalRequests / uptimeMinutes : 0;
return { ...this.metrics };
}
/**
* Get MCP transport instance
*/
getTransport() {
return this.transport;
}
}
exports.HttpTransport = HttpTransport;
//# sourceMappingURL=http-transport.js.map