@postnesia/mcp 0.1.7 → 0.1.12

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.
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { z } from "zod";
9
- import { getDb, queries, createMemory } from "@postnesia/db";
9
+ import { getDb, closeDb, createMemory } from "@postnesia/db";
10
+ import queries from "@postnesia/db/queries";
10
11
  import { embed } from "@postnesia/db/embeddings";
11
12
  import { logAccess } from "@postnesia/db/access";
12
13
  import { runConsolidation } from "@postnesia/db/importance";
@@ -19,14 +20,37 @@ const server = new McpServer({
19
20
  // memory tools
20
21
  // ---------------------------------------------------------------------------
21
22
  server.registerTool("memory_search", {
22
- description: "Search memories by content. Returns all matching memories.",
23
+ description: "Search memories by semantic similarity. Optionally filter by type.",
23
24
  inputSchema: {
24
25
  query: z.string().describe("Search query"),
25
26
  limit: z.number().optional().describe("Maximum results to return (default: 10)"),
27
+ type: z.enum(["event", "decision", "lesson", "preference", "person", "technical"]).optional().describe("Filter by memory type"),
28
+ },
29
+ }, async ({ query, limit = 10, type }) => {
30
+ const [embedding] = await embed(query);
31
+ if (!embedding) {
32
+ throw new ReferenceError('Unable to create query embedding');
33
+ }
34
+ const embeddingBuffer = Buffer.from(embedding.buffer);
35
+ const results = type
36
+ ? queries.vectorSearchByType(db).all(embeddingBuffer, limit, type)
37
+ : queries.vectorSearch(db).all(embeddingBuffer, limit);
38
+ for (const result of results) {
39
+ logAccess(result.id, "search");
40
+ }
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
43
+ };
44
+ });
45
+ server.registerTool("memory_search_keyword", {
46
+ description: "Keyword search memories by content (use when semantic search is not appropriate).",
47
+ inputSchema: {
48
+ query: z.string().describe("Keyword or phrase to search for"),
49
+ limit: z.number().optional().describe("Maximum results to return (default: 10)"),
26
50
  },
27
51
  }, async ({ query, limit = 10 }) => {
28
- const embedding = await embed(query);
29
- const results = queries.vectorSearch(db).all(Buffer.from(embedding.buffer), limit);
52
+ const pattern = `%${query}%`;
53
+ const results = queries.searchMemories(db).all(pattern, pattern, limit);
30
54
  for (const result of results) {
31
55
  logAccess(result.id, "search");
32
56
  }
@@ -34,6 +58,54 @@ server.registerTool("memory_search", {
34
58
  content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
35
59
  };
36
60
  });
61
+ server.registerTool("memory_by_context", {
62
+ description: "Find memories by context string (LIKE match).",
63
+ inputSchema: {
64
+ context: z.string().describe("Context string to search for"),
65
+ limit: z.number().optional().describe("Maximum results (default: 10)"),
66
+ },
67
+ }, async ({ context, limit = 10 }) => {
68
+ const results = queries.getMemoriesByContext(db).all(`%${context}%`, limit);
69
+ for (const r of results)
70
+ logAccess(r.id, "search");
71
+ return {
72
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
73
+ };
74
+ });
75
+ server.registerTool("memory_by_tag", {
76
+ description: "Find memories by tag (exact match).",
77
+ inputSchema: {
78
+ tag: z.string().describe("Tag to filter by"),
79
+ limit: z.number().optional().describe("Maximum results (default: 10)"),
80
+ },
81
+ }, async ({ tag, limit = 10 }) => {
82
+ const results = queries.getMemoriesByTag(db).all(tag, limit);
83
+ for (const r of results)
84
+ logAccess(r.id, "search");
85
+ return {
86
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
87
+ };
88
+ });
89
+ server.registerTool("memory_supersede_chain", {
90
+ description: "Trace the supersede chain for a memory (shows the full replacement history).",
91
+ inputSchema: {
92
+ memoryId: z.number().describe("Memory ID to trace"),
93
+ },
94
+ }, async ({ memoryId }) => {
95
+ const chain = queries.getSupersedeChain(db).all(memoryId);
96
+ return {
97
+ content: [{ type: "text", text: JSON.stringify(chain, null, 2) }],
98
+ };
99
+ });
100
+ server.registerTool("memory_core", {
101
+ description: "Get all core memories (foundational memories that are always loaded).",
102
+ inputSchema: {},
103
+ }, async () => {
104
+ const results = queries.coreMemories(db).all();
105
+ return {
106
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
107
+ };
108
+ });
37
109
  server.registerTool("memory_add", {
38
110
  description: "Add a new memory to the database",
39
111
  inputSchema: {
@@ -47,7 +119,10 @@ server.registerTool("memory_add", {
47
119
  },
48
120
  }, async ({ content, contentL1, type, importance, tags, context, core }) => {
49
121
  const content_l1 = contentL1 || content.slice(0, 200);
50
- const embedding = await embed(content);
122
+ const [embedding] = await embed(content);
123
+ if (!embedding) {
124
+ throw new ReferenceError('Unable to create query embedding');
125
+ }
51
126
  const id = createMemory(db, {
52
127
  timestamp: new Date().toISOString(),
53
128
  content,
@@ -81,12 +156,11 @@ server.registerTool("memory_update_core", {
81
156
  throw new Error(`Memory #${memoryId} not found`);
82
157
  if (!existing.core)
83
158
  throw new Error(`Memory #${memoryId} is not a core memory. Use memory_add with supersedes_id for regular memories.`);
84
- const embedding = await embed(content);
85
- db.prepare(`
86
- UPDATE memory
87
- SET content = ?, content_l1 = ?, updated_at = datetime('now')
88
- WHERE id = ?
89
- `).run(content, contentL1, memoryId);
159
+ const [embedding] = await embed(content);
160
+ if (!embedding) {
161
+ throw new ReferenceError('Unable to create query embedding');
162
+ }
163
+ queries.updateCoreMemory(db).run(content, contentL1, memoryId);
90
164
  db.prepare("DELETE FROM vec_memories WHERE memory_id = ?").run(BigInt(memoryId));
91
165
  db.prepare("INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)").run(BigInt(memoryId), Buffer.from(embedding.buffer));
92
166
  return {
@@ -202,7 +276,7 @@ server.registerTool("journal_add", {
202
276
  date: z.string().describe("Date YYYY-MM-DD"),
203
277
  content: z.string().describe("Full journal narrative"),
204
278
  learned: z.string().optional().describe("What I learned (optional)"),
205
- learnedAboutRye: z.string().optional().describe("What I learned about Rye (optional)"),
279
+ learnedAboutRye: z.string().optional().describe("What I learned about the user (optional)"),
206
280
  keyMoments: z.string().optional().describe("Key moments (optional)"),
207
281
  mood: z.string().optional().describe("Mood/feeling (optional)"),
208
282
  },
@@ -232,7 +306,7 @@ server.registerTool("task_create", {
232
306
  inputSchema: {
233
307
  title: z.string().describe("Short task title"),
234
308
  description: z.string().optional().describe("Detailed description of what needs to be done"),
235
- session_id: z.string().optional().describe("Project or feature label to group related tasks (e.g. 'openmind-mcp', 'auth-refactor')"),
309
+ session_id: z.string().optional().describe("Project or feature label to group related tasks (e.g. 'postnesia-mcp', 'auth-refactor')"),
236
310
  memory_id: z.number().optional().describe("Optional ID of a related memory"),
237
311
  },
238
312
  }, async ({ title, description, session_id, memory_id }) => {
@@ -310,5 +384,8 @@ async function main() {
310
384
  }
311
385
  main().catch((error) => {
312
386
  console.error("Fatal error:", error);
387
+ closeDb();
313
388
  process.exit(1);
314
389
  });
390
+ process.on('SIGINT', () => { closeDb(); process.exit(0); });
391
+ process.on('SIGTERM', () => { closeDb(); process.exit(0); });
@@ -0,0 +1 @@
1
+ import 'dotenv/config';
@@ -0,0 +1,98 @@
1
+ import 'dotenv/config';
2
+ var TypeEnum;
3
+ (function (TypeEnum) {
4
+ TypeEnum["claude"] = "claude";
5
+ TypeEnum["opencode"] = "opencode";
6
+ })(TypeEnum || (TypeEnum = {}));
7
+ ;
8
+ var ScopeEnum;
9
+ (function (ScopeEnum) {
10
+ ScopeEnum["user"] = "user";
11
+ ScopeEnum["project"] = "project";
12
+ ScopeEnum["local"] = "local";
13
+ })(ScopeEnum || (ScopeEnum = {}));
14
+ ;
15
+ if (!process.env['DATABASE_URL']) {
16
+ console.error('DATABASE_URL (absolute path) environment variable is not configured');
17
+ process.exit(1);
18
+ }
19
+ if (!process.env['GEMINI_API_KEY']) {
20
+ console.error('GEMINI_API_KEY environment variable is not configured');
21
+ process.exit(1);
22
+ }
23
+ const config = {
24
+ claude: {
25
+ mcpServers: {
26
+ postnesia: {
27
+ type: "stdio",
28
+ enabled: true,
29
+ command: "npx",
30
+ args: [
31
+ "pn-mcp"
32
+ ],
33
+ env: {
34
+ DATABASE_URL: process.env['DATABASE_URL'],
35
+ GEMINI_API_KEY: process.env['GEMINI_API_KEY']
36
+ }
37
+ }
38
+ }
39
+ },
40
+ opencode: {
41
+ mcp: {
42
+ postnesia: {
43
+ type: "local",
44
+ enabled: false,
45
+ command: [
46
+ "npx",
47
+ "pn-mcp"
48
+ ],
49
+ environment: {
50
+ DATABASE_URL: process.env['DATABASE_URL'],
51
+ GEMINI_API_KEY: process.env['GEMINI_API_KEY']
52
+ }
53
+ }
54
+ }
55
+ }
56
+ };
57
+ const [_, __, type, scope] = process.argv;
58
+ if (type === 'claude') {
59
+ const msg = 'Add the following to your $file configuration:';
60
+ if (scope === 'project') {
61
+ console.log(msg.replace('$file', `${process.cwd()}/.mcp.json`));
62
+ console.log(JSON.stringify(config.claude, null, 2));
63
+ process.exit(0);
64
+ }
65
+ if (scope === 'local') {
66
+ console.log(msg.replace('$file', `${process.env.HOME}/.claude.json for the current project`));
67
+ console.log(JSON.stringify({ projects: { [process.cwd()]: config.claude } }, null, 2));
68
+ process.exit(0);
69
+ }
70
+ if (scope === 'user' || !scope) {
71
+ console.log(msg.replace('$file', `${process.env.HOME}/.claude.json`));
72
+ console.log(JSON.stringify(config.claude, null, 2));
73
+ process.exit(0);
74
+ }
75
+ console.error('Invalid scope argument provided: "project", "user" or "local"');
76
+ process.exit(1);
77
+ }
78
+ if (type === 'opencode') {
79
+ const msg = 'Add the following to your $file configuration:';
80
+ if (scope === 'project') {
81
+ console.log(msg.replace('$file', `${process.cwd()}/.opencode.json`));
82
+ console.log(JSON.stringify(config.opencode, null, 2));
83
+ process.exit(0);
84
+ }
85
+ if (!scope || scope === 'user') {
86
+ console.log(msg.replace('$file', `${process.env.HOME}/.config/opencode/opencode.json`));
87
+ console.log(JSON.stringify(config.opencode, null, 2));
88
+ process.exit(0);
89
+ }
90
+ if (scope === 'local') {
91
+ console.log('Local scope does not exist for opencode');
92
+ process.exit(1);
93
+ }
94
+ console.error('Invalid scope argument provided: "project", "user"');
95
+ process.exit(1);
96
+ }
97
+ console.error('Argument type opencode or claude is required');
98
+ process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postnesia/mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.12",
4
4
  "description": "An MCP server to interact with the Postnesia database",
5
5
  "type": "module",
6
6
  "private": false,
@@ -8,15 +8,17 @@
8
8
  "dist"
9
9
  ],
10
10
  "bin": {
11
- "postnesia-mcp": "./dist/index.js"
11
+ "pn-mcp": "./dist/index.js",
12
+ "pn-mcp-install": "./dist/install.js"
12
13
  },
13
14
  "dependencies": {
14
15
  "@modelcontextprotocol/sdk": "^1.26.0",
15
16
  "zod": "^4.3.6",
16
- "@postnesia/db": "0.1.7"
17
+ "@postnesia/db": "0.1.12"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@types/node": "^22.10.5",
21
+ "dotenv": "^17.3.1",
20
22
  "tsx": "^4.19.2",
21
23
  "typescript": "^5.7.3"
22
24
  },