@postnesia/mcp 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -1,117 +1,63 @@
1
- # Postnesia Memory MCP Server
1
+ # @postnesia/mcp
2
2
 
3
- Model Context Protocol server for main agent memory system. Exposes memory operations as standardized tools.
3
+ MCP server for the Postnesia memory system. Exposes all memory, journal, and task operations as Model Context Protocol tools over stdio.
4
4
 
5
- ## Installation
5
+ ## Usage
6
6
 
7
- ```bash
8
- cd postnesia
9
- npm install
10
- npm run db:generate
11
- ```
12
-
13
- ## Configuration
14
-
15
- Add to your MCP settings (e.g., Claude Desktop config):
7
+ Add to your MCP config (e.g. `.claude/settings.json` or Claude Desktop):
16
8
 
17
9
  ```json
18
10
  {
19
11
  "mcpServers": {
20
12
  "postnesia": {
21
- "command": "/absolute/path/to/tsx",
13
+ "command": "npx",
14
+ "args": ["postnesia-mcp"],
22
15
  "env": {
23
- "DATABASE_URL": "file:/absolute/path/to/memory.db",
24
- "GEMINI_API_KEY": "token-for-gemini-embedding-model-api"
16
+ "DATABASE_URL": "file:///absolute/path/to/memory.db",
17
+ "GEMINI_API_KEY": "your-gemini-api-key",
18
+ "EMBEDDING_MODEL": "gemini-embedding-001",
19
+ "EMBEDDING_DIMENSIONS": "768"
25
20
  }
26
21
  }
27
22
  }
28
23
  }
29
24
  ```
30
25
 
31
- Or use npm script:
26
+ `DATABASE_URL` must be an absolute `file://` URL.
32
27
 
33
- ```json
34
- {
35
- "mcpServers": {
36
- "openmind": {
37
- "command": "/absolute/path/to/tsx",
38
- "env": {
39
- "DATABASE_URL": "/absolute/path/to/memory.db",
40
- "GEMINI_API_KEY": "token-for-gemini-embedding-model-api"
41
- }
42
- }
43
- }
44
- }
45
- ```
46
-
47
- ## Available Tools
48
-
49
- ### memory_search
50
- Semantic search across memories.
51
- - `query` (required): Search text
52
- - `maxResults` (optional): Max results (default: 10)
53
- - `minScore` (optional): Min similarity 0-1 (default: 0.3)
54
-
55
- ### memory_add
56
- Create a new memory.
57
- - `content` (required): Full memory text
58
- - `contentL1` (optional): Compressed form
59
- - `type` (required): event|decision|lesson|preference|person|technical
60
- - `importance` (required): 1-5
61
- - `tags` (required): Array of tags
62
- - `context` (optional): Creation context
63
-
64
- ### memory_recent
65
- Get recent memories.
66
- - `hours` (optional): Hours to look back (default: 24)
67
- - `limit` (optional): Max results (default: 20)
28
+ ## Tools
68
29
 
69
- ### memory_context
70
- Get contextually related memories.
71
- - `query` (required): Context query
72
- - `maxResults` (optional): Max results (default: 5)
30
+ ### Memory
73
31
 
74
- ### memory_stats
75
- Get database statistics. No parameters.
32
+ | Tool | Required | Optional |
33
+ |---|---|---|
34
+ | `memory_search` | `query` | `limit` (default: 10) |
35
+ | `memory_add` | `content`, `type`, `importance`, `tags` | `contentL1`, `context`, `core` |
36
+ | `memory_update_core` | `memoryId`, `content`, `contentL1` | — |
37
+ | `memory_recent` | — | `hours` (default: 24), `limit` (default: 20) |
38
+ | `memory_stats` | — | — |
39
+ | `memory_consolidate` | — | — |
40
+ | `memory_relationships` | `memoryId` | — |
76
41
 
77
- ### memory_consolidate
78
- Run consolidation cycle (decay + boost). No parameters - always applies changes.
42
+ **Memory types:** `event` `decision` `lesson` `preference` `person` `technical`
79
43
 
80
- ### journal_add
81
- Add daily journal entry.
82
- - `date` (required): YYYY-MM-DD
83
- - `content` (required): Full narrative
84
- - `learned` (optional): What I learned
85
- - `learnedAboutRye` (optional): What I learned about Rye
86
- - `keyMoments` (optional): Key moments
87
- - `mood` (optional): Mood/feeling
44
+ ### Journal
88
45
 
