import readline from 'readline';
import { CONFIG } from './config.js';
import { getPool, initializeDatabase, isDbInitialized } from './db/index.js';
import { memoryService } from './services/memory.js';
import { graphService } from './services/graph.js';
import { TOOLS } from './tools/definitions.js';
import { sendLogMessage, debugLog } from './utils/logger.js';
import { formatToolResponse, formatErrorResponse, sendResponse } from './utils/response.js';
// Track initialization states
let isInitialized = false;
// Initialize server
function initializeServer() {
try {
// Log startup immediately
debugLog('initializeServer() called', {
pid: process.pid,
nodeVersion: process.version,
platform: process.platform,
workingDir: process.cwd(),
env: {
PORT: process.env.PORT,
DATABASE_URL: process.env.DATABASE_URL,
MCP_SERVER_NAME: process.env.MCP_SERVER_NAME,
MCP_SERVER_VERSION: process.env.MCP_SERVER_VERSION
}
});
sendLogMessage('info', 'Memory MCP starting up', {
pid: process.pid,
nodeVersion: process.version,
platform: process.platform
});
// Set up stdio handling
process.stdin.setEncoding('utf8');
process.stdout.setDefaultEncoding('utf8');
// Prevent Node from exiting on stdin end
process.stdin.on('end', () => {
sendLogMessage('info', 'stdin end received, keeping process alive');
});
// Handle stdin errors
process.stdin.on('error', (error) => {
sendLogMessage('error', 'stdin error', { error: error.message });
});
// Handle stdout errors
process.stdout.on('error', (error) => {
sendLogMessage('error', 'stdout error', { error: error.message });
});
// Keep process alive and indicate readiness
setInterval(() => {
if (!isInitialized) {
sendLogMessage('debug', 'Waiting for initialization...', { pid: process.pid });
}
}, 30000);
// Resume stdin after all handlers are set up
process.stdin.resume();
sendLogMessage('info', 'Server ready for initialization requests');
return true;
} catch (error) {
sendLogMessage('error', 'Server initialization failed', {
error: error.message,
stack: error.stack
});
process.exit(1);
}
}
// Helper functions for resources (kept in server.js for simplicity, can be moved if needed)
async function handleListResources() {
if (!isDbInitialized()) await initializeDatabase();
const pool = getPool();
const typesResult = await pool.query("SELECT DISTINCT type FROM memories");
const types = typesResult.rows.map(row => row.type);
const resources = [
{
uri: "memory://types",
name: "Memory Types",
mimeType: "application/json",
description: "List of all memory types"
},
{
uri: "memory://tags",
name: "Memory Tags",
mimeType: "application/json",
description: "List of all memory tags"
}
];
types.forEach(type => {
resources.push({
uri: `memory://type/${type}`,
name: `Memories: ${type}`,
mimeType: "application/json",
description: `All memories of type ${type}`
});
});
return resources;
}
async function handleReadResource(resourceName) {
if (!isDbInitialized()) await initializeDatabase();
const pool = getPool();
if (resourceName === "memory://types") {
const result = await pool.query("SELECT DISTINCT type FROM memories");
const types = result.rows.map(row => row.type);
return { content: JSON.stringify(types), mimeType: "application/json" };
} else if (resourceName === "memory://tags") {
const result = await pool.query("SELECT DISTINCT unnest(tags) as tag FROM memories");
const tags = result.rows.map(row => row.tag);
return { content: JSON.stringify(tags), mimeType: "application/json" };
} else if (resourceName.startsWith("memory://type/")) {
const type = resourceName.substring("memory://type/".length);
const result = await pool.query("SELECT * FROM memories WHERE type = $1", [type]);
return { content: JSON.stringify(result.rows), mimeType: "application/json" };
} else {
throw new Error(`Unknown resource: ${resourceName}`);
}
}
// Start server initialization
if (!initializeServer()) {
process.exit(1);
}
// Setup stdio transport for local MCP
// NOTE: Do NOT set output to process.stdout - it interferes with MCP protocol responses
const rl = readline.createInterface({
input: process.stdin,
terminal: false
});
rl.on('line', async (line) => {
try {
if (!line.trim()) return;
debugLog('Received line', { length: line.length, preview: line.substring(0, 100) });
const message = JSON.parse(line);
const { id, method, params } = message;
switch (method) {
case 'initialize':
debugLog('Processing initialize request', { id });
isInitialized = true;
sendResponse({
jsonrpc: "2.0",
id,
result: {
protocolVersion: CONFIG.server.protocolVersion,
capabilities: {
tools: {},
resources: {},
prompts: {}
},
serverInfo: {
name: CONFIG.server.name,
version: CONFIG.server.version
}
}
});
sendLogMessage('info', 'Server initialized');
break;
case 'notifications/initialized':
debugLog('Received initialized notification');
break;
case 'listTools':
case 'tools/list':
debugLog('Processing tools list request', { id });
sendResponse({
jsonrpc: "2.0",
id,
result: {
tools: TOOLS
}
});
break;
case 'callTool':
case 'tools/call':
debugLog('Processing tool call', { method, id, params });
const toolName = params.name;
const toolArgs = params.arguments || params.input || {};
debugLog('Tool call details', { toolName, toolArgs });
try {
let result;
switch (toolName) {
case 'memory_create':
result = await memoryService.create(toolArgs);
break;
case 'memory_search':
result = await memoryService.search(toolArgs);
break;
case 'memory_list':
result = await memoryService.list(toolArgs);
break;
case 'memory_update':
result = await memoryService.update(toolArgs);
break;
case 'memory_delete':
result = await memoryService.delete(toolArgs);
break;
case 'graph_create_entities':
result = await graphService.createEntities(toolArgs);
break;
case 'graph_create_relations':
result = await graphService.createRelations(toolArgs);
break;
case 'graph_add_observations':
result = await graphService.addObservations(toolArgs);
break;
case 'graph_delete_entities':
result = await graphService.deleteEntities(toolArgs);
break;
case 'graph_delete_observations':
result = await graphService.deleteObservations(toolArgs);
break;
case 'graph_delete_relations':
result = await graphService.deleteRelations(toolArgs);
break;
case 'graph_read':
result = await graphService.readGraph(toolArgs);
break;
case 'graph_search_nodes':
result = await graphService.searchNodes(toolArgs);
break;
case 'graph_open_nodes':
result = await graphService.openNodes(toolArgs);
break;
default:
sendLogMessage('warn', 'Tool not found', { tool: toolName });
sendResponse(formatErrorResponse(id, -32601, "Tool not found"));
return;
}
const response = formatToolResponse(id, result);
debugLog(`Sending ${toolName} response`, response);
sendResponse(response);
} catch (error) {
sendLogMessage('error', `Failed to execute tool ${toolName}`, { error: error.message });
sendResponse(formatErrorResponse(id, -32000, error.message));
}
break;
case 'listResources':
case 'resources/list':
try {
debugLog('Processing resources list request', { id });
const resources = await handleListResources();
sendResponse({
jsonrpc: "2.0",
id,
result: {
resources: resources
}
});
} catch (error) {
sendLogMessage('error', 'Error listing resources', { error: error.message });
sendResponse(formatErrorResponse(id, -32000, error.message));
}
break;
case 'readResource':
case 'resources/read':
try {
const { name: resourceName } = params;
const { content, mimeType } = await handleReadResource(resourceName);
sendResponse({
jsonrpc: "2.0",
id,
result: {
resource: {
content,
mimeType
}
}
});
} catch (error) {
sendLogMessage('error', 'Error reading resource', { error: error.message });
sendResponse(formatErrorResponse(id, -32000, error.message));
}
break;
case 'listPrompts':
case 'prompts/list':
// Return empty list as we don't support prompts yet
debugLog('Processing prompts list request', { id });
sendResponse({
jsonrpc: "2.0",
id,
result: {
prompts: []
}
});
break;
default:
sendLogMessage('warn', 'Method not found', { method });
debugLog('Unhandled method', { method, id, params });
sendResponse(formatErrorResponse(id, -32601, `Method not found: ${method}`));
}
} catch (error) {
sendLogMessage('error', 'Failed to process message', { error: error.message, stack: error.stack });
}
});
// Only exit on explicit shutdown signals
process.on('SIGTERM', () => {
sendLogMessage('info', 'Received SIGTERM signal, shutting down...');
rl.close();
const pool = getPool();
if (pool) pool.end();
process.exit(0);
});
process.on('SIGINT', () => {
sendLogMessage('info', 'Received SIGINT signal, shutting down...');
rl.close();
const pool = getPool();
if (pool) pool.end();
process.exit(0);
});