🧠 Brainy 2.0.0 - Zero-Configuration AI Database with Triple Intelligence™
MAJOR RELEASE: Complete evolution of Brainy with groundbreaking features and performance. 🎯 KEY FEATURES: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✨ Triple Intelligence™ Engine - Unified Vector + Metadata + Graph search - O(log n) performance on all operations - 3ms average search latency at any scale ✨ API Consolidation - 15+ search methods → 2 clean APIs - search() for vector similarity - find() for natural language queries ✨ Natural Language Processing - 220+ pre-computed NLP patterns - Instant context understanding - "Show me recent React components with tests" ✨ Zero Configuration - Works instantly, no setup required - Built-in embedding models (no API keys) - Smart defaults for everything - Automatic optimization ✨ Enterprise Features (Free for Everyone) - Scales to 10M+ items - Write-Ahead Logging (WAL) for durability - Distributed architecture with sharding - Read/write separation - Connection pooling & request deduplication - Built-in monitoring & health checks ✨ Universal Compatibility - Node.js, Browser, Edge Workers - 4 Storage Adapters (Memory, FileSystem, OPFS, S3) - TypeScript with full type safety - Worker-based embeddings 📦 WHAT'S INCLUDED: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Core AI Database with HNSW indexing • 19 Production-ready augmentations • Universal Memory Manager • Complete CLI with all commands • Brain Cloud integration (soulcraft.com) • Comprehensive documentation • 52 test files with 400+ tests • Migration guide from 1.x 📊 PERFORMANCE: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Initialize: 450ms (24MB memory) • Search: 3ms average (up to 10M items) • Metadata Filter: 0.8ms (O(log n)) • Bulk Import: 2.3s per 1000 items • Production Scale: 5.8ms at 10M items 🔧 TECHNICAL IMPROVEMENTS: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • TypeScript compilation: 153 errors → 0 • Memory usage: 200MB → 24MB baseline • Circular dependencies resolved • Worker thread communication fixed • Storage adapter consistency • Request coalescing for 3x performance 🛠️ CLI FEATURES: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • brainy add - Smart data ingestion • brainy find - Natural language search • brainy search - Vector similarity • brainy chat - AI conversation mode • brainy cloud - Brain Cloud integration • brainy augment - Manage extensions • 100% API compatibility 📚 DOCUMENTATION: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • Professional README with examples • Quick Start guide (5 minutes) • Enterprise Features guide • Migration guide from 1.x • API reference • Architecture documentation 🌟 USE CASES: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ • AI memory layer for chatbots • Semantic document search • Code intelligence platforms • Knowledge management systems • Real-time recommendation engines • Customer support automation MIT License - Enterprise features included free for everyone. No premium tiers, no paywalls, no limits. Built with ❤️ by the Brainy community. Visit https://soulcraft.com for Brain Cloud integration.
This commit is contained in:
commit
9c87982a7d
301 changed files with 178087 additions and 0 deletions
104
src/mcp/README.md
Normal file
104
src/mcp/README.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# 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 is not included in the main Brainy package to keep the browser bundle lightweight and avoid Node.js-specific dependencies. In browser or other environments, you can 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 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
|
||||
}
|
||||
})
|
||||
```
|
||||
204
src/mcp/brainyMCPAdapter.ts
Normal file
204
src/mcp/brainyMCPAdapter.ts
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* 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 '../universal/uuid.js'
|
||||
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<MCPResponse> {
|
||||
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<MCPResponse> {
|
||||
const { id } = request.parameters
|
||||
|
||||
if (!id) {
|
||||
return this.createErrorResponse(
|
||||
request.requestId,
|
||||
'MISSING_PARAMETER',
|
||||
'Parameter "id" is required'
|
||||
)
|
||||
}
|
||||
|
||||
const noun = await this.brainyData.getNoun(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<MCPResponse> {
|
||||
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<MCPResponse> {
|
||||
const { text, metadata } = request.parameters
|
||||
|
||||
if (!text) {
|
||||
return this.createErrorResponse(
|
||||
request.requestId,
|
||||
'MISSING_PARAMETER',
|
||||
'Parameter "text" is required'
|
||||
)
|
||||
}
|
||||
|
||||
// Add noun directly - addNoun handles string input automatically
|
||||
const id = await this.brainyData.addNoun(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<MCPResponse> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
363
src/mcp/brainyMCPBroadcast.ts
Normal file
363
src/mcp/brainyMCPBroadcast.ts
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
/**
|
||||
* BrainyMCPBroadcast
|
||||
*
|
||||
* Enhanced MCP service with real-time WebSocket broadcasting capabilities
|
||||
* for multi-agent coordination (Jarvis ↔ Picasso communication)
|
||||
*
|
||||
* Features:
|
||||
* - WebSocket server for real-time push notifications
|
||||
* - Subscription management for multiple Claude instances
|
||||
* - Message broadcasting to all connected agents
|
||||
* - Works both locally and with cloud deployment
|
||||
*/
|
||||
|
||||
import { WebSocketServer, WebSocket } from 'ws'
|
||||
import { createServer, IncomingMessage } from 'http'
|
||||
import { BrainyMCPService } from './brainyMCPService.js'
|
||||
import { BrainyDataInterface } from '../types/brainyDataInterface.js'
|
||||
import { MCPServiceOptions } from '../types/mcpTypes.js'
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js'
|
||||
|
||||
interface BroadcastMessage {
|
||||
id: string
|
||||
from: string
|
||||
to?: string | string[]
|
||||
type: 'message' | 'notification' | 'sync' | 'heartbeat' | 'identify'
|
||||
event?: string
|
||||
data: any
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
interface ConnectedAgent {
|
||||
id: string
|
||||
name: string
|
||||
role: string
|
||||
socket: WebSocket
|
||||
lastSeen: number
|
||||
}
|
||||
|
||||
export class BrainyMCPBroadcast extends BrainyMCPService {
|
||||
private wsServer?: WebSocketServer
|
||||
private httpServer?: any
|
||||
private agents: Map<string, ConnectedAgent> = new Map()
|
||||
private messageHistory: BroadcastMessage[] = []
|
||||
private maxHistorySize = 100
|
||||
|
||||
constructor(
|
||||
brainyData: BrainyDataInterface,
|
||||
options: MCPServiceOptions & {
|
||||
broadcastPort?: number
|
||||
cloudUrl?: string
|
||||
} = {}
|
||||
) {
|
||||
super(brainyData, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the WebSocket broadcast server
|
||||
* @param port Port to listen on (default: 8765)
|
||||
* @param isCloud Whether this is a cloud deployment
|
||||
*/
|
||||
async startBroadcastServer(port = 8765, isCloud = false): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Create HTTP server
|
||||
this.httpServer = createServer((req, res) => {
|
||||
// Health check endpoint
|
||||
if (req.url === '/health') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||||
res.end(JSON.stringify({
|
||||
status: 'healthy',
|
||||
agents: Array.from(this.agents.values()).map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
role: a.role,
|
||||
connected: true
|
||||
})),
|
||||
uptime: process.uptime()
|
||||
}))
|
||||
} else {
|
||||
res.writeHead(404)
|
||||
res.end('Not found')
|
||||
}
|
||||
})
|
||||
|
||||
// Create WebSocket server
|
||||
this.wsServer = new WebSocketServer({
|
||||
server: this.httpServer,
|
||||
perMessageDeflate: false // Better performance
|
||||
})
|
||||
|
||||
this.wsServer.on('connection', (socket, request) => {
|
||||
this.handleNewConnection(socket, request)
|
||||
})
|
||||
|
||||
// Start listening
|
||||
this.httpServer.listen(port, () => {
|
||||
console.log(`🧠 Brain Jar Broadcast Server running on ${isCloud ? 'cloud' : 'local'} port ${port}`)
|
||||
console.log(`📡 WebSocket: ws://localhost:${port}`)
|
||||
console.log(`🔍 Health: http://localhost:${port}/health`)
|
||||
resolve()
|
||||
})
|
||||
|
||||
// Heartbeat to keep connections alive
|
||||
setInterval(() => {
|
||||
this.agents.forEach((agent) => {
|
||||
if (Date.now() - agent.lastSeen > 30000) {
|
||||
// Remove inactive agents
|
||||
this.removeAgent(agent.id)
|
||||
} else {
|
||||
// Send heartbeat
|
||||
this.sendToAgent(agent.id, {
|
||||
id: uuidv4(),
|
||||
from: 'server',
|
||||
type: 'heartbeat',
|
||||
data: { timestamp: Date.now() },
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 15000)
|
||||
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle new WebSocket connection
|
||||
*/
|
||||
private handleNewConnection(socket: WebSocket, request: IncomingMessage) {
|
||||
const agentId = uuidv4()
|
||||
|
||||
// Send welcome message
|
||||
socket.send(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
from: 'server',
|
||||
type: 'notification',
|
||||
event: 'welcome',
|
||||
data: {
|
||||
agentId,
|
||||
message: 'Connected to Brain Jar Broadcast Server',
|
||||
agents: Array.from(this.agents.values()).map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
role: a.role
|
||||
}))
|
||||
},
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
|
||||
// Handle messages from this agent
|
||||
socket.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString())
|
||||
this.handleAgentMessage(agentId, message)
|
||||
} catch (error) {
|
||||
console.error('Invalid message from agent:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Handle disconnection
|
||||
socket.on('close', () => {
|
||||
this.removeAgent(agentId)
|
||||
})
|
||||
|
||||
// Handle errors
|
||||
socket.on('error', (error) => {
|
||||
console.error(`Agent ${agentId} error:`, error)
|
||||
})
|
||||
|
||||
// Store temporary connection until identified
|
||||
this.agents.set(agentId, {
|
||||
id: agentId,
|
||||
name: 'Unknown',
|
||||
role: 'Unknown',
|
||||
socket,
|
||||
lastSeen: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle message from an agent
|
||||
*/
|
||||
private handleAgentMessage(agentId: string, message: any) {
|
||||
const agent = this.agents.get(agentId)
|
||||
if (!agent) return
|
||||
|
||||
// Update last seen
|
||||
agent.lastSeen = Date.now()
|
||||
|
||||
// Handle identification
|
||||
if (message.type === 'identify') {
|
||||
agent.name = message.name || agent.name
|
||||
agent.role = message.role || agent.role
|
||||
|
||||
// Notify all agents about new member
|
||||
this.broadcast({
|
||||
id: uuidv4(),
|
||||
from: 'server',
|
||||
type: 'notification',
|
||||
event: 'agent_joined',
|
||||
data: {
|
||||
agent: {
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
role: agent.role
|
||||
}
|
||||
},
|
||||
timestamp: Date.now()
|
||||
}, agentId) // Exclude the joining agent
|
||||
|
||||
// Send recent history to new agent
|
||||
if (this.messageHistory.length > 0) {
|
||||
this.sendToAgent(agentId, {
|
||||
id: uuidv4(),
|
||||
from: 'server',
|
||||
type: 'sync',
|
||||
data: {
|
||||
history: this.messageHistory.slice(-20) // Last 20 messages
|
||||
},
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Create broadcast message
|
||||
const broadcastMsg: BroadcastMessage = {
|
||||
id: message.id || uuidv4(),
|
||||
from: agent.name,
|
||||
to: message.to,
|
||||
type: message.type || 'message',
|
||||
event: message.event,
|
||||
data: message.data,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
// Store in history
|
||||
this.addToHistory(broadcastMsg)
|
||||
|
||||
// Broadcast based on recipient
|
||||
if (message.to) {
|
||||
// Send to specific agent(s)
|
||||
const recipients = Array.isArray(message.to) ? message.to : [message.to]
|
||||
recipients.forEach((recipientName: string) => {
|
||||
const recipient = Array.from(this.agents.values()).find(
|
||||
a => a.name === recipientName
|
||||
)
|
||||
if (recipient) {
|
||||
this.sendToAgent(recipient.id, broadcastMsg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Broadcast to all agents except sender
|
||||
this.broadcast(broadcastMsg, agentId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all connected agents
|
||||
*/
|
||||
broadcast(message: BroadcastMessage, excludeId?: string) {
|
||||
const messageStr = JSON.stringify(message)
|
||||
|
||||
this.agents.forEach((agent) => {
|
||||
if (agent.id !== excludeId && agent.socket.readyState === WebSocket.OPEN) {
|
||||
agent.socket.send(messageStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to specific agent
|
||||
*/
|
||||
private sendToAgent(agentId: string, message: BroadcastMessage) {
|
||||
const agent = this.agents.get(agentId)
|
||||
if (agent && agent.socket.readyState === WebSocket.OPEN) {
|
||||
agent.socket.send(JSON.stringify(message))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove agent from connected list
|
||||
*/
|
||||
private removeAgent(agentId: string) {
|
||||
const agent = this.agents.get(agentId)
|
||||
if (agent) {
|
||||
// Notify others about disconnection
|
||||
this.broadcast({
|
||||
id: uuidv4(),
|
||||
from: 'server',
|
||||
type: 'notification',
|
||||
event: 'agent_left',
|
||||
data: {
|
||||
agent: {
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
role: agent.role
|
||||
}
|
||||
},
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
this.agents.delete(agentId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add message to history
|
||||
*/
|
||||
private addToHistory(message: BroadcastMessage) {
|
||||
this.messageHistory.push(message)
|
||||
|
||||
// Trim history if too large
|
||||
if (this.messageHistory.length > this.maxHistorySize) {
|
||||
this.messageHistory = this.messageHistory.slice(-this.maxHistorySize)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the broadcast server
|
||||
*/
|
||||
async stopBroadcastServer(): Promise<void> {
|
||||
// Close all agent connections
|
||||
this.agents.forEach(agent => {
|
||||
agent.socket.close(1000, 'Server shutting down')
|
||||
})
|
||||
this.agents.clear()
|
||||
|
||||
// Close WebSocket server
|
||||
if (this.wsServer) {
|
||||
this.wsServer.close()
|
||||
}
|
||||
|
||||
// Close HTTP server
|
||||
if (this.httpServer) {
|
||||
this.httpServer.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connected agents
|
||||
*/
|
||||
getConnectedAgents(): Array<{ id: string; name: string; role: string }> {
|
||||
return Array.from(this.agents.values()).map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
role: a.role
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message history
|
||||
*/
|
||||
getMessageHistory(): BroadcastMessage[] {
|
||||
return [...this.messageHistory]
|
||||
}
|
||||
}
|
||||
|
||||
// Export for both environments
|
||||
export default BrainyMCPBroadcast
|
||||
310
src/mcp/brainyMCPClient.ts
Normal file
310
src/mcp/brainyMCPClient.ts
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/**
|
||||
* BrainyMCPClient
|
||||
*
|
||||
* Client for connecting Claude instances to the Brain Jar Broadcast Server
|
||||
* Utilizes Brainy for persistent memory and vector search capabilities
|
||||
*/
|
||||
|
||||
import WebSocket from 'ws'
|
||||
import { BrainyData } from '../brainyData.js'
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js'
|
||||
|
||||
interface ClientOptions {
|
||||
name: string // e.g., 'Jarvis' or 'Picasso'
|
||||
role: string // e.g., 'Backend Systems' or 'Frontend Design'
|
||||
serverUrl?: string // Default: ws://localhost:8765
|
||||
autoReconnect?: boolean
|
||||
useBrainyMemory?: boolean // Store messages in Brainy for persistence
|
||||
}
|
||||
|
||||
interface Message {
|
||||
id: string
|
||||
from: string
|
||||
to?: string | string[]
|
||||
type: 'message' | 'notification' | 'sync' | 'heartbeat' | 'identify'
|
||||
event?: string
|
||||
data: any
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export class BrainyMCPClient {
|
||||
private socket?: WebSocket
|
||||
private options: Required<ClientOptions>
|
||||
private brainy?: BrainyData
|
||||
private messageHandlers: Map<string, (message: Message) => void> = new Map()
|
||||
private reconnectTimeout?: NodeJS.Timeout
|
||||
private isConnected = false
|
||||
|
||||
constructor(options: ClientOptions) {
|
||||
this.options = {
|
||||
serverUrl: 'ws://localhost:8765',
|
||||
autoReconnect: true,
|
||||
useBrainyMemory: true,
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Brainy for persistent memory
|
||||
*/
|
||||
private async initBrainy() {
|
||||
if (this.options.useBrainyMemory && !this.brainy) {
|
||||
this.brainy = new BrainyData({
|
||||
storage: {
|
||||
requestPersistentStorage: true
|
||||
}
|
||||
})
|
||||
await this.brainy.init()
|
||||
console.log(`🧠 Brainy memory initialized for ${this.options.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the broadcast server
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
// Initialize Brainy first
|
||||
await this.initBrainy()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.socket = new WebSocket(this.options.serverUrl)
|
||||
|
||||
this.socket.on('open', () => {
|
||||
console.log(`✅ ${this.options.name} connected to Brain Jar Broadcast`)
|
||||
this.isConnected = true
|
||||
|
||||
// Identify ourselves
|
||||
this.send({
|
||||
type: 'identify',
|
||||
data: {
|
||||
name: this.options.name,
|
||||
role: this.options.role
|
||||
}
|
||||
})
|
||||
|
||||
resolve()
|
||||
})
|
||||
|
||||
this.socket.on('message', async (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString()) as Message
|
||||
await this.handleMessage(message)
|
||||
} catch (error) {
|
||||
console.error('Error parsing message:', error)
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('close', () => {
|
||||
console.log(`❌ ${this.options.name} disconnected from Brain Jar`)
|
||||
this.isConnected = false
|
||||
|
||||
if (this.options.autoReconnect) {
|
||||
this.scheduleReconnect()
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('error', (error) => {
|
||||
console.error(`Connection error for ${this.options.name}:`, error)
|
||||
reject(error)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming message
|
||||
*/
|
||||
private async handleMessage(message: Message) {
|
||||
// Store in Brainy for persistent memory
|
||||
if (this.brainy && message.type === 'message') {
|
||||
try {
|
||||
await this.brainy.add({
|
||||
text: `${message.from}: ${JSON.stringify(message.data)}`,
|
||||
metadata: {
|
||||
messageId: message.id,
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
timestamp: message.timestamp,
|
||||
type: message.type,
|
||||
event: message.event
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error storing message in Brainy:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sync messages (receive history)
|
||||
if (message.type === 'sync' && message.data.history) {
|
||||
console.log(`📜 ${this.options.name} received ${message.data.history.length} historical messages`)
|
||||
|
||||
// Store history in Brainy
|
||||
if (this.brainy) {
|
||||
for (const histMsg of message.data.history) {
|
||||
await this.brainy.add({
|
||||
text: `${histMsg.from}: ${JSON.stringify(histMsg.data)}`,
|
||||
metadata: histMsg
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call registered handlers
|
||||
const handler = this.messageHandlers.get(message.type)
|
||||
if (handler) {
|
||||
handler(message)
|
||||
}
|
||||
|
||||
// Call universal handler
|
||||
const universalHandler = this.messageHandlers.get('*')
|
||||
if (universalHandler) {
|
||||
universalHandler(message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message
|
||||
*/
|
||||
send(message: Partial<Message>) {
|
||||
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
||||
console.error(`${this.options.name} is not connected`)
|
||||
return
|
||||
}
|
||||
|
||||
const fullMessage: Message = {
|
||||
id: message.id || uuidv4(),
|
||||
from: this.options.name,
|
||||
type: message.type || 'message',
|
||||
data: message.data || {},
|
||||
timestamp: Date.now(),
|
||||
...message
|
||||
}
|
||||
|
||||
this.socket.send(JSON.stringify(fullMessage))
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to specific agent(s)
|
||||
*/
|
||||
sendTo(recipient: string | string[], data: any) {
|
||||
this.send({
|
||||
to: recipient,
|
||||
type: 'message',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast to all agents
|
||||
*/
|
||||
broadcast(data: any) {
|
||||
this.send({
|
||||
type: 'message',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a message handler
|
||||
*/
|
||||
on(type: string, handler: (message: Message) => void) {
|
||||
this.messageHandlers.set(type, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a message handler
|
||||
*/
|
||||
off(type: string) {
|
||||
this.messageHandlers.delete(type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search historical messages using Brainy's vector search
|
||||
*/
|
||||
async searchMemory(query: string, limit = 10): Promise<any[]> {
|
||||
if (!this.brainy) {
|
||||
console.warn('Brainy memory not initialized')
|
||||
return []
|
||||
}
|
||||
|
||||
const results = await this.brainy.search(query, limit)
|
||||
return results.map(r => ({
|
||||
...r.metadata,
|
||||
relevance: r.score
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent messages from Brainy memory
|
||||
*/
|
||||
async getRecentMessages(limit = 20): Promise<any[]> {
|
||||
if (!this.brainy) {
|
||||
console.warn('Brainy memory not initialized')
|
||||
return []
|
||||
}
|
||||
|
||||
// Search for recent activity
|
||||
const results = await this.brainy.search('recent messages communication', limit)
|
||||
return results
|
||||
.map(r => r.metadata)
|
||||
.sort((a: any, b: any) => b.timestamp - a.timestamp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule reconnection attempt
|
||||
*/
|
||||
private scheduleReconnect() {
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout)
|
||||
}
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
console.log(`🔄 ${this.options.name} attempting to reconnect...`)
|
||||
this.connect().catch(error => {
|
||||
console.error('Reconnection failed:', error)
|
||||
this.scheduleReconnect()
|
||||
})
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from server
|
||||
*/
|
||||
disconnect() {
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout)
|
||||
}
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.close(1000, 'Client disconnecting')
|
||||
this.socket = undefined
|
||||
}
|
||||
|
||||
this.isConnected = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected
|
||||
*/
|
||||
getIsConnected(): boolean {
|
||||
return this.isConnected
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent info
|
||||
*/
|
||||
getAgentInfo() {
|
||||
return {
|
||||
name: this.options.name,
|
||||
role: this.options.role,
|
||||
connected: this.isConnected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export for both environments
|
||||
export default BrainyMCPClient
|
||||
347
src/mcp/brainyMCPService.ts
Normal file
347
src/mcp/brainyMCPService.ts
Normal file
|
|
@ -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 '../universal/uuid.js'
|
||||
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<string, { userId: string; expires: number }>
|
||||
private rateLimits: Map<string, { count: number; resetTime: number }>
|
||||
|
||||
/**
|
||||
* 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<MCPResponse> {
|
||||
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<MCPResponse> {
|
||||
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<MCPResponse> {
|
||||
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<MCPResponse> {
|
||||
return await this.handleRequest(request)
|
||||
}
|
||||
}
|
||||
19
src/mcp/index.ts
Normal file
19
src/mcp/index.ts
Normal file
|
|
@ -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'
|
||||
222
src/mcp/mcpAugmentationToolset.ts
Normal file
222
src/mcp/mcpAugmentationToolset.ts
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* 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 '../universal/uuid.js'
|
||||
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<MCPResponse> {
|
||||
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<MCPTool[]> {
|
||||
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 as any)[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<any> {
|
||||
// In Brainy 2.0, we directly call methods on augmentation instances
|
||||
// instead of using the old typed pipeline system
|
||||
|
||||
const { args = [], options = {} } = parameters
|
||||
|
||||
// Get augmentations of the specified type
|
||||
const augmentations = augmentationPipeline.getAugmentationsByType(type as any)
|
||||
|
||||
// Find the first augmentation that has the requested method
|
||||
for (const augmentation of augmentations) {
|
||||
if (typeof (augmentation as any)[method] === 'function') {
|
||||
// Call the method directly on the augmentation instance
|
||||
return await (augmentation as any)[method](...args, options)
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Method '${method}' not found in any ${type} augmentation`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue