@bdmarvin/mcp-server-memory
Version:
MCP Server for LLM Long-Term Memory using KG and Google Drive
177 lines • 12.7 kB
JavaScript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { z } from 'zod';
import { UpdateKgNodeInputSchema, AddKgRelationshipInputSchema, LogDecisionInputSchema, GetKgNodeDetailsInputSchema, GetProjectSummaryFromKgInputSchema, SearchKgInputSchema, StoreDocumentInProjectDriveInputSchema, FindRelevantDocumentsInKgInputSchema, GetDocumentContentFromDriveInputSchema, GetDocumentSummaryFromDriveInputSchema, RetrieveKgInputSchema, TraverseKgInputSchema, QueryKgByAttributesInputSchema,
// New Deletion Schemas
DeleteKgNodeInputSchema, DeleteKgRelationshipInputSchema } from './toolSchemas.js';
import * as kgService from './services/kgService.js';
import * as driveService from './services/driveService.js';
import { TARGET_BASE_FOLDER_NAME_EXPORTED } from './services/driveService.js';
const packageVersion = process.env.npm_package_version || '0.2.1'; // Updated to 0.2.1
const server = new Server({ name: 'llm-memory-mcp-server', version: packageVersion }, { capabilities: { tools: {} } });
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error('DEBUG: ListToolsRequestSchema request received by mcp-server-memory');
return {
tools: [
// KG Modification Tools
{ name: 'tool_update_kg_node', description: UpdateKgNodeInputSchema.description || 'Creates or updates a node in the Knowledge Graph (stored in Drive).', inputSchema: zodToJsonSchema(UpdateKgNodeInputSchema), },
{ name: 'tool_add_kg_relationship', description: AddKgRelationshipInputSchema.description || 'Creates a relationship between two nodes in the Knowledge Graph (stored in Drive).', inputSchema: zodToJsonSchema(AddKgRelationshipInputSchema), },
{ name: 'tool_log_decision', description: LogDecisionInputSchema.description || 'Logs a project decision in the Knowledge Graph (stored in Drive).', inputSchema: zodToJsonSchema(LogDecisionInputSchema), },
{ name: 'tool_delete_kg_node', description: DeleteKgNodeInputSchema.description || 'Deletes a specific KG node and its connected relationships.', inputSchema: zodToJsonSchema(DeleteKgNodeInputSchema), },
{ name: 'tool_delete_kg_relationship', description: DeleteKgRelationshipInputSchema.description || 'Deletes a specific KG relationship by its ID.', inputSchema: zodToJsonSchema(DeleteKgRelationshipInputSchema), },
// KG Retrieval Tools
{ name: 'tool_get_kg_node_details', description: GetKgNodeDetailsInputSchema.description || 'Retrieves details of a specific KG node (from Drive).', inputSchema: zodToJsonSchema(GetKgNodeDetailsInputSchema), },
{ name: 'tool_get_project_summary_from_kg', description: GetProjectSummaryFromKgInputSchema.description || 'Retrieves a summary for a project from the KG (from Drive).', inputSchema: zodToJsonSchema(GetProjectSummaryFromKgInputSchema), },
{ name: 'tool_search_kg', description: SearchKgInputSchema.description || 'Performs a flexible search of the KG (from Drive) based on natural language query.', inputSchema: zodToJsonSchema(SearchKgInputSchema), },
{ name: 'tool_retrieve_kg', description: RetrieveKgInputSchema.description || 'Retrieves the entire Knowledge Graph for a project.', inputSchema: zodToJsonSchema(RetrieveKgInputSchema), },
{ name: 'tool_traverse_kg', description: TraverseKgInputSchema.description || 'Traverses the KG from a start node along specified relationships.', inputSchema: zodToJsonSchema(TraverseKgInputSchema), },
{ name: 'tool_query_kg_by_attributes', description: QueryKgByAttributesInputSchema.description || 'Finds KG nodes based on a structured query of their attributes.', inputSchema: zodToJsonSchema(QueryKgByAttributesInputSchema), },
// Drive Tools
{ name: 'tool_store_document_in_project_drive', description: StoreDocumentInProjectDriveInputSchema.description || 'Stores a document in Google Drive and links it in the KG.', inputSchema: zodToJsonSchema(StoreDocumentInProjectDriveInputSchema), },
{ name: 'tool_find_relevant_documents_in_kg', description: FindRelevantDocumentsInKgInputSchema.description || 'Queries the KG (from Drive) for document references matching criteria.', inputSchema: zodToJsonSchema(FindRelevantDocumentsInKgInputSchema), },
{ name: 'tool_get_document_content_from_drive', description: GetDocumentContentFromDriveInputSchema.description || 'Fetches document content from Google Drive.', inputSchema: zodToJsonSchema(GetDocumentContentFromDriveInputSchema), },
{ name: 'tool_get_document_summary_from_drive', description: GetDocumentSummaryFromDriveInputSchema.description || 'Generates and retrieves a summary of a document from Drive.', inputSchema: zodToJsonSchema(GetDocumentSummaryFromDriveInputSchema), },
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const toolName = request.params.name;
const allArgsFromController = request.params.arguments;
const googleAccessToken = allArgsFromController.__google_access_token__;
const googleUserEmail = allArgsFromController.__google_user_email__;
const originalToolArgs = {};
for (const key in allArgsFromController) {
if (key !== '__google_access_token__' && key !== '__google_user_id__' && key !== '__google_user_email__') {
originalToolArgs[key] = allArgsFromController[key];
}
}
console.error(`DEBUG: CallToolRequest - Tool: ${toolName}, User: ${googleUserEmail || 'N/A'}, Args: ${JSON.stringify(originalToolArgs)}`);
if (!googleAccessToken) {
const errorMsg = `Tool '${toolName}' requires Google Drive access for KG & document operations, but __google_access_token__ was not provided.`;
console.error(`ERROR: ${errorMsg} (Tool: ${toolName})`);
throw new Error(errorMsg);
}
try {
let responseData;
switch (toolName) {
// KG Modification Tools
case 'tool_update_kg_node': {
const parsedArgs = UpdateKgNodeInputSchema.parse(originalToolArgs);
responseData = await kgService.updateKgNode(googleAccessToken, parsedArgs);
break;
}
case 'tool_add_kg_relationship': {
const parsedArgs = AddKgRelationshipInputSchema.parse(originalToolArgs);
responseData = await kgService.addKgRelationship(googleAccessToken, parsedArgs);
break;
}
case 'tool_log_decision': {
const parsedArgs = LogDecisionInputSchema.parse(originalToolArgs);
responseData = await kgService.logDecision(googleAccessToken, parsedArgs);
break;
}
case 'tool_delete_kg_node': {
const parsedArgs = DeleteKgNodeInputSchema.parse(originalToolArgs);
responseData = await kgService.deleteKgNode(googleAccessToken, parsedArgs);
break;
}
case 'tool_delete_kg_relationship': {
const parsedArgs = DeleteKgRelationshipInputSchema.parse(originalToolArgs);
responseData = await kgService.deleteKgRelationship(googleAccessToken, parsedArgs);
break;
}
// KG Retrieval Tools
case 'tool_get_kg_node_details': {
const parsedArgs = GetKgNodeDetailsInputSchema.parse(originalToolArgs);
responseData = await kgService.getKgNodeDetails(googleAccessToken, parsedArgs);
break;
}
case 'tool_get_project_summary_from_kg': {
const parsedArgs = GetProjectSummaryFromKgInputSchema.parse(originalToolArgs);
responseData = await kgService.getProjectSummary(googleAccessToken, parsedArgs);
break;
}
case 'tool_search_kg': {
const parsedArgs = SearchKgInputSchema.parse(originalToolArgs);
responseData = await kgService.searchKg(googleAccessToken, parsedArgs);
break;
}
case 'tool_retrieve_kg': {
const parsedArgs = RetrieveKgInputSchema.parse(originalToolArgs);
responseData = await kgService.retrieveKg(googleAccessToken, parsedArgs);
break;
}
case 'tool_traverse_kg': {
const parsedArgs = TraverseKgInputSchema.parse(originalToolArgs);
responseData = await kgService.traverseKg(googleAccessToken, parsedArgs);
break;
}
case 'tool_query_kg_by_attributes': {
const parsedArgs = QueryKgByAttributesInputSchema.parse(originalToolArgs);
responseData = await kgService.queryKgByAttributes(googleAccessToken, parsedArgs);
break;
}
// Drive Tools
case 'tool_store_document_in_project_drive': {
const parsedArgs = StoreDocumentInProjectDriveInputSchema.parse(originalToolArgs);
responseData = await driveService.storeDocument(googleAccessToken, parsedArgs);
break;
}
case 'tool_find_relevant_documents_in_kg': {
const parsedArgs = FindRelevantDocumentsInKgInputSchema.parse(originalToolArgs);
responseData = await kgService.searchKg(googleAccessToken, {
project_id: parsedArgs.project_id,
query_description: parsedArgs.keywords.join(' '),
entity_types: ['document_reference'],
max_results: parsedArgs.max_results,
max_depth: 2,
});
break;
}
case 'tool_get_document_content_from_drive': {
const parsedArgs = GetDocumentContentFromDriveInputSchema.parse(originalToolArgs);
responseData = await driveService.getDocumentContent(googleAccessToken, parsedArgs);
break;
}
case 'tool_get_document_summary_from_drive': {
const parsedArgs = GetDocumentSummaryFromDriveInputSchema.parse(originalToolArgs);
responseData = await driveService.getDocumentSummary(googleAccessToken, parsedArgs);
break;
}
default:
console.warn(`WARN: Unknown tool requested: ${toolName}`);
throw new Error(`Unknown tool: ${toolName}`);
}
console.error(`DEBUG: Successfully processed tool '${toolName}'. Response preview: ${JSON.stringify(responseData)?.substring(0, 200)}`);
return { content: [{ type: 'text', text: JSON.stringify(responseData ?? null, null, 2) }] };
}
catch (error) {
console.error(`ERROR: Error in CallToolRequest for '${toolName}': ${error.message}. Stack: ${error.stack}. Args: ${JSON.stringify(originalToolArgs)}`);
if (error.response?.data) {
console.error(`ERROR: Underlying API Error Details for '${toolName}': ${JSON.stringify(error.response.data)}`);
}
if (error instanceof z.ZodError) {
const messages = error.errors.map((e) => `${e.path.join('.') || 'argument'}: ${e.message}`);
console.warn(`WARN: Invalid arguments for tool '${toolName}': ${JSON.stringify(error.errors)}`);
throw new Error(`Invalid arguments for tool '${toolName}': ${messages.join('; ')}`);
}
const errorMessage = error.message || `An unexpected error occurred in tool '${toolName}'`;
throw new Error(errorMessage);
}
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`INFO: LLM Memory MCP Server (v${packageVersion}) running on stdio.`);
console.error("INFO: All tools now require __google_access_token__ for KG (on Drive) and Drive document persistence.");
const baseFolderName = process.env.MCP_MEMORY_DRIVE_BASE_FOLDER_NAME || TARGET_BASE_FOLDER_NAME_EXPORTED;
console.error(`INFO: KG JSON files ('knowledge_graph.json') will be stored in project-specific folders within the Drive folder: '${baseFolderName}'.`);
}
runServer().catch((error) => {
console.error(`FATAL: Fatal error running LLM Memory MCP Server: ${error.message}. Stack: ${error.stack}`);
process.exit(1);
});
//# sourceMappingURL=index.js.map