import { createServer, IncomingMessage, ServerResponse } from 'http';
import { URL } from 'url';
import { RAGService } from '../rag/rag-service.js';
import { FileService } from '../connectors/file-service.js';
import { WebService } from '../connectors/web-service.js';
import { TaskService } from '../connectors/task-service.js';
export class HttpTransport {
private httpServer: any;
private port: number;
private ragService: RAGService;
private fileService: FileService;
private webService: WebService;
private taskService: TaskService;
constructor(port: number = 3333) {
this.port = port;
this.ragService = new RAGService();
this.fileService = new FileService();
this.webService = new WebService();
this.taskService = new TaskService();
}
async connect(): Promise<void> {
this.httpServer = createServer(this.handleRequest.bind(this));
return new Promise((resolve, reject) => {
this.httpServer.listen(this.port, () => {
console.error(`🌐 HTTP MCP сервер запущен на порту ${this.port}`);
resolve();
});
this.httpServer.on('error', (error: any) => {
console.error('Ошибка запуска HTTP сервера:', error);
reject(error);
});
});
}
private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
try {
const url = new URL(req.url || '/', `http://${req.headers.host}`);
const path = url.pathname;
// CORS заголовки
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Обработка различных эндпоинтов
switch (path) {
case '/health':
await this.handleHealth(req, res);
break;
case '/tools':
await this.handleListTools(req, res);
break;
case '/call':
await this.handleCallTool(req, res);
break;
case '/status':
await this.handleStatus(req, res);
break;
default:
await this.handleNotFound(req, res);
}
} catch (error) {
console.error('Ошибка обработки HTTP запроса:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Внутренняя ошибка сервера' }));
}
}
private async handleHealth(req: IncomingMessage, res: ServerResponse): Promise<void> {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
service: 'ai-ops-hub',
version: '0.1.0',
timestamp: new Date().toISOString()
}));
}
private async handleListTools(req: IncomingMessage, res: ServerResponse): Promise<void> {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Метод не разрешен' }));
return;
}
try {
// Возвращаем список доступных инструментов
const tools = [
{
name: 'rag_search',
description: 'Поиск по личному корпусу документов',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Поисковый запрос' },
limit: { type: 'number', description: 'Максимальное количество результатов', default: 5 }
},
required: ['query']
}
},
{
name: 'rag_add_document',
description: 'Добавить документ в RAG корпус',
inputSchema: {
type: 'object',
properties: {
uri: { type: 'string', description: 'URI документа' },
content: { type: 'string', description: 'Содержимое документа' },
title: { type: 'string', description: 'Заголовок документа' }
},
required: ['uri', 'content', 'title']
}
},
{
name: 'file_read',
description: 'Читать содержимое файла',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Путь к файлу' }
},
required: ['path']
}
},
{
name: 'file_write',
description: 'Записать содержимое в файл',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Путь к файлу' },
content: { type: 'string', description: 'Содержимое для записи' }
},
required: ['path', 'content']
}
},
{
name: 'web_fetch',
description: 'Получить содержимое веб-страницы',
inputSchema: {
type: 'object',
properties: {
url: { type: 'string', description: 'URL страницы' }
},
required: ['url']
}
},
{
name: 'task_create',
description: 'Создать новую задачу',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Заголовок задачи' },
project: { type: 'string', description: 'Проект' },
due: { type: 'string', description: 'Срок выполнения (YYYY-MM-DD)' }
},
required: ['title']
}
},
{
name: 'task_list',
description: 'Список задач',
inputSchema: {
type: 'object',
properties: {
project: { type: 'string', description: 'Фильтр по проекту' },
status: { type: 'string', description: 'Статус (open/completed)', enum: ['open', 'completed'] }
}
}
}
];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
jsonrpc: '2.0',
id: 'list-tools',
result: { tools }
}));
} catch (error) {
console.error('Ошибка получения списка инструментов:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Ошибка получения инструментов' }));
}
}
private async handleCallTool(req: IncomingMessage, res: ServerResponse): Promise<void> {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Метод не разрешен' }));
return;
}
try {
// Читаем тело запроса
const body = await this.readRequestBody(req);
const { name, arguments: args } = JSON.parse(body);
if (!name) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Имя инструмента обязательно' }));
return;
}
// Вызываем соответствующий сервис
let result;
switch (name) {
case 'rag_search':
result = await this.ragService.search(args.query, args.limit || 5);
break;
case 'rag_add_document':
await this.ragService.addDocument(args.uri, args.content, args.title);
result = { message: 'Документ добавлен' };
break;
case 'file_read':
result = await this.fileService.readFile(args.path);
break;
case 'file_write':
await this.fileService.writeFile(args.path, args.content);
result = { message: 'Файл записан' };
break;
case 'web_fetch':
result = await this.webService.fetchPage(args.url);
break;
case 'task_create':
result = await this.taskService.createTask(args.title, args.project, args.due);
break;
case 'task_list':
result = await this.taskService.listTasks(args.project, args.status);
break;
default:
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: `Неизвестный инструмент: ${name}` }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
jsonrpc: '2.0',
id: 'call-tool',
result: { content: result }
}));
} catch (error) {
console.error('Ошибка вызова инструмента:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Ошибка вызова инструмента' }));
}
}
private async handleStatus(req: IncomingMessage, res: ServerResponse): Promise<void> {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Метод не разрешен' }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
service: 'ai-ops-hub',
version: '0.1.0',
uptime: process.uptime(),
memory: process.memoryUsage(),
timestamp: new Date().toISOString()
}));
}
private async handleNotFound(req: IncomingMessage, res: ServerResponse): Promise<void> {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Эндпоинт не найден',
available: ['/health', '/tools', '/call', '/status']
}));
}
private async readRequestBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
resolve(body);
});
req.on('error', reject);
});
}
async disconnect(): Promise<void> {
if (this.httpServer) {
return new Promise((resolve) => {
this.httpServer.close(() => {
console.error('🌐 HTTP MCP сервер остановлен');
resolve();
});
});
}
}
}