Initial commit
This commit is contained in:
commit
5a8a6c1ba3
81 changed files with 39269 additions and 0 deletions
180
src/mcp/README.md
Normal file
180
src/mcp/README.md
Normal file
|
|
@ -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.
|
||||
203
src/mcp/brainyMCPAdapter.ts
Normal file
203
src/mcp/brainyMCPAdapter.ts
Normal file
|
|
@ -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<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.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<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'
|
||||
)
|
||||
}
|
||||
|
||||
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<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()
|
||||
}
|
||||
}
|
||||
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 '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<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'
|
||||
225
src/mcp/mcpAugmentationToolset.ts
Normal file
225
src/mcp/mcpAugmentationToolset.ts
Normal file
|
|
@ -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<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[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> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue