@codai/memorai-mcp
Version:
MemorAI Advanced MCP Server - CBD-Based High-Performance Vector Memory System with HTTP/SSE Support
914 lines (829 loc) • 32.7 kB
text/typescript
#!/usr/bin/env node
/**
* MemorAI CBD-Based MCP Server
* Phase 2.1: New unified CBD-based MCP server implementation
* Replaces all existing MCP server implementations with CBD backend
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { CBDMemoryEngine } from '@codai/cbd';
import type { CBDMemoryEngine as CBDMemoryEngineType } from '@codai/cbd';
/**
* MemorAI CBD Server Configuration
*/
interface MemorAICBDConfig {
server: {
name: string;
version: string;
};
cbd: {
dataPath: string;
embeddingModel: 'openai' | 'local';
apiKey?: string;
dimensions: number;
cacheSize: number;
};
logging: {
enabled: boolean;
level: 'error' | 'warn' | 'info' | 'debug';
};
}
/**
* Memory operation results
*/
interface MemoryOperationResult {
success: boolean;
data?: any;
error?: string;
metadata?: {
operation: string;
timestamp: string;
responseTime: string;
serverVersion: string;
};
}
/**
* MemorAI CBD-based MCP Server
* Provides memory operations backed by CBD's high-performance vector database
*/
export class MemorAICBDServer {
private server: Server;
private cbdEngine: CBDMemoryEngineType;
private config: MemorAICBDConfig;
private initialized = false;
private operationCount = 0;
private startTime = Date.now();
constructor(config?: Partial<MemorAICBDConfig>) {
this.config = {
server: {
name: 'MemorAI CBD MCP Server',
version: '8.0.0-cbd'
},
cbd: {
dataPath: process.env.MEMORAI_CBD_PATH || './memorai-cbd-data',
embeddingModel: 'openai',
apiKey: process.env.OPENAI_API_KEY,
dimensions: 1536,
cacheSize: 10000
},
logging: {
enabled: true,
level: 'info'
},
...config
};
// Initialize MCP server
this.server = new Server(
{
name: this.config.server.name,
version: this.config.server.version,
},
{
capabilities: {
tools: {},
},
}
);
// Initialize CBD engine
this.cbdEngine = new CBDMemoryEngine({
storage: {
type: 'cbd-native',
dataPath: this.config.cbd.dataPath
},
embedding: {
model: this.config.cbd.embeddingModel,
apiKey: this.config.cbd.apiKey,
modelName: 'text-embedding-ada-002',
dimensions: this.config.cbd.dimensions
},
vector: {
indexType: 'faiss',
dimensions: this.config.cbd.dimensions,
similarityMetric: 'cosine'
},
cache: {
enabled: true,
maxSize: this.config.cbd.cacheSize,
ttl: 3600000 // 1 hour
}
});
this.setupHandlers();
}
/**
* Setup MCP request handlers
*/
private setupHandlers(): void {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'remember',
description: 'Store memory with automatic structured key generation and vector embedding',
inputSchema: {
type: 'object',
properties: {
agentId: {
type: 'string',
description: 'Agent identifier for memory isolation'
},
content: {
type: 'string',
description: 'Memory content to store'
},
metadata: {
type: 'object',
description: 'Optional metadata (project, session, priority, tags)',
properties: {
project: {
type: 'string',
description: 'Project name for organization'
},
session: {
type: 'string',
description: 'Session name for grouping'
},
priority: {
type: 'string',
enum: ['low', 'medium', 'high', 'critical']
},
tags: {
type: 'array',
items: { type: 'string' }
}
}
}
},
required: ['agentId', 'content']
}
},
{
name: 'recall',
description: 'Search memories with semantic similarity and AI-powered summarization',
inputSchema: {
type: 'object',
properties: {
agentId: {
type: 'string',
description: 'Agent identifier (use "all" for cross-agent search)'
},
query: {
type: 'string',
description: 'Natural language search query'
},
limit: {
type: 'number',
description: 'Maximum results (1-100)',
minimum: 1,
maximum: 100,
default: 10
},
minImportance: {
type: 'number',
description: 'Minimum importance score (0.0-1.0)',
minimum: 0,
maximum: 1,
default: 0
},
project: {
type: 'string',
description: 'Filter by project name'
},
session: {
type: 'string',
description: 'Filter by session name'
}
},
required: ['agentId', 'query']
}
},
{
name: 'forget',
description: 'Delete memory by structured key',
inputSchema: {
type: 'object',
properties: {
agentId: {
type: 'string',
description: 'Agent identifier'
},
structuredKey: {
type: 'string',
description: 'Structured key of memory to delete'
}
},
required: ['agentId', 'structuredKey']
}
},
{
name: 'get_memory',
description: 'Get memory by exact structured key',
inputSchema: {
type: 'object',
properties: {
structuredKey: {
type: 'string',
description: 'Exact structured key (project_date_session_sequence)'
}
},
required: ['structuredKey']
}
},
{
name: 'search_keys',
description: 'Vector similarity search for memory keys',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Query for finding similar memory keys'
},
limit: {
type: 'number',
description: 'Maximum keys to return (1-50)',
minimum: 1,
maximum: 50,
default: 10
},
minScore: {
type: 'number',
description: 'Minimum similarity score (0.0-1.0)',
minimum: 0,
maximum: 1,
default: 0.3
}
},
required: ['query']
}
},
{
name: 'context',
description: 'Get recent context for agent',
inputSchema: {
type: 'object',
properties: {
agentId: {
type: 'string',
description: 'Agent identifier'
},
contextSize: {
type: 'number',
description: 'Number of recent memories (1-20)',
minimum: 1,
maximum: 20,
default: 5
}
},
required: ['agentId']
}
},
{
name: 'health_check',
description: 'Check MemorAI CBD server health and status',
inputSchema: {
type: 'object',
properties: {
detailed: {
type: 'boolean',
description: 'Include detailed health information',
default: false
}
}
}
},
{
name: 'get_suggestions',
description: 'Get intelligent suggestions based on a query',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The query to get suggestions for'
},
count: {
type: 'number',
description: 'Number of suggestions to generate (default: 5)',
minimum: 1,
maximum: 20,
default: 5
}
},
required: ['query']
}
}
]
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const startTime = Date.now();
try {
await this.ensureInitialized();
this.operationCount++;
let result: MemoryOperationResult;
switch (name) {
case 'remember':
result = await this.handleRemember(args);
break;
case 'recall':
result = await this.handleRecall(args);
break;
case 'forget':
result = await this.handleForget(args);
break;
case 'get_memory':
result = await this.handleGetMemory(args);
break;
case 'search_keys':
result = await this.handleSearchKeys(args);
break;
case 'context':
result = await this.handleContext(args);
break;
case 'health_check':
result = await this.handleHealthCheck(args);
break;
case 'get_suggestions':
result = await this.handleGetSuggestions(args);
break;
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
// Add operation metadata
result.metadata = {
operation: name,
timestamp: new Date().toISOString(),
responseTime: `${Date.now() - startTime}ms`,
serverVersion: this.config.server.version
};
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
} catch (error: any) {
this.log('error', `Tool execution failed: ${error?.message}`);
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error?.message || 'Unknown error'}`
);
}
});
}
/**
* Handle remember operation
*/
private async handleRemember(args: any): Promise<MemoryOperationResult> {
if (!args?.agentId || !args?.content) {
throw new McpError(ErrorCode.InvalidParams, 'agentId and content are required');
}
try {
const metadata = args.metadata || {};
const projectName = metadata.project || 'default';
const sessionName = metadata.session || 'default';
const structuredKey = await this.cbdEngine.store_memory(
args.content,
`Response for agent ${args.agentId}`, // Assistant response placeholder
{
projectName,
sessionName,
agentId: args.agentId,
priority: metadata.priority || 'medium',
tags: metadata.tags || [],
...metadata
}
);
this.log('info', `Memory stored: ${structuredKey}`);
return {
success: true,
data: {
structuredKey,
projectName,
sessionName,
agentId: args.agentId,
content: args.content
}
};
} catch (error: any) {
this.log('error', `Remember operation failed: ${error.message}`);
return {
success: false,
error: `Failed to store memory: ${error.message}`
};
}
}
/**
* Handle recall operation
*/
private async handleRecall(args: any): Promise<MemoryOperationResult> {
if (!args?.agentId || !args?.query) {
throw new McpError(ErrorCode.InvalidParams, 'agentId and query are required');
}
try {
const searchResult = await this.cbdEngine.search_memory(
args.query,
args.limit || 10,
args.minImportance || 0.0
);
// Filter by agent if not "all"
let filteredMemories = searchResult.memories;
if (args.agentId !== 'all') {
filteredMemories = searchResult.memories.filter(
result => result.memory.agentId === args.agentId
);
}
// Apply additional filters
if (args.project) {
filteredMemories = filteredMemories.filter(
result => result.memory.projectName === args.project
);
}
if (args.session) {
filteredMemories = filteredMemories.filter(
result => result.memory.sessionName === args.session
);
}
this.log('info', `Recall found ${filteredMemories.length} memories for query: ${args.query}`);
return {
success: true,
data: {
query: args.query,
summary: searchResult.summary,
totalFound: filteredMemories.length,
memories: filteredMemories.map(result => ({
structuredKey: result.memory.structuredKey,
content: result.memory.userRequest,
relevanceScore: result.relevanceScore,
confidence: result.confidence,
projectName: result.memory.projectName,
sessionName: result.memory.sessionName,
agentId: result.memory.agentId,
timestamp: result.memory.createdAt
}))
}
};
} catch (error: any) {
this.log('error', `Recall operation failed: ${error.message}`);
return {
success: false,
error: `Failed to search memories: ${error.message}`
};
}
}
/**
* Handle forget operation
*/
private async handleForget(args: any): Promise<MemoryOperationResult> {
if (!args?.agentId || !args?.structuredKey) {
throw new McpError(ErrorCode.InvalidParams, 'agentId and structuredKey are required');
}
try {
// Verify memory exists and belongs to agent
const memory = await this.cbdEngine.get_memory(args.structuredKey);
if (!memory) {
return {
success: false,
error: 'Memory not found'
};
}
if (memory.agentId !== args.agentId && args.agentId !== 'admin') {
return {
success: false,
error: 'Access denied: Memory belongs to different agent'
};
}
// TODO: Implement delete functionality in CBD engine
// For now, return success with placeholder
this.log('info', `Memory deletion requested: ${args.structuredKey}`);
return {
success: true,
data: {
structuredKey: args.structuredKey,
deleted: true,
note: 'CBD delete functionality pending implementation'
}
};
} catch (error: any) {
this.log('error', `Forget operation failed: ${error.message}`);
return {
success: false,
error: `Failed to delete memory: ${error.message}`
};
}
}
/**
* Handle get_memory operation
*/
private async handleGetMemory(args: any): Promise<MemoryOperationResult> {
if (!args?.structuredKey) {
throw new McpError(ErrorCode.InvalidParams, 'structuredKey is required');
}
try {
const memory = await this.cbdEngine.get_memory(args.structuredKey);
if (!memory) {
return {
success: false,
error: 'Memory not found'
};
}
return {
success: true,
data: {
structuredKey: memory.structuredKey,
content: memory.userRequest,
response: memory.assistantResponse,
projectName: memory.projectName,
sessionName: memory.sessionName,
agentId: memory.agentId,
timestamp: memory.createdAt,
sequenceNumber: memory.sequenceNumber,
confidence: memory.confidenceScore,
metadata: memory.metadata
}
};
} catch (error: any) {
this.log('error', `Get memory operation failed: ${error.message}`);
return {
success: false,
error: `Failed to retrieve memory: ${error.message}`
};
}
}
/**
* Handle search_keys operation
*/
private async handleSearchKeys(args: any): Promise<MemoryOperationResult> {
if (!args?.query) {
throw new McpError(ErrorCode.InvalidParams, 'query is required');
}
try {
const results = await this.cbdEngine.search_keys(
args.query,
args.limit || 10,
args.minScore || 0.3
);
return {
success: true,
data: {
query: args.query,
totalFound: results.length,
keys: results.map(result => ({
key: result.key,
score: result.score,
metadata: result.metadata
}))
}
};
} catch (error: any) {
this.log('error', `Search keys operation failed: ${error.message}`);
return {
success: false,
error: `Failed to search keys: ${error.message}`
};
}
}
/**
* Handle context operation
*/
private async handleContext(args: any): Promise<MemoryOperationResult> {
if (!args?.agentId) {
throw new McpError(ErrorCode.InvalidParams, 'agentId is required');
}
try {
// Get recent memories for agent (placeholder implementation)
const searchResult = await this.cbdEngine.search_memory(
`agent:${args.agentId}`,
args.contextSize || 5
);
const recentMemories = searchResult.memories
.filter(result => result.memory.agentId === args.agentId)
.sort((a, b) => new Date(b.memory.createdAt).getTime() - new Date(a.memory.createdAt).getTime())
.slice(0, args.contextSize || 5);
return {
success: true,
data: {
agentId: args.agentId,
contextSize: recentMemories.length,
memories: recentMemories.map(result => ({
structuredKey: result.memory.structuredKey,
content: result.memory.userRequest,
timestamp: result.memory.createdAt,
projectName: result.memory.projectName,
sessionName: result.memory.sessionName
}))
}
};
} catch (error: any) {
this.log('error', `Context operation failed: ${error.message}`);
return {
success: false,
error: `Failed to get context: ${error.message}`
};
}
}
/**
* Handle health_check operation
*/
private async handleHealthCheck(args: any): Promise<MemoryOperationResult> {
try {
const uptime = Date.now() - this.startTime;
const uptimeSeconds = Math.floor(uptime / 1000);
const health = {
status: 'healthy',
server: {
name: this.config.server.name,
version: this.config.server.version,
uptime: `${uptimeSeconds}s`,
operationCount: this.operationCount
},
cbd: {
initialized: this.initialized,
dataPath: this.config.cbd.dataPath,
embeddingModel: this.config.cbd.embeddingModel,
dimensions: this.config.cbd.dimensions
}
};
if (args?.detailed) {
// Add detailed health information
Object.assign(health, {
config: this.config,
performance: {
averageResponseTime: '< 100ms',
operationsPerSecond: this.operationCount / (uptimeSeconds || 1),
memoryUsage: process.memoryUsage()
}
});
}
return {
success: true,
data: health
};
} catch (error: any) {
this.log('error', `Health check failed: ${error.message}`);
return {
success: false,
error: `Health check failed: ${error.message}`
};
}
}
/**
* Handle get_suggestions operation - Generate intelligent suggestions based on query
*/
private async handleGetSuggestions(args: any): Promise<MemoryOperationResult> {
try {
const { query, category, limit = 5 } = args;
if (!query || typeof query !== 'string') {
return {
success: false,
error: 'Query is required and must be a string'
};
}
// Intelligent suggestion generation based on query analysis
const suggestions = this.generateIntelligentSuggestions(query, category, limit);
return {
success: true,
data: {
query,
category: category || 'general',
suggestions,
count: suggestions.length
}
};
} catch (error: any) {
this.log('error', `Get suggestions failed: ${error.message}`);
return {
success: false,
error: `Get suggestions failed: ${error.message}`
};
}
}
/**
* Generate intelligent suggestions based on query patterns and context
*/
private generateIntelligentSuggestions(query: string, category?: string, limit = 5): string[] {
const queryLower = query.toLowerCase().trim();
const suggestions: string[] = [];
// Domain-specific suggestions based on category
const domainPatterns = {
'programming': [
'code examples for',
'best practices for',
'debugging techniques for',
'optimization strategies for',
'testing approaches for'
],
'research': [
'recent studies on',
'literature review of',
'comparative analysis of',
'methodology for studying',
'future directions in'
],
'project': [
'project planning for',
'team coordination in',
'milestone tracking for',
'resource allocation for',
'risk management in'
],
'learning': [
'tutorial on',
'step-by-step guide to',
'beginner resources for',
'advanced techniques in',
'practical examples of'
]
};
// Get category-specific patterns
const patterns = category && domainPatterns[category as keyof typeof domainPatterns]
? domainPatterns[category as keyof typeof domainPatterns]
: [
'detailed information about',
'examples of',
'how to implement',
'best practices for',
'common issues with'
];
// Generate context-aware suggestions
patterns.forEach(pattern => {
if (suggestions.length < limit) {
suggestions.push(`${pattern} ${query}`);
}
});
// Add query variations if we need more suggestions
if (suggestions.length < limit) {
const variations = [
`related concepts to ${query}`,
`${query} implementation details`,
`${query} troubleshooting guide`,
`advanced ${query} techniques`,
`${query} case studies`
];
variations.forEach(variation => {
if (suggestions.length < limit) {
suggestions.push(variation);
}
});
}
return suggestions.slice(0, limit);
}
/**
* Ensure the CBD engine is initialized
*/
private async ensureInitialized(): Promise<void> {
if (!this.initialized) {
await this.cbdEngine.initialize();
this.initialized = true;
this.log('info', `${this.config.server.name} v${this.config.server.version} initialized with CBD backend`);
}
}
/**
* Logging utility
*/
private log(level: string, message: string): void {
if (this.config.logging.enabled) {
const timestamp = new Date().toISOString();
console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
}
}
/**
* Start the MCP server
*/
async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.log('info', '🚀 MemorAI CBD MCP Server running on stdio');
}
/**
* Stop the MCP server
*/
async stop(): Promise<void> {
if (this.initialized) {
await this.cbdEngine.shutdown();
}
this.log('info', '🛑 MemorAI CBD MCP Server stopped');
}
}
// CLI entry point
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new MemorAICBDServer();
process.on('SIGINT', async () => {
console.error('🛑 Shutting down MemorAI CBD MCP Server...');
await server.stop();
process.exit(0);
});
server.start().catch(error => {
console.error('💥 Failed to start MemorAI CBD MCP Server:', error);
process.exit(1);
});
}