🐺 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:
147
src/registry/ModuleManager.ts
Normal file
147
src/registry/ModuleManager.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { createLogger } from '../utils/logger';
|
||||
import { NatsClient } from '../nats/NatsClient';
|
||||
import { ToolRegistry } from './ToolRegistry';
|
||||
import { ModuleConfig } from '../types';
|
||||
import { config } from '../config';
|
||||
|
||||
const logger = createLogger('ModuleManager');
|
||||
|
||||
export class ModuleManager {
|
||||
private modules = new Map<string, ModuleInfo>();
|
||||
|
||||
constructor(
|
||||
private natsClient: NatsClient,
|
||||
private toolRegistry: ToolRegistry,
|
||||
) {
|
||||
// These will be used when implementing module communication
|
||||
this.natsClient;
|
||||
this.toolRegistry;
|
||||
}
|
||||
|
||||
async startAll(): Promise<void> {
|
||||
// Load module configurations
|
||||
const modulesConfig = await this.loadModuleConfigs();
|
||||
|
||||
for (const moduleConfig of modulesConfig) {
|
||||
try {
|
||||
await this.startModule(moduleConfig);
|
||||
} catch (error) {
|
||||
logger.error({ error, module: moduleConfig.name }, 'Failed to start module');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async stopAll(): Promise<void> {
|
||||
for (const [name, info] of this.modules) {
|
||||
try {
|
||||
await this.stopModule(name, info);
|
||||
} catch (error) {
|
||||
logger.error({ error, module: name }, 'Failed to stop module');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadModuleConfigs(): Promise<ModuleConfig[]> {
|
||||
try {
|
||||
// For now, return empty array - modules will be added later
|
||||
return [];
|
||||
} catch (error) {
|
||||
logger.warn('No modules configuration found');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async startModule(moduleConfig: ModuleConfig): Promise<void> {
|
||||
logger.info({ module: moduleConfig.name }, 'Starting module');
|
||||
|
||||
const token = this.generateModuleToken(moduleConfig);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
MODULE_TOKEN: token,
|
||||
MODULE_NAME: moduleConfig.name,
|
||||
NATS_URL: config.nats.url,
|
||||
};
|
||||
|
||||
const proc = spawn(moduleConfig.executable, [], {
|
||||
env,
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const info: ModuleInfo = {
|
||||
config: moduleConfig,
|
||||
process: proc,
|
||||
status: 'starting',
|
||||
startTime: Date.now(),
|
||||
};
|
||||
|
||||
this.modules.set(moduleConfig.name, info);
|
||||
|
||||
// Setup process handlers
|
||||
proc.stdout?.on('data', (data) => {
|
||||
logger.debug({ module: moduleConfig.name, output: data.toString() }, 'Module output');
|
||||
});
|
||||
|
||||
proc.stderr?.on('data', (data) => {
|
||||
logger.error({ module: moduleConfig.name, error: data.toString() }, 'Module error');
|
||||
});
|
||||
|
||||
proc.on('exit', (code) => {
|
||||
logger.warn({ module: moduleConfig.name, code }, 'Module exited');
|
||||
info.status = 'stopped';
|
||||
});
|
||||
|
||||
// Wait for module to be ready
|
||||
await this.waitForModuleReady(moduleConfig.name, moduleConfig.startupTimeout);
|
||||
}
|
||||
|
||||
private async stopModule(name: string, info: ModuleInfo): Promise<void> {
|
||||
|
||||
logger.info({ module: name }, 'Stopping module');
|
||||
|
||||
info.process.kill('SIGTERM');
|
||||
|
||||
// Wait for graceful shutdown
|
||||
await new Promise<void>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
info.process.kill('SIGKILL');
|
||||
resolve();
|
||||
}, 5000);
|
||||
|
||||
info.process.once('exit', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
this.modules.delete(name);
|
||||
}
|
||||
|
||||
private async waitForModuleReady(name: string, timeout = 5000): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
const info = this.modules.get(name);
|
||||
if (info?.status === 'ready') {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
throw new Error(`Module ${name} failed to start within timeout`);
|
||||
}
|
||||
|
||||
private generateModuleToken(moduleConfig: ModuleConfig): string {
|
||||
// For now, return a simple token - will implement JWT later
|
||||
return `module-token-${moduleConfig.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
interface ModuleInfo {
|
||||
config: ModuleConfig;
|
||||
process: ChildProcess;
|
||||
status: 'starting' | 'ready' | 'stopped';
|
||||
startTime: number;
|
||||
}
|
||||
107
src/registry/ToolRegistry.ts
Normal file
107
src/registry/ToolRegistry.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { createLogger } from '../utils/logger';
|
||||
import { NatsClient } from '../nats/NatsClient';
|
||||
import { ToolDefinition, ToolRequest } from '../types';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { createBuiltinTools, ToolHandler, ToolContext } from '../tools';
|
||||
|
||||
const logger = createLogger('ToolRegistry');
|
||||
|
||||
export class ToolRegistry {
|
||||
private tools = new Map<string, ToolDefinition>();
|
||||
private builtinHandlers = createBuiltinTools();
|
||||
|
||||
constructor(private natsClient: NatsClient) {
|
||||
this.registerBuiltinTools();
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
this.setupDiscovery();
|
||||
}
|
||||
|
||||
private setupDiscovery(): void {
|
||||
// Listen for tool announcements
|
||||
this.natsClient.subscribe('tools.discovery', (data) => {
|
||||
const announcement = data as {
|
||||
module: string;
|
||||
tools: ToolDefinition[];
|
||||
};
|
||||
|
||||
for (const tool of announcement.tools) {
|
||||
this.registerTool(tool);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerTool(tool: ToolDefinition): void {
|
||||
this.tools.set(tool.name, tool);
|
||||
logger.info({ tool: tool.name, module: tool.module }, 'Tool registered');
|
||||
}
|
||||
|
||||
async listTools(): Promise<ToolDefinition[]> {
|
||||
return Array.from(this.tools.values());
|
||||
}
|
||||
|
||||
async executeTool(toolName: string, params: unknown): Promise<unknown> {
|
||||
// Check if it's a built-in tool
|
||||
const builtinHandler = this.builtinHandlers.get(toolName);
|
||||
if (builtinHandler) {
|
||||
return this.executeBuiltinTool(builtinHandler, params);
|
||||
}
|
||||
|
||||
// Otherwise, execute via NATS
|
||||
const tool = this.tools.get(toolName);
|
||||
if (!tool) {
|
||||
throw new Error(`Tool not found: ${toolName}`);
|
||||
}
|
||||
|
||||
const request: ToolRequest = {
|
||||
id: randomUUID(),
|
||||
tool: toolName,
|
||||
method: 'execute',
|
||||
params,
|
||||
timeout: 30000,
|
||||
metadata: {
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
const subject = `tools.${tool.module}.${toolName}.execute`;
|
||||
logger.debug({ subject, request }, 'Executing tool');
|
||||
|
||||
const response = await this.natsClient.request(subject, request);
|
||||
|
||||
if (response.status === 'error' && response.error) {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
private async executeBuiltinTool(handler: ToolHandler, params: unknown): Promise<unknown> {
|
||||
const context: ToolContext = {
|
||||
requestId: randomUUID(),
|
||||
permissions: ['file:read', 'file:write', 'system:exec', 'network:http'], // TODO: get from auth
|
||||
};
|
||||
|
||||
return handler.execute(params, context);
|
||||
}
|
||||
|
||||
getTool(name: string): ToolDefinition | undefined {
|
||||
return this.tools.get(name);
|
||||
}
|
||||
|
||||
private registerBuiltinTools(): void {
|
||||
for (const [name, handler] of this.builtinHandlers) {
|
||||
const tool: ToolDefinition = {
|
||||
name: handler.name,
|
||||
description: handler.description,
|
||||
inputSchema: handler.schema,
|
||||
module: 'builtin',
|
||||
permissions: [],
|
||||
};
|
||||
|
||||
this.tools.set(name, tool);
|
||||
logger.info({ tool: name }, 'Registered built-in tool');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user