89
- ### journal_recent
90
- Get recent journal entries.
91
- - `days` (optional): Days to look back (default: 7)
46
+ | Tool | Required | Optional |
47
+ |---|---|---|
48
+ | `journal_add` | `date` (YYYY-MM-DD), `content` | `learned`, `learnedAboutRye`, `keyMoments`, `mood` |
49
+ | `journal_recent` | — | `days` (default: 7) |
92
50
 
93
- ### memory_relationships
94
- View relationship graph for a memory.
95
- - `memoryId` (required): Memory ID to explore
51
+ ### Tasks
96
52
 
97
- ## Access Tracking
98
-
99
- All read operations (search, recent, context) automatically log accesses, which feed into the importance dynamics system.
100
-
101
- ## Testing
102
-
103
- Test the server manually:
104
-
105
- ```bash
106
- npm run mcp
107
- ```
53
+ | Tool | Required | Optional |
54
+ |---|---|---|
55
+ | `task_create` | `title` | `description`, `session_id`, `memory_id` |
56
+ | `task_update` | `taskId` | `status`, `title`, `description` |
57
+ | `task_list` | — | `status`, `session_id`, `limit` (default: 50) |
108
58
 
109
- Then send MCP requests via stdin (see MCP protocol docs).
59
+ **Task statuses:** `pending` `in_progress` `completed` `cancelled`
110
60
 
111
- ## Architecture
61
+ ## Implementation
112
62
 
113
- - Built on Prisma + SQLite
114
- - Automatic access tracking
115
- - Dynamic importance scoring (decay + boost)
116
- - Relationship graph support
117
- - Journal integration
63
+ Built with `McpServer` from `@modelcontextprotocol/sdk` and Zod input schemas. All read operations (`memory_search`, `memory_recent`) automatically log accesses which feed into the importance decay/boost system.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * MCP Server for Belle's Memory System
3
+ * MCP Server for Postnesia Memory System
4
4
  * Exposes memory operations as Model Context Protocol tools
5
5
  */
6
- import 'dotenv/config';
6
+ export {};
package/dist/index.js CHANGED
@@ -1,514 +1,275 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * MCP Server for Belle's Memory System
3
+ * MCP Server for Postnesia Memory System
4
4
  * Exposes memory operations as Model Context Protocol tools
5
5
  */
6
- import 'dotenv/config';
7
- import { Server } from "@modelcontextprotocol/sdk/server";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
8
+ import { z } from "zod";
10
9
  import { getDb, queries, createMemory } from "@postnesia/db";
11
10
  import { embed } from "@postnesia/db/embeddings";
12
- import { logAccess } from "./access.js";
13
- import { runConsolidation } from "./importance.js";
11
+ import { logAccess } from "@postnesia/db/access";
12
+ import { runConsolidation } from "@postnesia/db/importance";
14
13
  const db = getDb();
