🐺 Initial commit - Lupul Augmentat MCP Server
- MCP server cu stdio transport pentru performanță maximă
- Tool-uri pentru file operations, HTTP requests, system commands
- Suport NATS pentru comunicare inter-module
- Configurare nginx cu API key auth și SSL
- Arhitectură modulară și extensibilă
🤖 Generated with Claude Code
This commit is contained in:
172
src/transport/HttpServerTransport.ts
Normal file
172
src/transport/HttpServerTransport.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { createLogger } from '../utils/logger';
|
||||
import { config } from '../config';
|
||||
|
||||
const logger = createLogger('HttpServerTransport');
|
||||
|
||||
export class HttpServerTransport implements Transport {
|
||||
private server?: http.Server | https.Server;
|
||||
private connections: Set<http.ServerResponse> = new Set();
|
||||
|
||||
onclose?: () => void;
|
||||
onerror?: (error: Error) => void;
|
||||
onmessage?: (message: JSONRPCMessage) => void;
|
||||
|
||||
constructor(
|
||||
private host: string,
|
||||
private port: number,
|
||||
private options?: https.ServerOptions
|
||||
) {}
|
||||
|
||||
async start(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const requestHandler = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
// CORS headers
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method !== 'POST') {
|
||||
res.writeHead(405);
|
||||
res.end('Method Not Allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check authentication if enabled
|
||||
if (config.security.authEnabled) {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32001, message: 'Unauthorized: Missing or invalid authorization header' },
|
||||
id: null
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, config.security.jwtSecret) as any;
|
||||
// Token is valid, continue processing
|
||||
logger.debug({ userId: decoded.id }, 'Authenticated request');
|
||||
} catch (error) {
|
||||
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32001, message: 'Unauthorized: Invalid token' },
|
||||
id: null
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const message = JSON.parse(body) as JSONRPCMessage;
|
||||
logger.debug({ message }, 'Received JSON-RPC message');
|
||||
|
||||
// Store response for sending reply
|
||||
this.connections.add(res);
|
||||
|
||||
// Pass message to handler
|
||||
if (this.onmessage) {
|
||||
this.onmessage(message);
|
||||
}
|
||||
|
||||
// Wait for response (simplified - in production would use message correlation)
|
||||
setTimeout(() => {
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end('{}');
|
||||
}
|
||||
this.connections.delete(res);
|
||||
}, 30000); // 30 second timeout
|
||||
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'Failed to parse JSON-RPC message');
|
||||
res.writeHead(400);
|
||||
res.end('Invalid JSON-RPC message');
|
||||
this.connections.delete(res);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (this.options) {
|
||||
this.server = https.createServer(this.options, requestHandler);
|
||||
} else {
|
||||
this.server = http.createServer(requestHandler);
|
||||
}
|
||||
|
||||
this.server.listen(this.port, this.host, () => {
|
||||
logger.info({ host: this.host, port: this.port }, 'HTTP transport started');
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.server.on('error', (error) => {
|
||||
logger.error({ error }, 'Server error');
|
||||
if (this.onerror) {
|
||||
this.onerror(error);
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async send(message: JSONRPCMessage): Promise<void> {
|
||||
// Send response to the most recent connection that matches the message ID
|
||||
const messageStr = JSON.stringify(message);
|
||||
|
||||
for (const res of this.connections) {
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(messageStr);
|
||||
this.connections.delete(res);
|
||||
logger.debug({ message }, 'Sent JSON-RPC response');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn({ message }, 'No active connection to send response');
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (this.server) {
|
||||
// Close all active connections
|
||||
for (const res of this.connections) {
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(503);
|
||||
res.end('Server shutting down');
|
||||
}
|
||||
}
|
||||
this.connections.clear();
|
||||
|
||||
this.server.close(() => {
|
||||
logger.info('HTTP transport closed');
|
||||
if (this.onclose) {
|
||||
this.onclose();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user