diff --git a/cloud-wrapper/src/services/mcpService.ts b/cloud-wrapper/src/services/mcpService.ts new file mode 100644 index 00000000..a09c3800 --- /dev/null +++ b/cloud-wrapper/src/services/mcpService.ts @@ -0,0 +1,205 @@ +import { WebSocketServer } from 'ws' +import express from 'express' +import cors from 'cors' +import { BrainyData, BrainyMCPService } from '@soulcraft/brainy' +import { v4 as uuidv4 } from 'uuid' + +/** + * Initialize the MCP service with WebSocket and REST API servers + */ +export function initializeMCPService( + brainy: BrainyData, + options: { + wsPort?: number + restPort?: number + enableAuth?: boolean + apiKeys?: string[] + rateLimit?: { + windowMs: number + maxRequests: number + } + cors?: any + } +) { + // Create the MCP service + const mcpService = new BrainyMCPService(brainy, options) + + // Start WebSocket server if port is provided + if (options.wsPort) { + startWebSocketServer(mcpService, options.wsPort) + } + + // Start REST server if port is provided + if (options.restPort) { + startRESTServer(mcpService, options.restPort, options.cors) + } + + return mcpService +} + +/** + * Start a WebSocket server for the MCP service + */ +function startWebSocketServer(mcpService: BrainyMCPService, port: number) { + const wss = new WebSocketServer({ port }) + + wss.on('connection', (ws: any) => { + ws.on('message', async (message: string) => { + try { + const request = JSON.parse(message) + + // Handle the request using the MCP service + const response = await mcpService.handleMCPRequest(request) + + // Send the response + ws.send(JSON.stringify(response)) + } catch (error) { + // Send error response + ws.send( + JSON.stringify({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + ) + } + }) + }) + + console.log(`MCP WebSocket server started on port ${port}`) + + return wss +} + +/** + * Start a REST server for the MCP service + */ +function startRESTServer( + mcpService: BrainyMCPService, + port: number, + corsOptions?: any +) { + const app = express() + + // Parse JSON request bodies + app.use(express.json()) + + // Enable CORS if configured + if (corsOptions) { + app.use(cors(corsOptions)) + } + + // MCP endpoints + app.post('/mcp/data', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'DATA_ACCESS' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + app.post('/mcp/tools', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'TOOL_EXECUTION' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + app.post('/mcp/system', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'SYSTEM_INFO' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + app.post('/mcp/auth', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + ...req.body, + type: 'AUTHENTICATION' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + // Get available tools + app.get('/mcp/tools', async (req: any, res: any) => { + try { + const response = await mcpService.handleMCPRequest({ + type: 'SYSTEM_INFO', + requestId: uuidv4(), + version: '1.0', + infoType: 'availableTools' + }) + + res.json(response) + } catch (error) { + res.status(500).json({ + success: false, + requestId: uuidv4(), + error: { + code: 'INTERNAL_ERROR', + message: error instanceof Error ? error.message : String(error) + } + }) + } + }) + + // Start the server + const server = app.listen(port, () => { + console.log(`MCP REST API server started on port ${port}`) + }) + + return server +} diff --git a/src/mcp/README.md b/src/mcp/README.md new file mode 100644 index 00000000..051fa368 --- /dev/null +++ b/src/mcp/README.md @@ -0,0 +1,180 @@ +# Model Control Protocol (MCP) for Brainy + +This document provides information about the Model Control Protocol (MCP) implementation in Brainy, which allows external models to access Brainy data and use the augmentation pipeline as tools. + +## Components + +The MCP implementation consists of three main components: + +1. **BrainyMCPAdapter**: Provides access to Brainy data through MCP +2. **MCPAugmentationToolset**: Exposes the augmentation pipeline as tools +3. **BrainyMCPService**: Integrates the adapter and toolset, providing WebSocket and REST server implementations for external model access + +## Environment Compatibility + +### BrainyMCPAdapter + +The `BrainyMCPAdapter` has no environment-specific dependencies and can run in any environment where Brainy itself runs, including: + +- Browser environments +- Node.js environments +- Server environments + +### MCPAugmentationToolset + +The `MCPAugmentationToolset` also has no environment-specific dependencies and can run in any environment where Brainy itself runs, including: + +- Browser environments +- Node.js environments +- Server environments + +### BrainyMCPService + +The `BrainyMCPService` has been refactored to separate the core functionality from the Node.js-specific server functionality: + +1. **Core Functionality**: The core request handling functionality (`handleMCPRequest`) can run in any environment where Brainy itself runs. This is what remains in the main Brainy package. + +2. **Server Functionality**: The WebSocket and REST server functionality has been moved to the cloud-wrapper project to avoid including Node.js-specific dependencies in the browser bundle: + - `ws` for WebSocket server + - `express` for REST API + - `cors` for Cross-Origin Resource Sharing + +This separation ensures that the browser bundle remains lightweight and doesn't include unnecessary Node.js-specific dependencies. In browser or other environments, you can still use the core functionality through the `handleMCPRequest` method. + +## Usage + +### In Any Environment (Browser, Node.js, Server) + +```typescript +import { BrainyData, BrainyMCPAdapter, MCPAugmentationToolset } from '@soulcraft/brainy' + +// Create a BrainyData instance +const brainyData = new BrainyData() +await brainyData.init() + +// Create an MCP adapter +const adapter = new BrainyMCPAdapter(brainyData) + +// Create a toolset +const toolset = new MCPAugmentationToolset() + +// Use the adapter to access Brainy data +const response = await adapter.handleRequest({ + type: 'data_access', + operation: 'search', + requestId: adapter.generateRequestId(), + version: '1.0.0', + parameters: { + query: 'example query', + k: 5 + } +}) + +// Use the toolset to execute augmentation pipeline tools +const toolResponse = await toolset.handleRequest({ + type: 'tool_execution', + toolName: 'brainy_memory_storeData', + requestId: toolset.generateRequestId(), + version: '1.0.0', + parameters: { + args: ['key1', { some: 'data' }] + } +}) +``` + +### In Node.js Environment with Server Functionality + +To use the MCP service with WebSocket and REST server functionality, you should use the cloud-wrapper project: + +```typescript +import { BrainyData } from '@soulcraft/brainy' +import { initializeBrainy } from './services/brainyService.js' +import { initializeMCPService } from './services/mcpService.js' + +// Initialize Brainy +const brainyData = await initializeBrainy() + +// Initialize MCP service with WebSocket and REST server functionality +const mcpService = initializeMCPService(brainyData, { + wsPort: 8080, + restPort: 3000, + enableAuth: true, + apiKeys: ['your-api-key'], + rateLimit: { + maxRequests: 100, + windowMs: 60000 // 1 minute + }, + cors: { + origin: '*', + credentials: true + } +}) +``` + +Alternatively, you can configure the MCP service using environment variables in the cloud-wrapper: + +``` +# MCP configuration +MCP_WS_PORT=8080 +MCP_REST_PORT=3000 +MCP_ENABLE_AUTH=true +MCP_API_KEYS=your-api-key,another-key +MCP_RATE_LIMIT_REQUESTS=100 +MCP_RATE_LIMIT_WINDOW_MS=60000 +MCP_ENABLE_CORS=true +``` + +### In Browser Environment (Core Functionality Only) + +```typescript +import { BrainyData, BrainyMCPService } from '@soulcraft/brainy' + +// Create a BrainyData instance +const brainyData = new BrainyData() +await brainyData.init() + +// Create an MCP service (server functionality will be disabled in browser) +const mcpService = new BrainyMCPService(brainyData) + +// Use the core functionality +const response = await mcpService.handleMCPRequest({ + type: 'data_access', + operation: 'search', + requestId: mcpService.generateRequestId(), + version: '1.0.0', + parameters: { + query: 'example query', + k: 5 + } +}) +``` + +## Cloud Wrapper Integration + +The MCP service's server functionality has been integrated directly into the cloud-wrapper project. The cloud-wrapper automatically initializes the MCP service if the appropriate environment variables are set: + +``` +# MCP configuration +MCP_WS_PORT=8080 +MCP_REST_PORT=3000 +MCP_ENABLE_AUTH=true +MCP_API_KEYS=your-api-key,another-key +MCP_RATE_LIMIT_REQUESTS=100 +MCP_RATE_LIMIT_WINDOW_MS=60000 +MCP_ENABLE_CORS=true +``` + +You can deploy the cloud wrapper to various cloud platforms using the npm scripts from the root directory: + +```bash +# Deploy to AWS Lambda and API Gateway +npm run deploy:cloud:aws + +# Deploy to Google Cloud Run +npm run deploy:cloud:gcp + +# Deploy to Cloudflare Workers +npm run deploy:cloud:cloudflare +``` + +The cloud wrapper is specifically designed for server environments and includes additional features like logging, security headers, and deployment scripts for various cloud providers. See the [Cloud Wrapper README](../../cloud-wrapper/README.md) for detailed configuration instructions and API documentation. diff --git a/src/mcp/brainyMCPAdapter.ts b/src/mcp/brainyMCPAdapter.ts new file mode 100644 index 00000000..1ffcd853 --- /dev/null +++ b/src/mcp/brainyMCPAdapter.ts @@ -0,0 +1,203 @@ +/** + * BrainyMCPAdapter + * + * This class provides an adapter for accessing Brainy data through the Model Control Protocol (MCP). + * It wraps a BrainyData instance and exposes methods for getting vectors, searching similar items, + * and getting relationships. + */ + +import { v4 as uuidv4 } from 'uuid' +import { BrainyDataInterface } from '../types/brainyDataInterface.js' +import { + MCPRequest, + MCPResponse, + MCPDataAccessRequest, + MCPRequestType, + MCP_VERSION +} from '../types/mcpTypes.js' + +export class BrainyMCPAdapter { + private brainyData: BrainyDataInterface + + /** + * Creates a new BrainyMCPAdapter + * @param brainyData The BrainyData instance to wrap + */ + constructor(brainyData: BrainyDataInterface) { + this.brainyData = brainyData + } + + /** + * Handles an MCP data access request + * @param request The MCP request + * @returns An MCP response + */ + async handleRequest(request: MCPDataAccessRequest): Promise { + try { + switch (request.operation) { + case 'get': + return await this.handleGetRequest(request) + case 'search': + return await this.handleSearchRequest(request) + case 'add': + return await this.handleAddRequest(request) + case 'getRelationships': + return await this.handleGetRelationshipsRequest(request) + default: + return this.createErrorResponse( + request.requestId, + 'UNSUPPORTED_OPERATION', + `Operation ${request.operation} is not supported` + ) + } + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Handles a get request + * @param request The MCP request + * @returns An MCP response + */ + private async handleGetRequest(request: MCPDataAccessRequest): Promise { + const { id } = request.parameters + + if (!id) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "id" is required' + ) + } + + const noun = await this.brainyData.get(id) + + if (!noun) { + return this.createErrorResponse( + request.requestId, + 'NOT_FOUND', + `No noun found with id ${id}` + ) + } + + return this.createSuccessResponse(request.requestId, noun) + } + + /** + * Handles a search request + * @param request The MCP request + * @returns An MCP response + */ + private async handleSearchRequest(request: MCPDataAccessRequest): Promise { + const { query, k = 10 } = request.parameters + + if (!query) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "query" is required' + ) + } + + const results = await this.brainyData.searchText(query, k) + return this.createSuccessResponse(request.requestId, results) + } + + /** + * Handles an add request + * @param request The MCP request + * @returns An MCP response + */ + private async handleAddRequest(request: MCPDataAccessRequest): Promise { + const { text, metadata } = request.parameters + + if (!text) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "text" is required' + ) + } + + const id = await this.brainyData.add(text, metadata) + return this.createSuccessResponse(request.requestId, { id }) + } + + /** + * Handles a getRelationships request + * @param request The MCP request + * @returns An MCP response + */ + private async handleGetRelationshipsRequest(request: MCPDataAccessRequest): Promise { + const { id } = request.parameters + + if (!id) { + return this.createErrorResponse( + request.requestId, + 'MISSING_PARAMETER', + 'Parameter "id" is required' + ) + } + + // This is a simplified implementation - in a real implementation, we would + // need to check if these methods exist on the BrainyDataInterface + const outgoing = await (this.brainyData as any).getVerbsBySource?.(id) || [] + const incoming = await (this.brainyData as any).getVerbsByTarget?.(id) || [] + + return this.createSuccessResponse(request.requestId, { outgoing, incoming }) + } + + /** + * Creates a success response + * @param requestId The request ID + * @param data The response data + * @returns An MCP response + */ + private createSuccessResponse(requestId: string, data: any): MCPResponse { + return { + success: true, + requestId, + version: MCP_VERSION, + data + } + } + + /** + * Creates an error response + * @param requestId The request ID + * @param code The error code + * @param message The error message + * @param details Optional error details + * @returns An MCP response + */ + private createErrorResponse( + requestId: string, + code: string, + message: string, + details?: any + ): MCPResponse { + return { + success: false, + requestId, + version: MCP_VERSION, + error: { + code, + message, + details + } + } + } + + /** + * Creates a new request ID + * @returns A new UUID + */ + generateRequestId(): string { + return uuidv4() + } +} diff --git a/src/mcp/brainyMCPService.ts b/src/mcp/brainyMCPService.ts new file mode 100644 index 00000000..a68a3955 --- /dev/null +++ b/src/mcp/brainyMCPService.ts @@ -0,0 +1,347 @@ +/** + * BrainyMCPService + * + * This class provides a unified service for accessing Brainy data and augmentations + * through the Model Control Protocol (MCP). It integrates the BrainyMCPAdapter and + * MCPAugmentationToolset classes and provides WebSocket and REST server implementations + * for external model access. + */ + +import { v4 as uuidv4 } from 'uuid' +import { BrainyDataInterface } from '../types/brainyDataInterface.js' +import { + MCPRequest, + MCPResponse, + MCPDataAccessRequest, + MCPToolExecutionRequest, + MCPSystemInfoRequest, + MCPAuthenticationRequest, + MCPRequestType, + MCPServiceOptions, + MCP_VERSION, + MCPTool +} from '../types/mcpTypes.js' +import { BrainyMCPAdapter } from './brainyMCPAdapter.js' +import { MCPAugmentationToolset } from './mcpAugmentationToolset.js' +import { isBrowser, isNode } from '../utils/environment.js' + +export class BrainyMCPService { + private dataAdapter: BrainyMCPAdapter + private toolset: MCPAugmentationToolset + private options: MCPServiceOptions + private authTokens: Map + private rateLimits: Map + + /** + * Creates a new BrainyMCPService + * @param brainyData The BrainyData instance to wrap + * @param options Configuration options for the service + */ + constructor( + brainyData: BrainyDataInterface, + options: MCPServiceOptions = {} + ) { + this.dataAdapter = new BrainyMCPAdapter(brainyData) + this.toolset = new MCPAugmentationToolset() + this.options = options + this.authTokens = new Map() + this.rateLimits = new Map() + } + + /** + * Handles an MCP request + * @param request The MCP request + * @returns An MCP response + */ + async handleRequest(request: MCPRequest): Promise { + try { + switch (request.type) { + case MCPRequestType.DATA_ACCESS: + return await this.dataAdapter.handleRequest( + request as MCPDataAccessRequest + ) + + case MCPRequestType.TOOL_EXECUTION: + return await this.toolset.handleRequest( + request as MCPToolExecutionRequest + ) + + case MCPRequestType.SYSTEM_INFO: + return await this.handleSystemInfoRequest( + request as MCPSystemInfoRequest + ) + + case MCPRequestType.AUTHENTICATION: + return await this.handleAuthenticationRequest( + request as MCPAuthenticationRequest + ) + + default: + return this.createErrorResponse( + request.requestId, + 'UNSUPPORTED_REQUEST_TYPE', + `Request type ${request.type} is not supported` + ) + } + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Handles a system info request + * @param request The MCP request + * @returns An MCP response + */ + private async handleSystemInfoRequest( + request: MCPSystemInfoRequest + ): Promise { + try { + switch (request.infoType) { + case 'status': + return this.createSuccessResponse(request.requestId, { + status: 'active', + version: MCP_VERSION, + environment: isBrowser() ? 'browser' : isNode() ? 'node' : 'unknown' + }) + + case 'availableTools': + const tools: MCPTool[] = await this.toolset.getAvailableTools() + return this.createSuccessResponse(request.requestId, tools) + + case 'version': + return this.createSuccessResponse(request.requestId, { + version: MCP_VERSION + }) + + default: + return this.createErrorResponse( + request.requestId, + 'UNSUPPORTED_INFO_TYPE', + `Info type ${request.infoType} is not supported` + ) + } + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Handles an authentication request + * @param request The MCP request + * @returns An MCP response + */ + private async handleAuthenticationRequest( + request: MCPAuthenticationRequest + ): Promise { + try { + if (!this.options.enableAuth) { + return this.createSuccessResponse(request.requestId, { + authenticated: true, + message: 'Authentication is not enabled' + }) + } + + const { credentials } = request + + // Check API key authentication + if ( + credentials.apiKey && + this.options.apiKeys?.includes(credentials.apiKey) + ) { + const token = this.generateAuthToken('api-user') + return this.createSuccessResponse(request.requestId, { + authenticated: true, + token + }) + } + + // Check username/password authentication + // This is a placeholder - in a real implementation, you would check against a database + if ( + credentials.username === 'admin' && + credentials.password === 'password' + ) { + const token = this.generateAuthToken(credentials.username) + return this.createSuccessResponse(request.requestId, { + authenticated: true, + token + }) + } + + return this.createErrorResponse( + request.requestId, + 'INVALID_CREDENTIALS', + 'Invalid credentials' + ) + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Checks if a request is valid + * @param request The request to check + * @returns Whether the request is valid + */ + private isValidRequest(request: any): boolean { + return ( + request && + typeof request === 'object' && + request.type && + request.requestId && + request.version + ) + } + + /** + * Checks if a request is authenticated + * @param request The request to check + * @returns Whether the request is authenticated + */ + private isAuthenticated(request: MCPRequest): boolean { + if (!this.options.enableAuth) { + return true + } + + return request.authToken ? this.isValidToken(request.authToken) : false + } + + /** + * Checks if a token is valid + * @param token The token to check + * @returns Whether the token is valid + */ + private isValidToken(token: string): boolean { + const tokenInfo = this.authTokens.get(token) + if (!tokenInfo) { + return false + } + + if (tokenInfo.expires < Date.now()) { + this.authTokens.delete(token) + return false + } + + return true + } + + /** + * Generates an authentication token + * @param userId The user ID to associate with the token + * @returns The generated token + */ + private generateAuthToken(userId: string): string { + const token = uuidv4() + const expires = Date.now() + 24 * 60 * 60 * 1000 // 24 hours + + this.authTokens.set(token, { userId, expires }) + + return token + } + + /** + * Checks if a client has exceeded the rate limit + * @param clientId The client ID to check + * @returns Whether the client is within the rate limit + */ + private checkRateLimit(clientId: string): boolean { + if (!this.options.rateLimit) { + return true + } + + const now = Date.now() + const limit = this.rateLimits.get(clientId) + + if (!limit) { + this.rateLimits.set(clientId, { + count: 1, + resetTime: now + this.options.rateLimit.windowMs + }) + return true + } + + if (limit.resetTime < now) { + limit.count = 1 + limit.resetTime = now + this.options.rateLimit.windowMs + return true + } + + if (limit.count >= this.options.rateLimit.maxRequests) { + return false + } + + limit.count++ + return true + } + + /** + * Creates a success response + * @param requestId The request ID + * @param data The response data + * @returns An MCP response + */ + private createSuccessResponse(requestId: string, data: any): MCPResponse { + return { + success: true, + requestId, + version: MCP_VERSION, + data + } + } + + /** + * Creates an error response + * @param requestId The request ID + * @param code The error code + * @param message The error message + * @param details Optional error details + * @returns An MCP response + */ + private createErrorResponse( + requestId: string, + code: string, + message: string, + details?: any + ): MCPResponse { + return { + success: false, + requestId, + version: MCP_VERSION, + error: { + code, + message, + details + } + } + } + + /** + * Creates a new request ID + * @returns A new UUID + */ + generateRequestId(): string { + return uuidv4() + } + + /** + * Handles an MCP request directly (for in-process models) + * @param request The MCP request + * @returns An MCP response + */ + async handleMCPRequest(request: MCPRequest): Promise { + return await this.handleRequest(request) + } +} diff --git a/src/mcp/index.ts b/src/mcp/index.ts new file mode 100644 index 00000000..a7d89dea --- /dev/null +++ b/src/mcp/index.ts @@ -0,0 +1,19 @@ +/** + * Model Control Protocol (MCP) for Brainy + * + * This module provides a Model Control Protocol (MCP) implementation for Brainy, + * allowing external models to access Brainy data and use the augmentation pipeline as tools. + */ + +// Import and re-export the MCP components +import { BrainyMCPAdapter } from './brainyMCPAdapter.js' +import { MCPAugmentationToolset } from './mcpAugmentationToolset.js' +import { BrainyMCPService } from './brainyMCPService.js' + +// Export the MCP components +export { BrainyMCPAdapter } +export { MCPAugmentationToolset } +export { BrainyMCPService } + +// Export the MCP types +export * from '../types/mcpTypes.js' diff --git a/src/mcp/mcpAugmentationToolset.ts b/src/mcp/mcpAugmentationToolset.ts new file mode 100644 index 00000000..1c118e99 --- /dev/null +++ b/src/mcp/mcpAugmentationToolset.ts @@ -0,0 +1,225 @@ +/** + * MCPAugmentationToolset + * + * This class exposes the Brainy augmentation pipeline as tools through the Model Control Protocol (MCP). + * It provides methods for getting available tools and executing tools. + */ + +import { v4 as uuidv4 } from 'uuid' +import { + MCPResponse, + MCPToolExecutionRequest, + MCPTool, + MCP_VERSION +} from '../types/mcpTypes.js' +import { AugmentationType } from '../types/augmentations.js' + +// Import the augmentation pipeline +import { augmentationPipeline } from '../augmentationPipeline.js' + +export class MCPAugmentationToolset { + /** + * Creates a new MCPAugmentationToolset + */ + constructor() { + // No initialization needed + } + + /** + * Handles an MCP tool execution request + * @param request The MCP request + * @returns An MCP response + */ + async handleRequest(request: MCPToolExecutionRequest): Promise { + try { + const { toolName, parameters } = request + + // Extract the augmentation type and method from the tool name + // Tool names are in the format: brainy_{augmentationType}_{method} + const parts = toolName.split('_') + + if (parts.length < 3 || parts[0] !== 'brainy') { + return this.createErrorResponse( + request.requestId, + 'INVALID_TOOL', + `Invalid tool name: ${toolName}. Tool names should be in the format: brainy_{augmentationType}_{method}` + ) + } + + const augmentationType = parts[1] + const method = parts.slice(2).join('_') + + // Validate the augmentation type + if (!this.isValidAugmentationType(augmentationType)) { + return this.createErrorResponse( + request.requestId, + 'INVALID_AUGMENTATION_TYPE', + `Invalid augmentation type: ${augmentationType}` + ) + } + + // Execute the appropriate pipeline based on the augmentation type + const result = await this.executePipeline(augmentationType, method, parameters) + + return this.createSuccessResponse(request.requestId, result) + } catch (error) { + return this.createErrorResponse( + request.requestId, + 'INTERNAL_ERROR', + error instanceof Error ? error.message : String(error) + ) + } + } + + /** + * Gets all available tools + * @returns An array of MCP tools + */ + async getAvailableTools(): Promise { + const tools: MCPTool[] = [] + + // Get all available augmentation types + const augmentationTypes = augmentationPipeline.getAvailableAugmentationTypes() + + for (const type of augmentationTypes) { + // Get all augmentations of this type + const augmentations = augmentationPipeline.getAugmentationsByType(type) + + for (const augmentation of augmentations) { + // Get all methods of this augmentation (excluding private methods and base methods) + const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(augmentation)) + .filter(method => + !method.startsWith('_') && + method !== 'constructor' && + method !== 'initialize' && + method !== 'shutDown' && + method !== 'getStatus' && + typeof augmentation[method] === 'function' + ) + + // Create a tool for each method + for (const method of methods) { + tools.push(this.createToolDefinition(type, augmentation.name, method)) + } + } + } + + return tools + } + + /** + * Creates a tool definition + * @param type The augmentation type + * @param augmentationName The augmentation name + * @param method The method name + * @returns An MCP tool definition + */ + private createToolDefinition(type: string, augmentationName: string, method: string): MCPTool { + return { + name: `brainy_${type}_${method}`, + description: `Access to Brainy's ${type} augmentation '${augmentationName}' method '${method}'`, + parameters: { + type: 'object', + properties: { + args: { + type: 'array', + description: `Arguments for the ${method} method` + }, + options: { + type: 'object', + description: 'Optional execution options' + } + }, + required: ['args'] + } + } + } + + /** + * Executes the appropriate pipeline based on the augmentation type + * @param type The augmentation type + * @param method The method to execute + * @param parameters The parameters for the method + * @returns The result of the pipeline execution + */ + private async executePipeline(type: string, method: string, parameters: any): Promise { + const { args = [], options = {} } = parameters + + switch (type) { + case AugmentationType.SENSE: + return await augmentationPipeline.executeSensePipeline(method, args, options) + case AugmentationType.CONDUIT: + return await augmentationPipeline.executeConduitPipeline(method, args, options) + case AugmentationType.COGNITION: + return await augmentationPipeline.executeCognitionPipeline(method, args, options) + case AugmentationType.MEMORY: + return await augmentationPipeline.executeMemoryPipeline(method, args, options) + case AugmentationType.PERCEPTION: + return await augmentationPipeline.executePerceptionPipeline(method, args, options) + case AugmentationType.DIALOG: + return await augmentationPipeline.executeDialogPipeline(method, args, options) + case AugmentationType.ACTIVATION: + return await augmentationPipeline.executeActivationPipeline(method, args, options) + default: + throw new Error(`Unsupported augmentation type: ${type}`) + } + } + + /** + * Checks if an augmentation type is valid + * @param type The augmentation type to check + * @returns Whether the augmentation type is valid + */ + private isValidAugmentationType(type: string): boolean { + return Object.values(AugmentationType).includes(type as AugmentationType) + } + + /** + * Creates a success response + * @param requestId The request ID + * @param data The response data + * @returns An MCP response + */ + private createSuccessResponse(requestId: string, data: any): MCPResponse { + return { + success: true, + requestId, + version: MCP_VERSION, + data + } + } + + /** + * Creates an error response + * @param requestId The request ID + * @param code The error code + * @param message The error message + * @param details Optional error details + * @returns An MCP response + */ + private createErrorResponse( + requestId: string, + code: string, + message: string, + details?: any + ): MCPResponse { + return { + success: false, + requestId, + version: MCP_VERSION, + error: { + code, + message, + details + } + } + } + + /** + * Creates a new request ID + * @returns A new UUID + */ + generateRequestId(): string { + return uuidv4() + } +} diff --git a/src/types/mcpTypes.ts b/src/types/mcpTypes.ts new file mode 100644 index 00000000..965dcc50 --- /dev/null +++ b/src/types/mcpTypes.ts @@ -0,0 +1,149 @@ +/** + * Model Control Protocol (MCP) Types + * + * This file defines the types and interfaces for the Model Control Protocol (MCP) + * implementation in Brainy. MCP allows external models to access Brainy data and + * use the augmentation pipeline as tools. + */ + +/** + * MCP version information + */ +export const MCP_VERSION = '1.0.0' + +/** + * MCP request types + */ +export enum MCPRequestType { + DATA_ACCESS = 'data_access', + TOOL_EXECUTION = 'tool_execution', + SYSTEM_INFO = 'system_info', + AUTHENTICATION = 'authentication' +} + +/** + * Base interface for all MCP requests + */ +export interface MCPRequest { + /** The type of request */ + type: MCPRequestType + /** Request ID for tracking and correlation */ + requestId: string + /** API version */ + version: string + /** Authentication token (if required) */ + authToken?: string +} + +/** + * Interface for data access requests + */ +export interface MCPDataAccessRequest extends MCPRequest { + type: MCPRequestType.DATA_ACCESS + /** The data access operation to perform */ + operation: 'get' | 'search' | 'add' | 'getRelationships' + /** Parameters for the operation */ + parameters: Record +} + +/** + * Interface for tool execution requests + */ +export interface MCPToolExecutionRequest extends MCPRequest { + type: MCPRequestType.TOOL_EXECUTION + /** The name of the tool to execute */ + toolName: string + /** Parameters for the tool */ + parameters: Record +} + +/** + * Interface for system info requests + */ +export interface MCPSystemInfoRequest extends MCPRequest { + type: MCPRequestType.SYSTEM_INFO + /** The type of information to retrieve */ + infoType: 'status' | 'availableTools' | 'version' +} + +/** + * Interface for authentication requests + */ +export interface MCPAuthenticationRequest extends MCPRequest { + type: MCPRequestType.AUTHENTICATION + /** The authentication credentials */ + credentials: { + apiKey?: string + username?: string + password?: string + } +} + +/** + * Base interface for all MCP responses + */ +export interface MCPResponse { + /** Whether the request was successful */ + success: boolean + /** The request ID from the original request */ + requestId: string + /** API version */ + version: string + /** Response data (if successful) */ + data?: any + /** Error information (if unsuccessful) */ + error?: { + code: string + message: string + details?: any + } +} + +/** + * Interface for MCP tool definitions + */ +export interface MCPTool { + /** The name of the tool */ + name: string + /** A description of what the tool does */ + description: string + /** The parameters the tool accepts */ + parameters: { + type: 'object' + properties: Record + required: string[] + } +} + +/** + * Configuration options for MCP services + */ +export interface MCPServiceOptions { + /** Port for the WebSocket server */ + wsPort?: number + /** Port for the REST server */ + restPort?: number + /** Whether to enable authentication */ + enableAuth?: boolean + /** API keys for authentication */ + apiKeys?: string[] + /** Rate limiting configuration */ + rateLimit?: { + /** Maximum number of requests per window */ + maxRequests: number + /** Time window in milliseconds */ + windowMs: number + } + /** CORS configuration for REST API */ + cors?: { + /** Allowed origins */ + origin: string | string[] + /** Whether to allow credentials */ + credentials: boolean + } +}