import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import MemoryClient from 'mem0ai';
import * as dotenv from 'dotenv';
import { createHash } from 'crypto';
// Load environment variables from .env file
dotenv.config();
// Polyfill for window.crypto.subtle.digest
if (typeof window === 'undefined') {
global.window = {
crypto: {
subtle: {
digest: async (algorithm, data) => {
const hash = createHash('sha-256');
hash.update(Buffer.from(data));
return hash.digest();
}
}
}
};
}
// Initialize Mem0 client
console.log("Initializing Mem0 client with:");
console.log("- API Key:", process.env.MEM0_API_KEY?.substring(0, 8) + "...");
console.log("- Organization ID:", process.env.MEM0_ORG_ID);
console.log("- Project ID:", process.env.MEM0_PROJECT_ID);
const mem0Client = new MemoryClient({
apiKey: process.env.MEM0_API_KEY || '',
organizationId: process.env.MEM0_ORG_ID,
projectId: process.env.MEM0_PROJECT_ID
});
// Validate configuration
if (!process.env.MEM0_API_KEY) {
console.error("Error: MEM0_API_KEY environment variable is not set");
process.exit(1);
}
if (!process.env.MEM0_ORG_ID) {
console.error("Error: MEM0_ORG_ID environment variable is not set");
process.exit(1);
}
if (!process.env.MEM0_PROJECT_ID) {
console.error("Error: MEM0_PROJECT_ID environment variable is not set");
process.exit(1);
}
const memoryStreams = new Map();
// Create MCP server instance
const server = new McpServer({
name: "mem0-server",
version: "1.0.0",
description: "Memory stream management server with Mem0 integration"
});
// Define memory stream resource with template
server.resource("memory-stream", new ResourceTemplate("memory://{streamId}", {
list: async () => ({
resources: Array.from(memoryStreams.values()).map(stream => ({
name: stream.name,
uri: `memory://${stream.id}`,
description: `Memory stream with ${stream.content.length} messages`,
mimeType: "application/json"
}))
})
}), async (uri, params) => {
const streamId = typeof params.streamId === 'string' ? params.streamId : params.streamId[0];
const stream = memoryStreams.get(streamId);
if (!stream) {
throw new Error(`Stream ${streamId} not found`);
}
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
id: stream.id,
name: stream.name,
contentLength: stream.content.length,
createdAt: stream.createdAt,
userId: stream.userId,
agentId: stream.agentId
}, null, 2)
}]
};
});
// List available streams
server.resource("memory-streams", "memory://", async () => ({
contents: [{
uri: "memory://",
text: JSON.stringify(Array.from(memoryStreams.values()).map(stream => ({
id: stream.id,
name: stream.name,
contentLength: stream.content.length,
createdAt: stream.createdAt,
userId: stream.userId,
agentId: stream.agentId
})), null, 2)
}]
}));
// Create a new memory stream with Mem0 integration
server.tool("create-memory-stream", {
name: z.string(),
initialContent: z.string().optional(),
userId: z.string().optional(),
agentId: z.string().optional()
}, async ({ name, initialContent, userId, agentId }) => {
console.log(`Creating memory stream: ${name}`);
console.log(`User ID: ${userId}, Agent ID: ${agentId}`);
const streamId = Math.random().toString(36).substring(7);
const stream = {
id: streamId,
name,
content: initialContent ? [initialContent] : [],
createdAt: new Date(),
userId,
agentId
};
memoryStreams.set(streamId, stream);
// If userId and agentId are provided, store in Mem0
if (userId && agentId && initialContent) {
const messages = [
{ role: "user", content: initialContent }
];
const memoryOptions = {
user_id: userId,
agent_id: agentId
};
try {
console.log("Storing memory in Mem0:", {
messages,
options: memoryOptions,
projectId: process.env.MEM0_PROJECT_ID,
organizationId: process.env.MEM0_ORG_ID
});
// Test Mem0 client connection
console.log("Testing Mem0 client connection...");
const testSearch = await mem0Client.search("test", {
user_id: userId,
agent_id: agentId,
threshold: 0.1
});
console.log("Mem0 connection test result:", testSearch);
// Add the actual memory
const result = await mem0Client.add(messages, memoryOptions);
console.log("Successfully stored in Mem0:", result);
}
catch (error) {
console.error("Failed to store memory in Mem0:", error);
if (error instanceof Error) {
console.error("Error details:", {
name: error.name,
message: error.message,
stack: error.stack
});
}
throw error;
}
}
return {
content: [{
type: "text",
text: JSON.stringify({
streamId,
name,
contentLength: stream.content.length,
createdAt: stream.createdAt,
userId,
agentId
}, null, 2)
}]
};
});
// Append content to a memory stream with Mem0 integration
server.tool("append-to-stream", {
streamId: z.string(),
content: z.string(),
role: z.enum(["user", "assistant"]).optional()
}, async ({ streamId, content, role = "user" }) => {
console.log(`Appending to stream ${streamId} with role ${role}`);
const stream = memoryStreams.get(streamId);
if (!stream) {
return {
content: [{ type: "text", text: `Stream ${streamId} not found` }],
isError: true
};
}
stream.content.push(content);
// If stream has Mem0 integration, store the new message
if (stream.userId && stream.agentId) {
const messages = [
{ role, content }
];
const memoryOptions = {
user_id: stream.userId,
agent_id: stream.agentId
};
try {
console.log("Storing message in Mem0:", {
messages,
options: memoryOptions
});
const result = await mem0Client.add(messages, memoryOptions);
console.log("Successfully stored message in Mem0:", result);
}
catch (error) {
console.error("Failed to store message in Mem0:", error);
throw error; // Re-throw to see the full error in the response
}
}
return {
content: [{
type: "text",
text: JSON.stringify({
streamId,
contentLength: stream.content.length,
lastContent: content
}, null, 2)
}]
};
});
// Search memories in Mem0
server.tool("search-memories", {
query: z.string(),
userId: z.string(),
agentId: z.string().optional(),
threshold: z.number().optional()
}, async ({ query, userId, agentId, threshold = 0.1 }) => {
console.log(`Searching memories: "${query}"`);
console.log(`User ID: ${userId}, Agent ID: ${agentId}, Threshold: ${threshold}`);
try {
const searchOptions = {
user_id: userId,
filters: agentId ? {
OR: [
{ agent_id: agentId },
{ user_id: userId }
]
} : undefined,
threshold,
api_version: 'v2'
};
console.log("Search options:", searchOptions);
const results = await mem0Client.search(query, searchOptions);
console.log("Search results:", results);
return {
content: [{
type: "text",
text: JSON.stringify(results, null, 2)
}]
};
}
catch (error) {
console.error("Error searching memories:", error);
throw error; // Re-throw to see the full error in the response
}
});
// Read from a memory stream
server.tool("read-stream", {
streamId: z.string(),
startIndex: z.number().optional(),
endIndex: z.number().optional()
}, async ({ streamId, startIndex = 0, endIndex }) => {
const stream = memoryStreams.get(streamId);
if (!stream) {
return {
content: [{ type: "text", text: `Stream ${streamId} not found` }],
isError: true
};
}
const end = endIndex ?? stream.content.length;
const content = stream.content.slice(startIndex, end);
return {
content: [{
type: "text",
text: JSON.stringify({
streamId,
content,
range: { startIndex, endIndex: end },
totalLength: stream.content.length,
userId: stream.userId,
agentId: stream.agentId
}, null, 2)
}]
};
});
// Delete a memory stream
server.tool("delete-stream", {
streamId: z.string()
}, async ({ streamId }) => {
const stream = memoryStreams.get(streamId);
if (!stream) {
return {
content: [{ type: "text", text: `Stream ${streamId} not found` }],
isError: true
};
}
memoryStreams.delete(streamId);
return {
content: [{
type: "text",
text: JSON.stringify({
streamId,
status: "deleted",
name: stream.name,
userId: stream.userId,
agentId: stream.agentId
}, null, 2)
}]
};
});
// Connect to transport and start server
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("mem0 MCP server started...");