diff options
| author | Fuwn <[email protected]> | 2026-02-03 20:51:45 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-03 20:51:45 -0800 |
| commit | 79d7142f0d035ca3e270d8f670de70704b54da46 (patch) | |
| tree | 49551d810ab34088509a3334589db994f9b122da | |
| parent | feat(sdk): Add semantic search and embedding support (diff) | |
| download | archived-imemio-79d7142f0d035ca3e270d8f670de70704b54da46.tar.xz archived-imemio-79d7142f0d035ca3e270d8f670de70704b54da46.zip | |
feat(mcp): Wire to Supabase with project and search tools
| -rw-r--r-- | packages/mcp/src/index.ts | 460 |
1 files changed, 453 insertions, 7 deletions
diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index 28ac15a..5fc6a7e 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -3,9 +3,37 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -import { InMemoryStore } from "@imemio/sdk"; +import { + createSupabaseClient, + EmbeddingService, + SupabaseStore, + SupabaseProjectStore, +} from "@imemio/sdk"; -const store = new InMemoryStore(); +function getRequiredEnvironmentVariable(name: string): string { + const value = process.env[name]; + + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + + return value; +} + +function getOptionalEnvironmentVariable(name: string): string | undefined { + return process.env[name]; +} + +const supabaseUrl = getRequiredEnvironmentVariable("SUPABASE_URL"); +const supabaseKey = getRequiredEnvironmentVariable("SUPABASE_ANON_KEY"); +const userId = getRequiredEnvironmentVariable("IMEMIO_USER_ID"); +const openaiApiKey = getOptionalEnvironmentVariable("OPENAI_API_KEY"); +const client = createSupabaseClient(supabaseUrl, supabaseKey); +const memoryStore = new SupabaseStore(client, userId); +const projectStore = new SupabaseProjectStore(client, userId); +const embeddingService = openaiApiKey + ? new EmbeddingService({ apiKey: openaiApiKey }) + : null; const tagSchema = z.object({ id: z.string(), name: z.string(), @@ -27,7 +55,7 @@ server.tool( metadata: metadataSchema.optional().describe("Optional metadata"), }, async (parameters) => { - const memory = await store.create({ + const memory = await memoryStore.create({ content: parameters.content, projectId: parameters.projectId, folderId: parameters.folderId, @@ -52,7 +80,7 @@ server.tool( id: z.string().describe("The memory ID"), }, async (parameters) => { - const result = await store.read(parameters.id); + const result = await memoryStore.read(parameters.id); if (!result.success) { return { @@ -87,7 +115,7 @@ server.tool( metadata: metadataSchema.optional().describe("New metadata"), }, async (parameters) => { - const result = await store.update(parameters.id, { + const result = await memoryStore.update(parameters.id, { content: parameters.content, folderId: parameters.folderId, tags: parameters.tags, @@ -123,7 +151,7 @@ server.tool( id: z.string().describe("The memory ID to delete"), }, async (parameters) => { - const result = await store.delete(parameters.id); + const result = await memoryStore.delete(parameters.id); if (!result.success) { return { @@ -164,7 +192,7 @@ server.tool( tags: parameters.tags, } : undefined; - const memories = await store.list(filter); + const memories = await memoryStore.list(filter); return { content: [ @@ -176,6 +204,424 @@ server.tool( }; }, ); +server.tool( + "create_project", + "Create a new project", + { + name: z.string().describe("The project name"), + description: z.string().optional().describe("Optional project description"), + isGlobal: z.boolean().optional().describe("Whether the project is global"), + }, + async (parameters) => { + const result = await projectStore.create({ + name: parameters.name, + description: parameters.description, + isGlobal: parameters.isGlobal, + }); + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; + }, +); +server.tool( + "get_project", + "Get a project by ID", + { + id: z.string().describe("The project ID"), + }, + async (parameters) => { + const result = await projectStore.get(parameters.id); + + if (!result.success) { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.id}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; + }, +); +server.tool( + "update_project", + "Update an existing project", + { + id: z.string().describe("The project ID to update"), + name: z.string().optional().describe("New project name"), + description: z.string().nullable().optional().describe("New description"), + isGlobal: z.boolean().optional().describe("New global status"), + }, + async (parameters) => { + const result = await projectStore.update(parameters.id, { + name: parameters.name, + description: parameters.description, + isGlobal: parameters.isGlobal, + }); + + if (!result.success) { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.id}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; + }, +); +server.tool( + "delete_project", + "Delete a project", + { + id: z.string().describe("The project ID to delete"), + }, + async (parameters) => { + const result = await projectStore.delete(parameters.id); + + if (!result.success) { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.id}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: `Project deleted: ${parameters.id}`, + }, + ], + }; + }, +); +server.tool("list_projects", "List all projects", {}, async () => { + const result = await projectStore.list(); + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; +}); +server.tool( + "add_folder", + "Add a folder to a project", + { + projectId: z.string().describe("The project ID"), + name: z.string().describe("The folder name"), + description: z.string().optional().describe("Optional folder description"), + }, + async (parameters) => { + const result = await projectStore.addFolder(parameters.projectId, { + name: parameters.name, + description: parameters.description, + }); + + if (!result.success) { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.projectId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; + }, +); +server.tool( + "update_folder", + "Update an existing folder", + { + projectId: z.string().describe("The project ID"), + folderId: z.string().describe("The folder ID to update"), + name: z.string().optional().describe("New folder name"), + description: z.string().nullable().optional().describe("New description"), + }, + async (parameters) => { + const result = await projectStore.updateFolder( + parameters.projectId, + parameters.folderId, + { + name: parameters.name, + description: parameters.description, + }, + ); + + if (!result.success) { + const errorType = result.error.type; + + if (errorType === "PROJECT_NOT_FOUND") { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.projectId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: `Folder not found: ${parameters.folderId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; + }, +); +server.tool( + "remove_folder", + "Remove a folder from a project", + { + projectId: z.string().describe("The project ID"), + folderId: z.string().describe("The folder ID to remove"), + }, + async (parameters) => { + const result = await projectStore.removeFolder( + parameters.projectId, + parameters.folderId, + ); + + if (!result.success) { + const errorType = result.error.type; + + if (errorType === "PROJECT_NOT_FOUND") { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.projectId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: `Folder not found: ${parameters.folderId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: `Folder removed: ${parameters.folderId}`, + }, + ], + }; + }, +); +server.tool( + "list_folders", + "List all folders in a project", + { + projectId: z.string().describe("The project ID"), + }, + async (parameters) => { + const result = await projectStore.listFolders(parameters.projectId); + + if (!result.success) { + return { + content: [ + { + type: "text" as const, + text: `Project not found: ${parameters.projectId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(result.value, null, 2), + }, + ], + }; + }, +); +server.tool( + "search_memories", + "Search memories using semantic similarity", + { + embedding: z + .array(z.number()) + .describe("The embedding vector (1536 dimensions) to search with"), + threshold: z + .number() + .optional() + .describe("Minimum similarity threshold (0-1, default 0.7)"), + limit: z + .number() + .optional() + .describe("Maximum number of results (default 10)"), + projectId: z.string().optional().describe("Filter by project ID"), + folderId: z.string().optional().describe("Filter by folder ID"), + }, + async (parameters) => { + const results = await memoryStore.search(parameters.embedding, { + threshold: parameters.threshold, + limit: parameters.limit, + projectId: parameters.projectId, + folderId: parameters.folderId, + }); + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(results, null, 2), + }, + ], + }; + }, +); +server.tool( + "generate_embedding", + "Generate an embedding vector for text content", + { + text: z.string().describe("The text to generate an embedding for"), + }, + async (parameters) => { + if (!embeddingService) { + return { + content: [ + { + type: "text" as const, + text: "Embedding service not configured. Set OPENAI_API_KEY environment variable.", + }, + ], + isError: true, + }; + } + + const embedding = await embeddingService.generate(parameters.text); + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify({ embedding, dimensions: embedding.length }), + }, + ], + }; + }, +); +server.tool( + "create_memory_with_embedding", + "Create a new memory with auto-generated embedding", + { + content: z.string().describe("The content of the memory"), + projectId: z.string().describe("The project ID this memory belongs to"), + folderId: z.string().optional().describe("Optional folder ID"), + tags: z.array(tagSchema).optional().describe("Optional tags"), + metadata: metadataSchema.optional().describe("Optional metadata"), + }, + async (parameters) => { + if (!embeddingService) { + return { + content: [ + { + type: "text" as const, + text: "Embedding service not configured. Set OPENAI_API_KEY environment variable.", + }, + ], + isError: true, + }; + } + + const embedding = await embeddingService.generate(parameters.content); + const memory = await memoryStore.create({ + content: parameters.content, + projectId: parameters.projectId, + folderId: parameters.folderId, + tags: parameters.tags, + metadata: parameters.metadata, + embedding, + }); + + return { + content: [ + { + type: "text" as const, + text: JSON.stringify(memory, null, 2), + }, + ], + }; + }, +); async function main(): Promise<void> { const transport = new StdioServerTransport(); |