@aeriondyseti/vector-memory-mcp 0.9.0-dev.2 → 0.9.0-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +21 -1
  2. package/dist/scripts/publish.d.ts +13 -0
  3. package/dist/scripts/publish.d.ts.map +1 -0
  4. package/dist/scripts/publish.js +56 -0
  5. package/dist/scripts/publish.js.map +1 -0
  6. package/dist/scripts/test-runner.d.ts +9 -0
  7. package/dist/scripts/test-runner.d.ts.map +1 -0
  8. package/dist/scripts/test-runner.js +61 -0
  9. package/dist/scripts/test-runner.js.map +1 -0
  10. package/dist/scripts/warmup.d.ts +8 -0
  11. package/dist/scripts/warmup.d.ts.map +1 -0
  12. package/dist/scripts/warmup.js +61 -0
  13. package/dist/scripts/warmup.js.map +1 -0
  14. package/dist/src/config/index.d.ts +23 -0
  15. package/dist/src/config/index.d.ts.map +1 -0
  16. package/dist/src/config/index.js +46 -0
  17. package/dist/src/config/index.js.map +1 -0
  18. package/dist/src/db/connection.d.ts +3 -0
  19. package/dist/src/db/connection.d.ts.map +1 -0
  20. package/dist/src/db/connection.js +10 -0
  21. package/dist/src/db/connection.js.map +1 -0
  22. package/dist/src/db/memory.repository.d.ts +13 -0
  23. package/dist/src/db/memory.repository.d.ts.map +1 -0
  24. package/dist/src/db/memory.repository.js +97 -0
  25. package/dist/src/db/memory.repository.js.map +1 -0
  26. package/dist/src/db/schema.d.ts +4 -0
  27. package/dist/src/db/schema.d.ts.map +1 -0
  28. package/dist/src/db/schema.js +12 -0
  29. package/dist/src/db/schema.js.map +1 -0
  30. package/dist/src/http/mcp-transport.d.ts +19 -0
  31. package/dist/src/http/mcp-transport.d.ts.map +1 -0
  32. package/dist/src/http/mcp-transport.js +191 -0
  33. package/dist/src/http/mcp-transport.js.map +1 -0
  34. package/dist/src/http/server.d.ts +12 -0
  35. package/dist/src/http/server.d.ts.map +1 -0
  36. package/dist/src/http/server.js +168 -0
  37. package/dist/src/http/server.js.map +1 -0
  38. package/dist/src/index.d.ts +3 -0
  39. package/dist/src/index.d.ts.map +1 -0
  40. package/dist/src/index.js +59 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/mcp/handlers.d.ts +11 -0
  43. package/dist/src/mcp/handlers.d.ts.map +1 -0
  44. package/dist/src/mcp/handlers.js +175 -0
  45. package/dist/src/mcp/handlers.js.map +1 -0
  46. package/dist/src/mcp/server.d.ts +5 -0
  47. package/dist/src/mcp/server.d.ts.map +1 -0
  48. package/dist/src/mcp/server.js +22 -0
  49. package/dist/src/mcp/server.js.map +1 -0
  50. package/dist/src/mcp/tools.d.ts +9 -0
  51. package/dist/src/mcp/tools.d.ts.map +1 -0
  52. package/dist/src/mcp/tools.js +243 -0
  53. package/dist/src/mcp/tools.js.map +1 -0
  54. package/dist/src/services/embeddings.service.d.ts +12 -0
  55. package/dist/src/services/embeddings.service.d.ts.map +1 -0
  56. package/dist/src/services/embeddings.service.js +37 -0
  57. package/dist/src/services/embeddings.service.js.map +1 -0
  58. package/dist/src/services/memory.service.d.ts +31 -0
  59. package/dist/src/services/memory.service.d.ts.map +1 -0
  60. package/dist/src/services/memory.service.js +131 -0
  61. package/dist/src/services/memory.service.js.map +1 -0
  62. package/dist/src/types/memory.d.ts +17 -0
  63. package/dist/src/types/memory.d.ts.map +1 -0
  64. package/dist/src/types/memory.js +15 -0
  65. package/dist/src/types/memory.js.map +1 -0
  66. package/package.json +12 -8
  67. package/src/config/index.ts +0 -75
  68. package/src/db/connection.ts +0 -11
  69. package/src/db/memory.repository.ts +0 -115
  70. package/src/db/schema.ts +0 -34
  71. package/src/http/mcp-transport.ts +0 -255
  72. package/src/http/server.ts +0 -190
  73. package/src/index.ts +0 -70
  74. package/src/mcp/handlers.ts +0 -248
  75. package/src/mcp/server.ts +0 -34
  76. package/src/mcp/tools.ts +0 -254
  77. package/src/services/embeddings.service.ts +0 -48
  78. package/src/services/memory.service.ts +0 -185
  79. package/src/types/memory.ts +0 -31