15
- const server = new Server({
16
- name: "openmind",
14
+ const server = new McpServer({
15
+ name: "postnesia",
17
16
  version: "1.0.0",
18
- }, {
19
- capabilities: {
20
- tools: {},
17
+ });
18
+ // ---------------------------------------------------------------------------
19
+ // memory tools
20
+ // ---------------------------------------------------------------------------
21
+ server.registerTool("memory_search", {
22
+ description: "Search memories by content. Returns all matching memories.",
23
+ inputSchema: {
24
+ query: z.string().describe("Search query"),
25
+ limit: z.number().optional().describe("Maximum results to return (default: 10)"),
21
26
  },
27
+ }, async ({ query, limit = 10 }) => {
28
+ const embedding = await embed(query);
29
+ const results = queries.vectorSearch(db).all(Buffer.from(embedding.buffer), limit);
30
+ for (const result of results) {
31
+ logAccess(result.id, "search");
32
+ }
33
+ return {
34
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
35
+ };
22
36
  });
23
- // Tool definitions
24
- server.setRequestHandler(ListToolsRequestSchema, async () => {
37
+ server.registerTool("memory_add", {
38
+ description: "Add a new memory to the database",
39
+ inputSchema: {
40
+ content: z.string().describe("Memory content (full form)"),
41
+ contentL1: z.string().optional().describe("Compressed L1 form (optional, will auto-generate if omitted)"),
42
+ type: z.enum(["event", "decision", "lesson", "preference", "person", "technical"]).describe("Memory type"),
43
+ importance: z.number().describe("Base importance 1-5"),
44
+ tags: z.array(z.string()).describe("Tags for categorization"),
45
+ context: z.string().optional().describe("Optional context about when/why this memory was created"),
46
+ core: z.boolean().optional().describe("Mark as a core memory (always loaded, never decays, cannot be superseded)"),
47
+ },
48
+ }, async ({ content, contentL1, type, importance, tags, context, core }) => {
49
+ const content_l1 = contentL1 || content.slice(0, 200);
50
+ const embedding = await embed(content);
51
+ const id = createMemory(db, {
52
+ timestamp: new Date().toISOString(),
53
+ content,
54
+ content_l1,
55
+ type,
56
+ core: core ? 1 : 0,
57
+ importance,
58
+ context: context || undefined,
59
+ tags,
60
+ embedding,
61
+ });
25
62
  return {
26
- tools: [
27
- {
28
- name: "memory_search",
29
- description: "Search memories by content. Returns all matching memories.",
30
- inputSchema: {
31
- type: "object",
32
- properties: {
33
- query: {
34
- type: "string",
35
- description: "Search query",
36
- },
37
- limit: {
38
- type: "number",
39
- description: "Maximum results to return (default: 10)",
40
- },
41
- },
42
- required: ["query"],
43
- },
44
- },
45
- {
46
- name: "memory_add",
47
- description: "Add a new memory to the database",
48
- inputSchema: {
49
- type: "object",
50
- properties: {
51
- content: {
52
- type: "string",
53
- description: "Memory content (full form)",
54
- },
55
- contentL1: {
56
- type: "string",
57
- description: "Compressed L1 form (optional, will auto-generate if omitted)",
58
- },
59
- type: {
60
- type: "string",
61
- enum: ["event", "decision", "lesson", "preference", "person", "technical"],
62
- description: "Memory type",
63
- },
64
- importance: {
65
- type: "number",
66
- description: "Base importance 1-5",
67
- },
68
- tags: {
69
- type: "array",
70
- items: { type: "string" },
71
- description: "Tags for categorization",
72
- },
73
- context: {
74
- type: "string",
75
- description: "Optional context about when/why this memory was created",
76
- },
77
- core: {
78
- type: "boolean",
79
- description: "Mark as a core memory (always loaded, never decays, cannot be superseded)",
80
- },
81
- },
82
- required: ["content", "type", "importance", "tags"],
83
- },
84
- },
85
- {
86
- name: "memory_update_core",
87
- description: "Update the content of an existing core memory in place (core memories must be updated, never superseded)",
88
- inputSchema: {
89
- type: "object",
90
- properties: {
91
- memoryId: {
92
- type: "number",
93
- description: "ID of the memory to update",
94
- },
95
- content: {
96
- type: "string",
97
- description: "New full content",
98
- },
99
- contentL1: {
100
- type: "string",
101
- description: "New compressed L1 summary",
102
- },
103
- },
104
- required: ["memoryId", "content", "contentL1"],
105
- },
106
- },
107
- {
108
- name: "memory_recent",
109
- description: "Get recent memories within time window",
110
- inputSchema: {
111
- type: "object",
112
- properties: {
113
- hours: {
114
- type: "number",
115
- description: "Hours to look back (default: 24)",
116
- },
117
- limit: {
118
- type: "number",
119
- description: "Maximum results (default: 20)",
120
- },
121
- },
122
- },
123
- },
124
- {
125
- name: "memory_stats",
126
- description: "Get memory database statistics",
127
- inputSchema: {
128
- type: "object",
129
- properties: {},
130
- },
131
- },
132
- {
133
- name: "memory_consolidate",
134
- description: "Run memory consolidation cycle (decay old, boost accessed)",
135
- inputSchema: {
136
- type: "object",
137
- properties: {},
138
- },
139
- },
140
- {
141
- name: "journal_add",
142
- description: "Add a daily journal entry",
143
- inputSchema: {
144
- type: "object",
145
- properties: {
146
- date: {
147
- type: "string",
148
- description: "Date YYYY-MM-DD",
149
- },
150
- content: {
151
- type: "string",
152
- description: "Full journal narrative",
153
- },
154
- learned: {
155
- type: "string",
156
- description: "What I learned (optional)",
157
- },
158
- learnedAboutRye: {
159
- type: "string",
160
- description: "What I learned about Rye (optional)",
161
- },
162
- keyMoments: {
163
- type: "string",
164
- description: "Key moments (optional)",
165
- },
166
- mood: {
167
- type: "string",
168
- description: "Mood/feeling (optional)",
169
- },
170
- },
171
- required: ["date", "content"],
172
- },
173
- },
63
+ content: [
174
64
  {
175
- name: "journal_recent",
176
- description: "Get recent journal entries",
177
- inputSchema: {
178
- type: "object",
179
- properties: {
180
- days: {
181
- type: "number",
182
- description: "Days to look back (default: 7)",
183
- },
184
- },
185
- },
65
+ type: "text",
66
+ text: `Created memory #${id}\n Type: ${type}\n Core: ${core ? "yes" : "no"}\n Importance: ${importance}/5\n Tags: ${tags.join(", ")}`,
186
67
  },
68
+ ],
69
+ };
70
+ });
71
+ server.registerTool("memory_update_core", {
72
+ description: "Update the content of an existing core memory in place (core memories must be updated, never superseded)",
73
+ inputSchema: {
74
+ memoryId: z.number().describe("ID of the memory to update"),
75
+ content: z.string().describe("New full content"),
76
+ contentL1: z.string().describe("New compressed L1 summary"),
77
+ },
78
+ }, async ({ memoryId, content, contentL1 }) => {
79
+ const existing = db.prepare("SELECT id, core FROM memory WHERE id = ?").get(memoryId);
80
+ if (!existing)
81
+ throw new Error(`Memory #${memoryId} not found`);
82
+ if (!existing.core)
83
+ 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);
90
+ db.prepare("DELETE FROM vec_memories WHERE memory_id = ?").run(BigInt(memoryId));
91
+ db.prepare("INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)").run(BigInt(memoryId), Buffer.from(embedding.buffer));
92
+ return {
93
+ content: [{ type: "text", text: `Updated core memory #${memoryId}` }],
94
+ };
95
+ });
96
+ server.registerTool("memory_recent", {
97
+ description: "Get recent memories within time window",
98
+ inputSchema: {
99
+ hours: z.number().optional().describe("Hours to look back (default: 24)"),
100
+ limit: z.number().optional().describe("Maximum results (default: 20)"),
101
+ },
102
+ }, async ({ hours = 24, limit = 20 }) => {
103
+ const results = queries.getRecentMemories(db).all(`-${hours} hours`, limit);
104
+ for (const result of results) {
105
+ logAccess(result.id, "recent");
106
+ }
107
+ return {
108
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
109
+ };
110
+ });
111
+ server.registerTool("memory_stats", {
112
+ description: "Get memory database statistics",
113
+ inputSchema: {},
114
+ }, async () => {
115
+ const stats = queries.getStats(db).all();
116
+ const total = db.prepare("SELECT COUNT(*) as count FROM memory").get().count;
117
+ const tags = db.prepare("SELECT COUNT(*) as count FROM tag").get().count;
118
+ const relationships = db.prepare("SELECT COUNT(*) as count FROM relationship").get().count;
119
+ const accessLogs = db.prepare("SELECT COUNT(*) as count FROM access_log").get().count;
120
+ const rows = stats
121
+ .map((s) => ` ${s.type.padEnd(15)} ${s.count.toString().padStart(3)} memories (avg importance: ${s.avg_importance?.toFixed(1)})`)
122
+ .join("\n");
123
+ return {
124
+ content: [
187
125
  {
188
- name: "memory_relationships",
189
- description: "View relationship graph for a memory",
190
- inputSchema: {
191
- type: "object",
192
- properties: {
193
- memoryId: {
194
- type: "number",
195
- description: "Memory ID to explore",
196
- },
197
- },
198
- required: ["memoryId"],
199
- },
126
+ type: "text",
127
+ text: `Memory Statistics:\n\n${rows}\n\n Total: ${total} memories\n Tags: ${tags}\n Relationships: ${relationships}\n Access Logs: ${accessLogs}`,
200
128
  },
129
+ ],
130
+ };
131
+ });
132
+ server.registerTool("memory_consolidate", {
133
+ description: "Run memory consolidation cycle (decay old, boost accessed)",
134
+ inputSchema: {},
135
+ }, async () => {
136
+ const results = runConsolidation();
137
+ return {
138
+ content: [
201
139
  {
202
- name: "task_create",
203
- description: "Create a new task. Use session_id to group tasks by project or feature.",
204
- inputSchema: {
205
- type: "object",
206
- properties: {
207
- title: {
208
- type: "string",
209
- description: "Short task title",
210
- },
211
- description: {
212
- type: "string",
213
- description: "Detailed description of what needs to be done",
214
- },
215
- session_id: {
216
- type: "string",
217
- description: "Project or feature label to group related tasks (e.g. 'openmind-mcp', 'auth-refactor')",
218
- },
219
- memory_id: {
220
- type: "number",
221
- description: "Optional ID of a related memory",
222
- },
223
- },
224
- required: ["title"],
225
- },
140
+ type: "text",
141
+ text: `Consolidation Complete\n\nReviewed: ${results.reviewed} memories\nBoosted: ${results.boosted} memories\nDecayed: ${results.decayed} memories\nUnchanged: ${results.unchanged} memories\n\nChanges saved to database`,
226
142
  },
143
+ ],
144
+ };
145
+ });
146
+ server.registerTool("memory_relationships", {
147
+ description: "View relationship graph for a memory",
148
+ inputSchema: {
149
+ memoryId: z.number().describe("Memory ID to explore"),
150
+ },
151
+ }, async ({ memoryId }) => {
152
+ const relationships = queries.getMemoryRelationships(db).all(memoryId, memoryId);
153
+ return {
154
+ content: [{ type: "text", text: JSON.stringify(relationships, null, 2) }],
155
+ };
156
+ });
157
+ // ---------------------------------------------------------------------------
158
+ // journal tools
159
+ // ---------------------------------------------------------------------------
160
+ server.registerTool("journal_add", {
161
+ description: "Add a daily journal entry",
162
+ inputSchema: {
163
+ date: z.string().describe("Date YYYY-MM-DD"),
164
+ content: z.string().describe("Full journal narrative"),
165
+ learned: z.string().optional().describe("What I learned (optional)"),
166
+ learnedAboutRye: z.string().optional().describe("What I learned about Rye (optional)"),
167
+ keyMoments: z.string().optional().describe("Key moments (optional)"),
168
+ mood: z.string().optional().describe("Mood/feeling (optional)"),
169
+ },
170
+ }, async ({ date, content, learned, learnedAboutRye, keyMoments, mood }) => {
171
+ const combinedLearned = [learned, learnedAboutRye].filter(Boolean).join("\n\n") || null;
172
+ queries.insertJournal(db).run(date, content, combinedLearned, keyMoments || null, mood || null);
173
+ return {
174
+ content: [{ type: "text", text: `Journal entry created for ${date}` }],
175
+ };
176
+ });
177
+ server.registerTool("journal_recent", {
178
+ description: "Get recent journal entries",
179
+ inputSchema: {
180
+ days: z.number().optional().describe("Days to look back (default: 7)"),
181
+ },
182
+ }, async ({ days = 7 }) => {
183
+ const entries = queries.getRecentJournals(db).all(`-${days} days`);
184
+ return {
185
+ content: [{ type: "text", text: JSON.stringify(entries, null, 2) }],
186
+ };
187
+ });
188
+ // ---------------------------------------------------------------------------
189
+ // task tools
190
+ // ---------------------------------------------------------------------------
191
+ server.registerTool("task_create", {
192
+ description: "Create a new task. Use session_id to group tasks by project or feature.",
193
+ inputSchema: {
194
+ title: z.string().describe("Short task title"),
195
+ description: z.string().optional().describe("Detailed description of what needs to be done"),
196
+ session_id: z.string().optional().describe("Project or feature label to group related tasks (e.g. 'openmind-mcp', 'auth-refactor')"),
197
+ memory_id: z.number().optional().describe("Optional ID of a related memory"),
198
+ },
199
+ }, async ({ title, description, session_id, memory_id }) => {
200
+ const result = queries.insertTask(db).run(title, description || null, session_id || null, memory_id || null);
201
+ const id = Number(result.lastInsertRowid);
202
+ return {
203
+ content: [
227
204
  {
228
- name: "task_update",
229
- description: "Update a task's status, title, or description",
230
- inputSchema: {
231
- type: "object",
232
- properties: {
233
- taskId: {
234
- type: "number",
235
- description: "ID of the task to update",
236
- },
237
- status: {
238
- type: "string",
239
- enum: ["pending", "in_progress", "completed", "cancelled"],
240
- description: "New status",
241
- },
242
- title: {
243
- type: "string",
244
- description: "Updated title",
245
- },
246
- description: {
247
- type: "string",
248
- description: "Updated description",
249
- },
250
- },
251
- required: ["taskId"],
252
- },
205
+ type: "text",
206
+ text: `Created task #${id}: ${title}${session_id ? `\n Session: ${session_id}` : ""}`,
253
207
  },
208
+ ],
209
+ };
210
+ });
211
+ server.registerTool("task_update", {
212
+ description: "Update a task's status, title, or description",
213
+ inputSchema: {
214
+ taskId: z.number().describe("ID of the task to update"),
215
+ status: z.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("New status"),
216
+ title: z.string().optional().describe("Updated title"),
217
+ description: z.string().optional().describe("Updated description"),
218
+ },
219
+ }, async ({ taskId, status, title, description }) => {
220
+ const existing = queries.getTaskById(db).get(taskId);
221
+ if (!existing)
222
+ throw new Error(`Task #${taskId} not found`);
223
+ queries.updateTask(db).run(status || null, title || null, description || null, taskId);
224
+ const updated = queries.getTaskById(db).get(taskId);
225
+ return {
226
+ content: [
254
227
  {
255
- name: "task_list",
256
- description: "List tasks, optionally filtered by status and/or session_id. Use at session start to resume open work.",
257
- inputSchema: {
258
- type: "object",
259
- properties: {
260
- status: {
261
- type: "string",
262
- enum: ["pending", "in_progress", "completed", "cancelled"],
263
- description: "Filter by status",
264
- },
265
- session_id: {
266
- type: "string",
267
- description: "Filter by project/feature label",
268
- },
269
- limit: {
270
- type: "number",
271
- description: "Maximum results (default: 50)",
272
- },
273
- },
274
- },
228
+ type: "text",
229
+ text: `Task #${taskId} updated\n Status: ${updated.status}\n Title: ${updated.title}`,
275
230
  },
276
231
  ],
277
232
  };
278
233
  });
279
- // Tool handlers
280
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
281
- try {
282
- const { name, arguments: args = {} } = request.params;
283
- switch (name) {
284
- case "memory_search": {
285
- const query = args.query.toLowerCase();
286
- const limit = args.limit || 10;
287
- const pattern = `%${query}%`;
288
- const results = queries.searchMemories(db).all(pattern, pattern, limit);
289
- for (const result of results) {
290
- logAccess(result.id, 'search');
291
- }
292
- return {
293
- content: [
294
- {
295
- type: "text",
296
- text: JSON.stringify(results, null, 2),
297
- },
298
- ],
299
- };
300
- }
301
- case "memory_add": {
302
- const { content, contentL1, type, importance, tags, context, core } = args;
303
- const content_l1 = contentL1 || content.slice(0, 200);
304
- const embedding = await embed(content);
305
- const id = createMemory(db, {
306
- timestamp: new Date().toISOString(),
307
- content,
308
- content_l1,
309
- type,
310
- core: core ? 1 : 0,
311
- importance,
312
- context: context || undefined,
313
- tags,
314
- embedding,
315
- });
316
- return {
317
- content: [
318
- {
319
- type: "text",
320
- text: `✓ Created memory #${id}\n Type: ${type}\n Core: ${core ? 'yes' : 'no'}\n Importance: ${'⭐'.repeat(importance)}\n Tags: ${tags.join(', ')}`,
321
- },
322
- ],
323
- };
324
- }
325
- case "memory_update_core": {
326
- const { memoryId, content, contentL1 } = args;
327
- const existing = db.prepare('SELECT id, core FROM memory WHERE id = ?').get(memoryId);
328
- if (!existing) {
329
- throw new Error(`Memory #${memoryId} not found`);
330
- }
331
- if (!existing.core) {
332
- throw new Error(`Memory #${memoryId} is not a core memory. Use memory_add with supersedes_id for regular memories.`);
333
- }
334
- const embedding = await embed(content);
335
- db.prepare(`
336
- UPDATE memory
337
- SET content = ?, content_l1 = ?, updated_at = datetime('now')
338
- WHERE id = ?
339
- `).run(content, contentL1, memoryId);
340
- // Update embedding in vec_memories virtual table
341
- db.prepare('DELETE FROM vec_memories WHERE memory_id = ?').run(BigInt(memoryId));
342
- db.prepare('INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)').run(BigInt(memoryId), Buffer.from(embedding.buffer));
343
- return {
344
- content: [
345
- {
346
- type: "text",
347
- text: `✓ Updated core memory #${memoryId}`,
348
- },
349
- ],
350
- };
351
- }
352
- case "memory_recent": {
353
- const hours = args.hours || 24;
354
- const limit = args.limit || 20;
355
- const results = queries.getRecentMemories(db).all(`-${hours} hours`, limit);
356
- for (const result of results) {
357
- logAccess(result.id, 'recent');
358
- }
359
- return {
360
- content: [
361
- {
362
- type: "text",
363
- text: JSON.stringify(results, null, 2),
364
- },
365
- ],
366
- };
367
- }
368
- case "memory_stats": {
369
- const stats = queries.getStats(db).all();
370
- const total = db.prepare('SELECT COUNT(*) as count FROM memory').get().count;
371
- const tags = db.prepare('SELECT COUNT(*) as count FROM tag').get().count;
372
- const relationships = db.prepare('SELECT COUNT(*) as count FROM relationship').get().count;
373
- const accessLogs = db.prepare('SELECT COUNT(*) as count FROM access_log').get().count;
374
- return {
375
- content: [
376
- {
377
- type: "text",
378
- text: `📊 Memory Statistics:\n\n${stats.map(s => ` ${s.type.padEnd(15)} ${s.count.toString().padStart(3)} memories (avg importance: ${s.avg_importance?.toFixed(1)})`).join('\n')}\n\n Total: ${total} memories\n Tags: ${tags}\n Relationships: ${relationships}\n Access Logs: ${accessLogs}`,
379
- },
380
- ],
381
- };
382
- }
383
- case "memory_consolidate": {
384
- const results = runConsolidation();
385
- return {
386
- content: [
387
- {
388
- type: "text",
389
- text: `🔄 Consolidation Complete\n\nReviewed: ${results.reviewed} memories\nBoosted: ${results.boosted} memories\nDecayed: ${results.decayed} memories\nUnchanged: ${results.unchanged} memories\n\nChanges saved to database`,
390
- },
391
- ],
392
- };
393
- }
394
- case "journal_add": {
395
- const { date, content, learned, learnedAboutRye, keyMoments, mood } = args;
396
- // Combine learned + learnedAboutRye into the single learned column
397
- const combinedLearned = [learned, learnedAboutRye].filter(Boolean).join('\n\n') || null;
398
- queries.insertJournal(db).run(date, content, combinedLearned, keyMoments || null, mood || null);
399
- return {
400
- content: [
401
- {
402
- type: "text",
403
- text: `✓ Journal entry created for ${date}`,
404
- },
405
- ],
406
- };
407
- }
408
- case "journal_recent": {
409
- const days = args.days || 7;
410
- const entries = queries.getRecentJournals(db).all(`-${days} days`);
411
- return {
412
- content: [
413
- {
414
- type: "text",
415
- text: JSON.stringify(entries, null, 2),
416
- },
417
- ],
418
- };
419
- }
420
- case "memory_relationships": {
421
- const memoryId = args.memoryId;
422
- const relationships = queries.getMemoryRelationships(db).all(memoryId, memoryId);
423
- return {
424
- content: [
425
- {
426
- type: "text",
427
- text: JSON.stringify(relationships, null, 2),
428
- },
429
- ],
430
- };
431
- }
432
- case "task_create": {
433
- const { title, description, session_id, memory_id } = args;
434
- const result = queries.insertTask(db).run(title, description || null, session_id || null, memory_id || null);
435
- const id = Number(result.lastInsertRowid);
436
- return {
437
- content: [
438
- {
439
- type: "text",
440
- text: `✓ Created task #${id}: ${title}${session_id ? `\n Session: ${session_id}` : ''}`,
441
- },
442
- ],
443
- };
444
- }
445
- case "task_update": {
446
- const { taskId, status, title, description } = args;
447
- const existing = queries.getTaskById(db).get(taskId);
448
- if (!existing)
449
- throw new Error(`Task #${taskId} not found`);
450
- queries.updateTask(db).run(status || null, title || null, description || null, taskId);
451
- const updated = queries.getTaskById(db).get(taskId);
452
- return {
453
- content: [
454
- {
455
- type: "text",
456
- text: `✓ Task #${taskId} updated\n Status: ${updated.status}\n Title: ${updated.title}`,
457
- },
458
- ],
459
- };
460
- }
461
- case "task_list": {
462
- const { status, session_id, limit = 50 } = args;
463
- // Build dynamic WHERE clause
464
- const conditions = [];
465
- const params = [];
466
- if (status) {
467
- conditions.push('status = ?');
468
- params.push(status);
469
- }
470
- if (session_id) {
471
- conditions.push('session_id = ?');
472
- params.push(session_id);
473
- }
474
- const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
475
- params.push(limit);
476
- const tasks = db.prepare(`
477
- SELECT * FROM task
478
- ${where}
479
- ORDER BY created_at ASC
480
- LIMIT ?
481
- `).all(...params);
482
- return {
483
- content: [
484
- {
485
- type: "text",
486
- text: JSON.stringify(tasks, null, 2),
487
- },
488
- ],
489
- };
490
- }
491
- default:
492
- throw new Error(`Unknown tool: ${name}`);
493
- }
234
+ server.registerTool("task_list", {
235
+ description: "List tasks, optionally filtered by status and/or session_id. Use at session start to resume open work.",
236
+ inputSchema: {
237
+ status: z.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Filter by status"),
238
+ session_id: z.string().optional().describe("Filter by project/feature label"),
239
+ limit: z.number().optional().describe("Maximum results (default: 50)"),
240
+ },
241
+ }, async ({ status, session_id, limit = 50 }) => {
242
+ const conditions = [];
243
+ const params = [];
244
+ if (status) {
245
+ conditions.push("status = ?");
246
+ params.push(status);
494
247
  }
495
- catch (error) {
496
- return {
497
- content: [
498
- {
499
- type: "text",
500
- text: `Error: ${error.message}`,
501
- },
502
- ],
503
- isError: true,
504
- };
248
+ if (session_id) {
249
+ conditions.push("session_id = ?");
250
+ params.push(session_id);
505
251
  }
252
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
253
+ params.push(limit);
254
+ const tasks = db.prepare(`
255
+ SELECT * FROM task
256
+ ${where}
257
+ ORDER BY created_at ASC
258
+ LIMIT ?
259
+ `).all(...params);
260
+ return {
261
+ content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }],
262
+ };
506
263
  });
264
+ // ---------------------------------------------------------------------------
507
265
  // Start server
508
- try {
266
+ // ---------------------------------------------------------------------------
267
+ async function main() {
509
268
  const transport = new StdioServerTransport();
510
269
  await server.connect(transport);
270
+ console.error("Postnesia MCP Server running on stdio");
511
271
  }
512
- catch (error) {
513
- console.error("OpenMind Memory MCP Server running on stdio");
514
- }
272
+ main().catch((error) => {
273
+ console.error("Fatal error:", error);
274
+ process.exit(1);
275
+ });
package/package.json CHANGED
@@ -1,27 +1,19 @@
1
1
  {
2
2
  "name": "@postnesia/mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "An MCP server to interact with the Postnesia database",
5
5
  "type": "module",
6
+ "private": false,
6
7
  "files": [
7
8
  "dist"
8
9
  ],
9
- "exports": {
10
- "./access": {
11
- "types": "./dist/access.d.ts",
12
- "import": "./dist/access.js"
13
- },
14
- "./importance": {
15
- "types": "./dist/importance.d.ts",
16
- "import": "./dist/importance.js"
17
- }
18
- },
19
10
  "bin": {
20
11
  "postnesia-mcp": "./dist/index.js"
21
12
  },
22
13
  "dependencies": {
23
14
  "@modelcontextprotocol/sdk": "^1.26.0",
24
- "@postnesia/db": "0.1.4"
15
+ "@postnesia/db": "^0.1.5",
16
+ "zod": "^4.3.6"
25
17
  },
26
18
  "devDependencies": {
27
19
  "@types/node": "^22.10.5",