aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-03 20:51:45 -0800
committerFuwn <[email protected]>2026-02-03 20:51:45 -0800
commit79d7142f0d035ca3e270d8f670de70704b54da46 (patch)
tree49551d810ab34088509a3334589db994f9b122da
parentfeat(sdk): Add semantic search and embedding support (diff)
downloadarchived-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.ts460
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();