@@ -1,75 +0,0 @@
1
- import arg from "arg";
2
- import { join } from "path";
3
-
4
- export type TransportMode = "stdio" | "http" | "both";
5
-
6
- export interface Config {
7
- dbPath: string;
8
- embeddingModel: string;
9
- embeddingDimension: number;
10
- httpPort: number;
11
- httpHost: string;
12
- enableHttp: boolean;
13
- transportMode: TransportMode;
14
- }
15
-
16
- export interface ConfigOverrides {
17
- dbPath?: string;
18
- httpPort?: number;
19
- enableHttp?: boolean;
20
- transportMode?: TransportMode;
21
- }
22
-
23
- // Defaults - always use repo-local .vector-memory folder
24
- const DEFAULT_DB_PATH = join(process.cwd(), ".vector-memory", "memories.db");
25
- const DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
26
- const DEFAULT_EMBEDDING_DIMENSION = 384;
27
- const DEFAULT_HTTP_PORT = 3271;
28
- const DEFAULT_HTTP_HOST = "127.0.0.1";
29
-
30
- function resolvePath(path: string): string {
31
- return path.startsWith("/") ? path : join(process.cwd(), path);
32
- }
33
-
34
- export function loadConfig(overrides: ConfigOverrides = {}): Config {
35
- const transportMode = overrides.transportMode ?? "stdio";
36
- // HTTP enabled by default (needed for hooks), can disable with --no-http
37
- const enableHttp = overrides.enableHttp ?? true;
38
-
39
- return {
40
- dbPath: resolvePath(overrides.dbPath ?? DEFAULT_DB_PATH),
41
- embeddingModel: DEFAULT_EMBEDDING_MODEL,
42
- embeddingDimension: DEFAULT_EMBEDDING_DIMENSION,
43
- httpPort: overrides.httpPort ?? DEFAULT_HTTP_PORT,
44
- httpHost: DEFAULT_HTTP_HOST,
45
- enableHttp,
46
- transportMode,
47
- };
48
- }
49
-
50
- /**
51
- * Parse CLI arguments into config overrides.
52
- */
53
- export function parseCliArgs(argv: string[]): ConfigOverrides {
54
- const args = arg(
55
- {
56
- "--db-file": String,
57
- "--port": Number,
58
- "--no-http": Boolean,
59
-
60
- // Aliases
61
- "-d": "--db-file",
62
- "-p": "--port",
63
- },
64
- { argv, permissive: true }
65
- );
66
-
67
- return {
68
- dbPath: args["--db-file"],
69
- httpPort: args["--port"],
70
- enableHttp: args["--no-http"] ? false : undefined,
71
- };
72
- }
73
-
74
- // Default config for imports that don't use CLI args
75
- export const config = loadConfig();
@@ -1,11 +0,0 @@
1
- import * as lancedb from "@lancedb/lancedb";
2
- import { mkdirSync } from "fs";
3
- import { dirname } from "path";
4
-
5
- export async function connectToDatabase(dbPath: string): Promise<lancedb.Connection> {
6
- // Ensure directory exists
7
- mkdirSync(dirname(dbPath), { recursive: true });
8
-
9
- const db = await lancedb.connect(dbPath);
10
- return db;
11
- }
@@ -1,115 +0,0 @@
1
- import * as lancedb from "@lancedb/lancedb";
2
- import { TABLE_NAME, memorySchema } from "./schema.js";
3
- import {
4
- type Memory,
5
- type VectorRow,
6
- DELETED_TOMBSTONE,
7
- } from "../types/memory.js";
8
-
9
- export class MemoryRepository {
10
- constructor(private db: lancedb.Connection) {}
11
-
12
- private async getTable() {
13
- const names = await this.db.tableNames();
14
- if (names.includes(TABLE_NAME)) {
15
- return await this.db.openTable(TABLE_NAME);
16
- }
17
- // Create with empty data to initialize schema
18
- return await this.db.createTable(TABLE_NAME, [], { schema: memorySchema });
19
- }
20
-
21
- async insert(memory: Memory): Promise<void> {
22
- const table = await this.getTable();
23
- await table.add([
24
- {
25
- id: memory.id,
26
- vector: memory.embedding,
27
- content: memory.content,
28
- metadata: JSON.stringify(memory.metadata),
29
- created_at: memory.createdAt.getTime(),
30
- updated_at: memory.updatedAt.getTime(),
31
- superseded_by: memory.supersededBy,
32
- },
33
- ]);
34
- }
35
-
36
- async upsert(memory: Memory): Promise<void> {
37
- const table = await this.getTable();
38
- const existing = await table.query().where(`id = '${memory.id}'`).limit(1).toArray();
39
-
40
- if (existing.length === 0) {
41
- return await this.insert(memory);
42
- }
43
-
44
- await table.update({
45
- where: `id = '${memory.id}'`,
46
- values: {
47
- vector: memory.embedding,
48
- content: memory.content,
49
- metadata: JSON.stringify(memory.metadata),
50
- created_at: memory.createdAt.getTime(),
51
- updated_at: memory.updatedAt.getTime(),
52
- superseded_by: memory.supersededBy,
53
- },
54
- });
55
- }
56
-
57
- async findById(id: string): Promise<Memory | null> {
58
- const table = await this.getTable();
59
- const results = await table.query().where(`id = '${id}'`).limit(1).toArray();
60
-
61
- if (results.length === 0) {
62
- return null;
63
- }
64
-
65
- const row = results[0];
66
-
67
- // Handle Arrow Vector type conversion
68
- // LanceDB returns an Arrow Vector object which is iterable but not an array
69
- const vectorData = row.vector as any;
70
- const embedding = Array.isArray(vectorData)
71
- ? vectorData
72
- : Array.from(vectorData) as number[];
73
-
74
- return {
75
- id: row.id as string,
76
- content: row.content as string,
77
- embedding,
78
- metadata: JSON.parse(row.metadata as string),
79
- createdAt: new Date(row.created_at as number),
80
- updatedAt: new Date(row.updated_at as number),
81
- supersededBy: row.superseded_by as string | null,
82
- };
83
- }
84
-
85
- async markDeleted(id: string): Promise<boolean> {
86
- const table = await this.getTable();
87
-
88
- // Verify existence first to match previous behavior (return false if not found)
89
- const existing = await table.query().where(`id = '${id}'`).limit(1).toArray();
90
- if (existing.length === 0) {
91
- return false;
92
- }
93
-
94
- const now = Date.now();
95
- await table.update({
96
- where: `id = '${id}'`,
97
- values: {
98
- superseded_by: DELETED_TOMBSTONE,
99
- updated_at: now,
100
- },
101
- });
102
-
103
- return true;
104
- }
105
-
106
- async findSimilar(embedding: number[], limit: number): Promise<VectorRow[]> {
107
- const table = await this.getTable();
108
- const results = await table.vectorSearch(embedding).limit(limit).toArray();
109
-
110
- return results.map((r) => ({
111
- id: r.id as string,
112
- distance: r._distance as number,
113
- }));
114
- }
115
- }
package/src/db/schema.ts DELETED
@@ -1,34 +0,0 @@
1
- import {
2
- Schema,
3
- Field,
4
- FixedSizeList,
5
- Float32,
6
- Utf8,
7
- Timestamp,
8
- TimeUnit,
9
- } from "apache-arrow";
10
-
11
- export const TABLE_NAME = "memories";
12
-
13
- export const memorySchema = new Schema([
14
- new Field("id", new Utf8(), false),
15
- new Field(
16
- "vector",
17
- new FixedSizeList(384, new Field("item", new Float32())),
18
- false
19
- ),
20
- new Field("content", new Utf8(), false),
21
- new Field("metadata", new Utf8(), false), // JSON string
22
- new Field(
23
- "created_at",
24
- new Timestamp(TimeUnit.MILLISECOND),
25
- false
26
- ),
27
- new Field(
28
- "updated_at",
29
- new Timestamp(TimeUnit.MILLISECOND),
30
- false
31
- ),
32
- new Field("superseded_by", new Utf8(), true), // Nullable
33
- ]);
34
-
@@ -1,255 +0,0 @@
1
- /**
2
- * MCP HTTP Transport Handler
3
- *
4
- * Provides StreamableHTTP transport for MCP over HTTP.
5
- * and other HTTP-based MCP clients to connect to the memory server.
6
- *
7
- * This implementation handles the MCP protocol directly using Hono's streaming
8
- * capabilities, since StreamableHTTPServerTransport expects Node.js req/res objects.
9
- */
10
-
11
- import { Hono } from "hono";
12
- import { streamSSE } from "hono/streaming";
13
- import { randomUUID } from "node:crypto";
14
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
15
- import {
16
- CallToolRequestSchema,
17
- ListToolsRequestSchema,
18
- type JSONRPCMessage,
19
- type JSONRPCRequest,
20
- type JSONRPCNotification,
21
- } from "@modelcontextprotocol/sdk/types.js";
22
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
23
-
24
- import { tools } from "../mcp/tools.js";
25
- import { handleToolCall } from "../mcp/handlers.js";
26
- import type { MemoryService } from "../services/memory.service.js";
27
-
28
- interface Session {
29
- server: Server;
30
- serverTransport: InstanceType<typeof InMemoryTransport>;
31
- clientTransport: InstanceType<typeof InMemoryTransport>;
32
- pendingResponses: Map<string | number, (response: JSONRPCMessage) => void>;
33
- sseClients: Set<(message: JSONRPCMessage) => void>;
34
- }
35
-
36
- /**
37
- * Creates MCP routes for a Hono app.
38
- *
39
- * Uses InMemoryTransport internally and bridges to HTTP/SSE manually,
40
- * since StreamableHTTPServerTransport requires Node.js req/res objects.
41
- */
42
- export function createMcpRoutes(memoryService: MemoryService): Hono {
43
- const app = new Hono();
44
-
45
- // Store active sessions by session ID
46
- const sessions: Map<string, Session> = new Map();
47
-
48
- /**
49
- * Creates a new MCP server instance configured with memory tools.
50
- */
51
- async function createSession(): Promise<Session> {
52
- const server = new Server(
53
- { name: "vector-memory-mcp", version: "0.6.0" },
54
- { capabilities: { tools: {} } }
55
- );
56
-
57
- server.setRequestHandler(ListToolsRequestSchema, async () => {
58
- return { tools };
59
- });
60
-
61
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
62
- const { name, arguments: args } = request.params;
63
- return handleToolCall(name, args, memoryService);
64
- });
65
-
66
- // Create linked in-memory transports
67
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
68
-
69
- // Connect server to its transport
70
- await server.connect(serverTransport);
71
-
72
- const session: Session = {
73
- server,
74
- serverTransport,
75
- clientTransport,
76
- pendingResponses: new Map(),
77
- sseClients: new Set(),
78
- };
79
-
80
- // Handle messages from server (responses and notifications)
81
- clientTransport.onmessage = (message: JSONRPCMessage) => {
82
- // Check if this is a response to a pending request
83
- if ("id" in message && message.id !== undefined) {
84
- const resolver = session.pendingResponses.get(message.id);
85
- if (resolver) {
86
- resolver(message);
87
- session.pendingResponses.delete(message.id);
88
- return;
89
- }
90
- }
91
-
92
- // Otherwise, broadcast to SSE clients (notifications)
93
- for (const sendToClient of session.sseClients) {
94
- sendToClient(message);
95
- }
96
- };
97
-
98
- return session;
99
- }
100
-
101
- /**
102
- * Handle POST requests - session initialization and message handling
103
- */
104
- app.post("/mcp", async (c) => {
105
- const sessionId = c.req.header("mcp-session-id");
106
- const body = await c.req.json();
107
-
108
- let session: Session | undefined;
109
- let newSessionId: string | undefined;
110
-
111
- if (sessionId && sessions.has(sessionId)) {
112
- // Reuse existing session
113
- session = sessions.get(sessionId)!;
114
- } else if (isInitializeRequest(body)) {
115
- // New session initialization
116
- newSessionId = randomUUID();
117
- session = await createSession();
118
- sessions.set(newSessionId, session);
119
- console.error(`[vector-memory-mcp] MCP session initialized: ${newSessionId}`);
120
- } else {
121
- // Invalid request - no session ID and not an initialize request
122
- return c.json(
123
- {
124
- jsonrpc: "2.0",
125
- error: {
126
- code: -32000,
127
- message: "Invalid session. Send initialize request without session ID to start.",
128
- },
129
- id: body.id ?? null,
130
- },
131
- 400
132
- );
133
- }
134
-
135
- // Send message to server and wait for response
136
- const response = await sendAndWaitForResponse(session, body);
137
-
138
- // Include session ID header for new sessions
139
- if (newSessionId) {
140
- c.header("mcp-session-id", newSessionId);
141
- }
142
-
143
- return c.json(response);
144
- });
145
-
146
- /**
147
- * Handle GET requests - SSE stream for server-to-client notifications
148
- */
149
- app.get("/mcp", async (c) => {
150
- const sessionId = c.req.header("mcp-session-id");
151
-
152
- if (!sessionId || !sessions.has(sessionId)) {
153
- return c.json(
154
- {
155
- jsonrpc: "2.0",
156
- error: { code: -32000, message: "Invalid or missing session ID" },
157
- id: null,
158
- },
159
- 400
160
- );
161
- }
162
-
163
- const session = sessions.get(sessionId)!;
164
-
165
- return streamSSE(c, async (stream) => {
166
- // Register this SSE client
167
- const sendMessage = (message: JSONRPCMessage) => {
168
- stream.writeSSE({
169
- data: JSON.stringify(message),
170
- event: "message",
171
- });
172
- };
173
-
174
- session.sseClients.add(sendMessage);
175
-
176
- // Keep connection open
177
- try {
178
- // Send a ping every 30 seconds to keep connection alive
179
- while (true) {
180
- await stream.sleep(30000);
181
- await stream.writeSSE({ event: "ping", data: "" });
182
- }
183
- } finally {
184
- session.sseClients.delete(sendMessage);
185
- }
186
- });
187
- });
188
-
189
- /**
190
- * Handle DELETE requests - session termination
191
- */
192
- app.delete("/mcp", async (c) => {
193
- const sessionId = c.req.header("mcp-session-id");
194
-
195
- if (!sessionId || !sessions.has(sessionId)) {
196
- return c.json(
197
- {
198
- jsonrpc: "2.0",
199
- error: { code: -32000, message: "Invalid or missing session ID" },
200
- id: null,
201
- },
202
- 400
203
- );
204
- }
205
-
206
- const session = sessions.get(sessionId)!;
207
-
208
- // Close transports
209
- await session.clientTransport.close();
210
- await session.serverTransport.close();
211
- await session.server.close();
212
-
213
- sessions.delete(sessionId);
214
- console.error(`[vector-memory-mcp] MCP session closed: ${sessionId}`);
215
-
216
- return c.json({ success: true });
217
- });
218
-
219
- return app;
220
- }
221
-
222
- /**
223
- * Send a message to the server and wait for its response.
224
- */
225
- async function sendAndWaitForResponse(
226
- session: Session,
227
- message: JSONRPCRequest | JSONRPCNotification
228
- ): Promise<JSONRPCMessage> {
229
- return new Promise((resolve) => {
230
- // Register response handler for requests (messages with id)
231
- if ("id" in message && message.id !== undefined) {
232
- session.pendingResponses.set(message.id, resolve);
233
- }
234
-
235
- // Send message to server
236
- session.clientTransport.send(message);
237
-
238
- // For notifications (no id), resolve immediately with empty response
239
- if (!("id" in message) || message.id === undefined) {
240
- resolve({ jsonrpc: "2.0" } as JSONRPCMessage);
241
- }
242
- });
243
- }
244
-
245
- /**
246
- * Check if a message is an initialize request.
247
- */
248
- function isInitializeRequest(body: unknown): boolean {
249
- return (
250
- typeof body === "object" &&
251
- body !== null &&
252
- "method" in body &&
253
- (body as { method: string }).method === "initialize"
254
- );
255
- }
@@ -1,190 +0,0 @@
1
- import { Hono } from "hono";
2
- import { cors } from "hono/cors";
3
- import type { MemoryService } from "../services/memory.service.js";
4
- import type { Config } from "../config/index.js";
5
- import { isDeleted } from "../types/memory.js";
6
- import { createMcpRoutes } from "./mcp-transport.js";
7
- import type { Memory } from "../types/memory.js";
8
-
9
- export interface HttpServerOptions {
10
- memoryService: MemoryService;
11
- config: Config;
12
- }
13
-
14
- // Track server start time for uptime calculation
15
- const startedAt = Date.now();
16
-
17
- export function createHttpApp(memoryService: MemoryService, config: Config): Hono {
18
- const app = new Hono();
19
-
20
- // Enable CORS for local development
21
- app.use("/*", cors());
22
-
23
- // Mount MCP routes for StreamableHTTP transport
24
- const mcpApp = createMcpRoutes(memoryService);
25
- app.route("/", mcpApp);
26
-
27
- // Health check endpoint with config info
28
- app.get("/health", (c) => {
29
- return c.json({
30
- status: "ok",
31
- timestamp: new Date().toISOString(),
32
- pid: process.pid,
33
- uptime: Math.floor((Date.now() - startedAt) / 1000),
34
- config: {
35
- dbPath: config.dbPath,
36
- embeddingModel: config.embeddingModel,
37
- embeddingDimension: config.embeddingDimension,
38
- },
39
- });
40
- });
41
-
42
- // Search endpoint
43
- app.post("/search", async (c) => {
44
- try {
45
- const body = await c.req.json();
46
- const query = body.query;
47
- const limit = body.limit ?? 10;
48
-
49
- if (!query || typeof query !== "string") {
50
- return c.json({ error: "Missing or invalid 'query' field" }, 400);
51
- }
52
-
53
- const memories = await memoryService.search(query, limit);
54
-
55
- return c.json({
56
- memories: memories.map((m) => ({
57
- id: m.id,
58
- content: m.content,
59
- metadata: m.metadata,
60
- createdAt: m.createdAt.toISOString(),
61
- })),
62
- count: memories.length,
63
- });
64
- } catch (error) {
65
- const message = error instanceof Error ? error.message : "Unknown error";
66
- return c.json({ error: message }, 500);
67
- }
68
- });
69
-
70
- // Store endpoint
71
- app.post("/store", async (c) => {
72
- try {
73
- const body = await c.req.json();
74
- const { content, metadata, embeddingText } = body;
75
-
76
- if (!content || typeof content !== "string") {
77
- return c.json({ error: "Missing or invalid 'content' field" }, 400);
78
- }
79
-
80
- const memory = await memoryService.store(
81
- content,
82
- metadata ?? {},
83
- embeddingText
84
- );
85
-
86
- return c.json({
87
- id: memory.id,
88
- createdAt: memory.createdAt.toISOString(),
89
- });
90
- } catch (error) {
91
- const message = error instanceof Error ? error.message : "Unknown error";
92
- return c.json({ error: message }, 500);
93
- }
94
- });
95
-
96
- // Delete endpoint
97
- app.delete("/memories/:id", async (c) => {
98
- try {
99
- const id = c.req.param("id");
100
- const deleted = await memoryService.delete(id);
101
-
102
- if (!deleted) {
103
- return c.json({ error: "Memory not found" }, 404);
104
- }
105
-
106
- return c.json({ deleted: true });
107
- } catch (error) {
108
- const message = error instanceof Error ? error.message : "Unknown error";
109
- return c.json({ error: message }, 500);
110
- }
111
- });
112
-
113
- // Get latest handoff
114
- app.get("/handoff", async (c) => {
115
- try {
116
- const handoff = await memoryService.getLatestHandoff();
117
-
118
- if (!handoff) {
119
- return c.json({ error: "No handoff found" }, 404);
120
- }
121
-
122
- // Fetch referenced memories if any
123
- const memoryIds = (handoff.metadata.memory_ids as string[] | undefined) ?? [];
124
- const referencedMemories: Array<{ id: string; content: string }> = [];
125
-
126
- for (const id of memoryIds) {
127
- const memory = await memoryService.get(id);
128
- if (memory && !isDeleted(memory)) {
129
- referencedMemories.push({ id: memory.id, content: memory.content });
130
- }
131
- }
132
-
133
- return c.json({
134
- content: handoff.content,
135
- metadata: handoff.metadata,
136
- referencedMemories,
137
- updatedAt: handoff.updatedAt.toISOString(),
138
- });
139
- } catch (error) {
140
- const message = error instanceof Error ? error.message : "Unknown error";
141
- return c.json({ error: message }, 500);
142
- }
143
- });
144
-
145
- // Get single memory
146
- app.get("/memories/:id", async (c) => {
147
- try {
148
- const id = c.req.param("id");
149
- const memory = await memoryService.get(id);
150
-
151
- if (!memory || isDeleted(memory)) {
152
- return c.json({ error: "Memory not found" }, 404);
153
- }
154
-
155
- return c.json({
156
- id: memory.id,
157
- content: memory.content,
158
- metadata: memory.metadata,
159
- createdAt: memory.createdAt.toISOString(),
160
- updatedAt: memory.updatedAt.toISOString(),
161
- });
162
- } catch (error) {
163
- const message = error instanceof Error ? error.message : "Unknown error";
164
- return c.json({ error: message }, 500);
165
- }
166
- });
167
-
168
- return app;
169
- }
170
-
171
- export async function startHttpServer(
172
- memoryService: MemoryService,
173
- config: Config
174
- ): Promise<{ stop: () => void }> {
175
- const app = createHttpApp(memoryService, config);
176
-
177
- const server = Bun.serve({
178
- port: config.httpPort,
179
- hostname: config.httpHost,
180
- fetch: app.fetch,
181
- });
182
-
183
- console.error(
184
- `[vector-memory-mcp] HTTP server listening on http://${config.httpHost}:${config.httpPort}`
185
- );
186
-
187
- return {
188
- stop: () => server.stop(),
189
- };
190
- }