@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 +90 -13
- package/dist/install.d.ts +1 -0
- package/dist/install.js +98 -0
- package/package.json +5 -3
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,
|
|
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
|
|
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
|
|
29
|
-
const results = queries.
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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. '
|
|
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';
|
package/dist/install.js
ADDED
|
@@ -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.
|
|
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
|
-
"
|
|
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.
|
|
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
|
},
|