Open source vector database with HNSW indexing, graph relationships, and metadata facets. Features CLI with professional augmentation registry integration for discovering extensions and capabilities.
248 lines
No EOL
8.6 KiB
JavaScript
248 lines
No EOL
8.6 KiB
JavaScript
/**
|
|
* 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 { MCPRequestType, MCP_VERSION } from '../types/mcpTypes.js';
|
|
import { BrainyMCPAdapter } from './brainyMCPAdapter.js';
|
|
import { MCPAugmentationToolset } from './mcpAugmentationToolset.js';
|
|
import { isBrowser, isNode } from '../utils/environment.js';
|
|
export class BrainyMCPService {
|
|
/**
|
|
* Creates a new BrainyMCPService
|
|
* @param brainyData The BrainyData instance to wrap
|
|
* @param options Configuration options for the service
|
|
*/
|
|
constructor(brainyData, options = {}) {
|
|
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) {
|
|
try {
|
|
switch (request.type) {
|
|
case MCPRequestType.DATA_ACCESS:
|
|
return await this.dataAdapter.handleRequest(request);
|
|
case MCPRequestType.TOOL_EXECUTION:
|
|
return await this.toolset.handleRequest(request);
|
|
case MCPRequestType.SYSTEM_INFO:
|
|
return await this.handleSystemInfoRequest(request);
|
|
case MCPRequestType.AUTHENTICATION:
|
|
return await this.handleAuthenticationRequest(request);
|
|
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
|
|
*/
|
|
async handleSystemInfoRequest(request) {
|
|
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 = 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
|
|
*/
|
|
async handleAuthenticationRequest(request) {
|
|
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
|
|
*/
|
|
isValidRequest(request) {
|
|
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
|
|
*/
|
|
isAuthenticated(request) {
|
|
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
|
|
*/
|
|
isValidToken(token) {
|
|
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
|
|
*/
|
|
generateAuthToken(userId) {
|
|
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
|
|
*/
|
|
checkRateLimit(clientId) {
|
|
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
|
|
*/
|
|
createSuccessResponse(requestId, data) {
|
|
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
|
|
*/
|
|
createErrorResponse(requestId, code, message, details) {
|
|
return {
|
|
success: false,
|
|
requestId,
|
|
version: MCP_VERSION,
|
|
error: {
|
|
code,
|
|
message,
|
|
details
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Creates a new request ID
|
|
* @returns A new UUID
|
|
*/
|
|
generateRequestId() {
|
|
return uuidv4();
|
|
}
|
|
/**
|
|
* Handles an MCP request directly (for in-process models)
|
|
* @param request The MCP request
|
|
* @returns An MCP response
|
|
*/
|
|
async handleMCPRequest(request) {
|
|
return await this.handleRequest(request);
|
|
}
|
|
}
|
|
//# sourceMappingURL=brainyMCPService.js.map
|