@postnesia/mcp 0.1.14 → 0.1.16

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
@@ -3,28 +3,28 @@
3
3
  * MCP Server for Postnesia Memory System
4
4
  * Exposes memory operations as Model Context Protocol tools
5
5
  */
6
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
- import { z } from "zod";
9
- import { getDb, closeDb, createMemory } from "@postnesia/db";
10
- import queries from "@postnesia/db/queries";
11
- import { embed } from "@postnesia/db/embeddings";
12
- import { logAccess } from "@postnesia/db/access";
13
- import { runConsolidation } from "@postnesia/db/importance";
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { z } from 'zod';
9
+ import { getDb, closeDb, createMemory } from '@postnesia/db';
10
+ import queries from '@postnesia/db/queries';
11
+ import { embed } from '@postnesia/db/embeddings';
12
+ import { logAccess } from '@postnesia/db/access';
13
+ import { runConsolidation } from '@postnesia/db/importance';
14
14
  const db = getDb();
15
15
  const server = new McpServer({
16
- name: "postnesia",
17
- version: "1.0.0",
16
+ name: 'postnesia',
17
+ version: '1.0.0',
18
18
  });
19
19
  // ---------------------------------------------------------------------------
20
20
  // memory tools
21
21
  // ---------------------------------------------------------------------------
22
- server.registerTool("memory_search", {
23
- description: "Search memories by semantic similarity. Optionally filter by type.",
22
+ server.registerTool('memory_search', {
23
+ description: 'Search memories by semantic similarity. Optionally filter by type.',
24
24
  inputSchema: {
25
- query: z.string().describe("Search query"),
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"),
25
+ query: z.string().describe('Search query'),
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
28
  },
29
29
  }, async ({ query, limit = 10, type }) => {
30
30
  const [embedding] = await embed(query);
@@ -36,86 +36,86 @@ server.registerTool("memory_search", {
36
36
  ? queries.vectorSearchByType(db).all(embeddingBuffer, limit, type)
37
37
  : queries.vectorSearch(db).all(embeddingBuffer, limit);
38
38
  for (const result of results) {
39
- logAccess(result.id, "search");
39
+ logAccess(result.id, 'search');
40
40
  }
41
41
  return {
42
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
42
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
43
43
  };
44
44
  });
45
- server.registerTool("memory_search_keyword", {
46
- description: "Keyword search memories by content (use when semantic search is not appropriate).",
45
+ server.registerTool('memory_search_keyword', {
46
+ description: 'Keyword search memories by content (use when semantic search is not appropriate).',
47
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)"),
48
+ query: z.string().describe('Keyword or phrase to search for'),
49
+ limit: z.number().optional().describe('Maximum results to return (default: 10)'),
50
50
  },
51
51
  }, async ({ query, limit = 10 }) => {
52
52
  const pattern = `%${query}%`;
53
53
  const results = queries.searchMemories(db).all(pattern, pattern, limit);
54
54
  for (const result of results) {
55
- logAccess(result.id, "search");
55
+ logAccess(result.id, 'search');
56
56
  }
57
57
  return {
58
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
58
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
59
59
  };
60
60
  });
61
- server.registerTool("memory_by_context", {
62
- description: "Find memories by context string (LIKE match).",
61
+ server.registerTool('memory_by_context', {
62
+ description: 'Find memories by context string (LIKE match).',
63
63
  inputSchema: {
64
- context: z.string().describe("Context string to search for"),
65
- limit: z.number().optional().describe("Maximum results (default: 10)"),
64
+ context: z.string().describe('Context string to search for'),
65
+ limit: z.number().optional().describe('Maximum results (default: 10)'),
66
66
  },
67
67
  }, async ({ context, limit = 10 }) => {
68
68
  const results = queries.getMemoriesByContext(db).all(`%${context}%`, limit);
69
69
  for (const r of results)
70
- logAccess(r.id, "search");
70
+ logAccess(r.id, 'search');
71
71
  return {
72
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
72
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
73
73
  };
74
74
  });
75
- server.registerTool("memory_by_tag", {
76
- description: "Find memories by tag (exact match).",
75
+ server.registerTool('memory_by_tag', {
76
+ description: 'Find memories by tag (exact match).',
77
77
  inputSchema: {
78
- tag: z.string().describe("Tag to filter by"),
79
- limit: z.number().optional().describe("Maximum results (default: 10)"),
78
+ tag: z.string().describe('Tag to filter by'),
79
+ limit: z.number().optional().describe('Maximum results (default: 10)'),
80
80
  },
81
81
  }, async ({ tag, limit = 10 }) => {
82
82
  const results = queries.getMemoriesByTag(db).all(tag, limit);
83
83
  for (const r of results)
84
- logAccess(r.id, "search");
84
+ logAccess(r.id, 'search');
85
85
  return {
86
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
86
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
87
87
  };
88
88
  });
89
- server.registerTool("memory_supersede_chain", {
90
- description: "Trace the supersede chain for a memory (shows the full replacement history).",
89
+ server.registerTool('memory_supersede_chain', {
90
+ description: 'Trace the supersede chain for a memory (shows the full replacement history).',
91
91
  inputSchema: {
92
- memoryId: z.number().describe("Memory ID to trace"),
92
+ memoryId: z.number().describe('Memory ID to trace'),
93
93
  },
94
94
  }, async ({ memoryId }) => {
95
95
  const chain = queries.getSupersedeChain(db).all(memoryId);
96
96
  return {
97
- content: [{ type: "text", text: JSON.stringify(chain, null, 2) }],
97
+ content: [{ type: 'text', text: JSON.stringify(chain, null, 2) }],
98
98
  };
99
99
  });
100
- server.registerTool("memory_core", {
101
- description: "Get all core memories (foundational memories that are always loaded).",
100
+ server.registerTool('memory_core', {
101
+ description: 'Get all core memories (foundational memories that are always loaded).',
102
102
  inputSchema: {},
103
103
  }, async () => {
104
104
  const results = queries.coreMemories(db).all();
105
105
  return {
106
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
106
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
107
107
  };
108
108
  });
109
- server.registerTool("memory_add", {
110
- description: "Add a new memory to the database",
109
+ server.registerTool('memory_add', {
110
+ description: 'Add a new memory to the database',
111
111
  inputSchema: {
112
- content: z.string().describe("Memory content (full form)"),
113
- contentL1: z.string().optional().describe("Compressed L1 form (optional, will auto-generate if omitted)"),
114
- type: z.enum(["event", "decision", "lesson", "preference", "person", "technical"]).describe("Memory type"),
115
- importance: z.number().describe("Base importance 1-5"),
116
- tags: z.array(z.string()).describe("Tags for categorization"),
117
- context: z.string().optional().describe("Optional context about when/why this memory was created"),
118
- core: z.boolean().optional().describe("Mark as a core memory (always loaded, never decays, cannot be superseded)"),
112
+ content: z.string().describe('Memory content (full form)'),
113
+ contentL1: z.string().optional().describe('Compressed L1 form (optional, will auto-generate if omitted)'),
114
+ type: z.enum(['event', 'decision', 'lesson', 'preference', 'person', 'technical']).describe('Memory type'),
115
+ importance: z.number().describe('Base importance 1-5'),
116
+ tags: z.array(z.string()).describe('Tags for categorization'),
117
+ context: z.string().optional().describe('Optional context about when/why this memory was created'),
118
+ core: z.boolean().optional().describe('Mark as a core memory (always loaded, never decays, cannot be superseded)'),
119
119
  },
120
120
  }, async ({ content, contentL1, type, importance, tags, context, core }) => {
121
121
  const content_l1 = contentL1 || content.slice(0, 200);
@@ -137,21 +137,21 @@ server.registerTool("memory_add", {
137
137
  return {
138
138
  content: [
139
139
  {
140
- type: "text",
141
- text: `Created memory #${id}\n Type: ${type}\n Core: ${core ? "yes" : "no"}\n Importance: ${importance}/5\n Tags: ${tags.join(", ")}`,
140
+ type: 'text',
141
+ text: `Created memory #${id}\n Type: ${type}\n Core: ${core ? 'yes' : 'no'}\n Importance: ${importance}/5\n Tags: ${tags.join(', ')}`,
142
142
  },
143
143
  ],
144
144
  };
145
145
  });
146
- server.registerTool("memory_update_core", {
147
- description: "Update the content of an existing core memory in place (core memories must be updated, never superseded)",
146
+ server.registerTool('memory_update_core', {
147
+ description: 'Update the content of an existing core memory in place (core memories must be updated, never superseded)',
148
148
  inputSchema: {
149
- memoryId: z.number().describe("ID of the memory to update"),
150
- content: z.string().describe("New full content"),
151
- contentL1: z.string().describe("New compressed L1 summary"),
149
+ memoryId: z.number().describe('ID of the memory to update'),
150
+ content: z.string().describe('New full content'),
151
+ contentL1: z.string().describe('New compressed L1 summary'),
152
152
  },
153
153
  }, async ({ memoryId, content, contentL1 }) => {
154
- const existing = db.prepare("SELECT id, core FROM memory WHERE id = ?").get(memoryId);
154
+ const existing = db.prepare('SELECT id, core FROM memory WHERE id = ?').get(memoryId);
155
155
  if (!existing)
156
156
  throw new Error(`Memory #${memoryId} not found`);
157
157
  if (!existing.core)
@@ -161,152 +161,181 @@ server.registerTool("memory_update_core", {
161
161
  throw new ReferenceError('Unable to create query embedding');
162
162
  }
163
163
  queries.updateCoreMemory(db).run(content, contentL1, memoryId);
164
- db.prepare("DELETE FROM vec_memories WHERE memory_id = ?").run(BigInt(memoryId));
165
- db.prepare("INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)").run(BigInt(memoryId), Buffer.from(embedding.buffer));
164
+ db.prepare('DELETE FROM vec_memories WHERE memory_id = ?').run(BigInt(memoryId));
165
+ db.prepare('INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)').run(BigInt(memoryId), Buffer.from(embedding.buffer));
166
166
  return {
167
- content: [{ type: "text", text: `Updated core memory #${memoryId}` }],
167
+ content: [{ type: 'text', text: `Updated core memory #${memoryId}` }],
168
168
  };
169
169
  });
170
- server.registerTool("memory_recent", {
171
- description: "Get recent memories within time window",
170
+ server.registerTool('memory_recent', {
171
+ description: 'Get recent memories within time window',
172
172
  inputSchema: {
173
- hours: z.number().optional().describe("Hours to look back (default: 24)"),
174
- limit: z.number().optional().describe("Maximum results (default: 20)"),
173
+ hours: z.number().optional().describe('Hours to look back (default: 24)'),
174
+ limit: z.number().optional().describe('Maximum results (default: 20)'),
175
175
  },
176
176
  }, async ({ hours = 24, limit = 20 }) => {
177
177
  const results = queries.getRecentMemories(db).all(`-${hours} hours`, limit);
178
178
  for (const result of results) {
179
- logAccess(result.id, "recent");
179
+ logAccess(result.id, 'recent');
180
180
  }
181
181
  return {
182
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
182
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
183
183
  };
184
184
  });
185
- server.registerTool("memory_stats", {
186
- description: "Get memory database statistics",
185
+ server.registerTool('memory_stats', {
186
+ description: 'Get memory database statistics',
187
187
  inputSchema: {},
188
188
  }, async () => {
189
189
  const stats = queries.getStats(db).all();
190
- const total = db.prepare("SELECT COUNT(*) as count FROM memory").get().count;
191
- const tags = db.prepare("SELECT COUNT(*) as count FROM tag").get().count;
192
- const relationships = db.prepare("SELECT COUNT(*) as count FROM relationship").get().count;
193
- const accessLogs = db.prepare("SELECT COUNT(*) as count FROM access_log").get().count;
190
+ const total = db.prepare('SELECT COUNT(*) as count FROM memory').get().count;
191
+ const tags = db.prepare('SELECT COUNT(*) as count FROM tag').get().count;
192
+ const relationships = db.prepare('SELECT COUNT(*) as count FROM relationship').get().count;
193
+ const accessLogs = db.prepare('SELECT COUNT(*) as count FROM access_log').get().count;
194
194
  const rows = stats
195
195
  .map((s) => ` ${s.type.padEnd(15)} ${s.count.toString().padStart(3)} memories (avg importance: ${s.avg_importance?.toFixed(1)})`)
196
- .join("\n");
196
+ .join('\n');
197
197
  return {
198
198
  content: [
199
199
  {
200
- type: "text",
200
+ type: 'text',
201
201
  text: `Memory Statistics:\n\n${rows}\n\n Total: ${total} memories\n Tags: ${tags}\n Relationships: ${relationships}\n Access Logs: ${accessLogs}`,
202
202
  },
203
203
  ],
204
204
  };
205
205
  });
206
- server.registerTool("memory_consolidate", {
207
- description: "Run memory consolidation cycle (decay old, boost accessed)",
206
+ server.registerTool('memory_consolidate', {
207
+ description: 'Run memory consolidation cycle (decay old, boost accessed)',
208
208
  inputSchema: {},
209
209
  }, async () => {
210
210
  const results = runConsolidation();
211
211
  return {
212
212
  content: [
213
213
  {
214
- type: "text",
214
+ type: 'text',
215
215
  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`,
216
216
  },
217
217
  ],
218
218
  };
219
219
  });
220
- server.registerTool("memory_relationships", {
221
- description: "View relationship graph for a memory",
220
+ server.registerTool('memory_relationships', {
221
+ description: 'View relationship graph for a memory',
222
222
  inputSchema: {
223
- memoryId: z.number().describe("Memory ID to explore"),
223
+ memoryId: z.number().describe('Memory ID to explore'),
224
224
  },
225
225
  }, async ({ memoryId }) => {
226
226
  const relationships = queries.getMemoryRelationships(db).all(memoryId, memoryId);
227
227
  return {
228
- content: [{ type: "text", text: JSON.stringify(relationships, null, 2) }],
228
+ content: [{ type: 'text', text: JSON.stringify(relationships, null, 2) }],
229
229
  };
230
230
  });
231
- server.registerTool("memory_link", {
232
- description: "Explicitly create a typed relationship between two memories",
231
+ server.registerTool('memory_link', {
232
+ description: 'Explicitly create a typed relationship between two memories',
233
233
  inputSchema: {
234
- fromId: z.number().describe("Source memory ID"),
235
- toId: z.number().describe("Target memory ID"),
236
- type: z.enum(["related", "supersedes", "supports", "contradicts", "derives_from"])
237
- .describe("Relationship type"),
234
+ fromId: z.number().describe('Source memory ID'),
235
+ toId: z.number().describe('Target memory ID'),
236
+ type: z.enum(['related', 'supersedes', 'supports', 'contradicts', 'derives_from'])
237
+ .describe('Relationship type'),
238
238
  },
239
239
  }, async ({ fromId, toId, type }) => {
240
- const from = db.prepare("SELECT id FROM memory WHERE id = ?").get(fromId);
240
+ const from = db.prepare('SELECT id FROM memory WHERE id = ?').get(fromId);
241
241
  if (!from)
242
242
  throw new Error(`Memory #${fromId} not found`);
243
- const to = db.prepare("SELECT id FROM memory WHERE id = ?").get(toId);
243
+ const to = db.prepare('SELECT id FROM memory WHERE id = ?').get(toId);
244
244
  if (!to)
245
245
  throw new Error(`Memory #${toId} not found`);
246
246
  const existing = queries.findRelationshipBetween(db).get(fromId, toId, type);
247
247
  if (existing) {
248
- return { content: [{ type: "text", text: `Relationship already exists (id: ${existing.id})` }] };
248
+ return { content: [{ type: 'text', text: `Relationship already exists (id: ${existing.id})` }] };
249
249
  }
250
250
  const result = queries.insertRelationship(db).run(fromId, toId, type);
251
251
  const id = Number(result.lastInsertRowid);
252
252
  return {
253
- content: [{ type: "text", text: `Created relationship #${id}: #${fromId} -[${type}]-> #${toId}` }],
253
+ content: [{ type: 'text', text: `Created relationship #${id}: #${fromId} -[${type}]-> #${toId}` }],
254
254
  };
255
255
  });
256
- server.registerTool("memory_unlink", {
257
- description: "Remove a relationship between two memories by relationship ID",
256
+ server.registerTool('memory_unlink', {
257
+ description: 'Remove a relationship between two memories by relationship ID',
258
258
  inputSchema: {
259
- relationshipId: z.number().describe("Relationship ID to delete"),
259
+ relationshipId: z.number().describe('Relationship ID to delete'),
260
260
  },
261
261
  }, async ({ relationshipId }) => {
262
- const existing = db.prepare("SELECT id FROM relationship WHERE id = ?").get(relationshipId);
262
+ const existing = db.prepare('SELECT id FROM relationship WHERE id = ?').get(relationshipId);
263
263
  if (!existing)
264
264
  throw new Error(`Relationship #${relationshipId} not found`);
265
265
  queries.deleteRelationship(db).run(relationshipId);
266
266
  return {
267
- content: [{ type: "text", text: `Deleted relationship #${relationshipId}` }],
267
+ content: [{ type: 'text', text: `Deleted relationship #${relationshipId}` }],
268
+ };
269
+ });
270
+ server.registerTool('memory_supersede', {
271
+ description: 'Mark an existing memory as superseded by another — use for deduplication. Demotes the duplicate\'s importance and links it to the canonical memory. Refuses core memories.',
272
+ inputSchema: {
273
+ duplicateId: z.number().describe('ID of the duplicate memory to demote'),
274
+ canonicalId: z.number().describe('ID of the canonical memory to keep'),
275
+ },
276
+ }, async ({ duplicateId, canonicalId }) => {
277
+ if (duplicateId === canonicalId)
278
+ throw new Error('duplicate and canonical must be different memories');
279
+ const dup = db.prepare('SELECT id, core FROM memory WHERE id = ?').get(duplicateId);
280
+ if (!dup)
281
+ throw new Error(`Memory #${duplicateId} not found`);
282
+ if (dup.core)
283
+ throw new Error(`Memory #${duplicateId} is a core memory and cannot be superseded — use memory_update_core instead`);
284
+ const can = db.prepare('SELECT id FROM memory WHERE id = ?').get(canonicalId);
285
+ if (!can)
286
+ throw new Error(`Canonical memory #${canonicalId} not found`);
287
+ const alreadyLinked = db.prepare('SELECT supersedes_id FROM memory WHERE id = ? AND supersedes_id IS NOT NULL').get(duplicateId);
288
+ if (alreadyLinked)
289
+ throw new Error(`Memory #${duplicateId} already has a supersedes_id — check memory_supersede_chain first`);
290
+ db.prepare('UPDATE memory SET supersedes_id = ?, importance = MAX(1, importance - 2), updated_at = datetime(\'now\') WHERE id = ?').run(canonicalId, duplicateId);
291
+ const existing = queries.findRelationshipBetween(db).get(duplicateId, canonicalId, 'supersedes');
292
+ if (!existing) {
293
+ queries.insertRelationship(db).run(duplicateId, canonicalId, 'supersedes');
294
+ }
295
+ return {
296
+ content: [{ type: 'text', text: `Memory #${duplicateId} now superseded by #${canonicalId} (importance demoted by 2)` }],
268
297
  };
269
298
  });
270
299
  // ---------------------------------------------------------------------------
271
300
  // journal tools
272
301
  // ---------------------------------------------------------------------------
273
- server.registerTool("journal_add", {
274
- description: "Add a daily journal entry",
302
+ server.registerTool('journal_add', {
303
+ description: 'Add a daily journal entry',
275
304
  inputSchema: {
276
- date: z.string().describe("Date YYYY-MM-DD"),
277
- content: z.string().describe("Full journal narrative"),
278
- learned: z.string().optional().describe("What I learned (optional)"),
279
- keyMoments: z.string().optional().describe("Key moments (optional)"),
280
- mood: z.string().optional().describe("Mood/feeling (optional)"),
305
+ date: z.string().describe('Date YYYY-MM-DD'),
306
+ content: z.string().describe('Full journal narrative'),
307
+ learned: z.string().optional().describe('What I learned (optional)'),
308
+ keyMoments: z.string().optional().describe('Key moments (optional)'),
309
+ mood: z.string().optional().describe('Mood/feeling (optional)'),
281
310
  },
282
311
  }, async ({ date, content, learned, keyMoments, mood }) => {
283
- const combinedLearned = [learned].filter(Boolean).join("\n\n") || null;
312
+ const combinedLearned = [learned].filter(Boolean).join('\n\n') || null;
284
313
  queries.insertJournal(db).run(date, content, combinedLearned, keyMoments || null, mood || null);
285
314
  return {
286
- content: [{ type: "text", text: `Journal entry created for ${date}` }],
315
+ content: [{ type: 'text', text: `Journal entry created for ${date}` }],
287
316
  };
288
317
  });
289
- server.registerTool("journal_recent", {
290
- description: "Get recent journal entries",
318
+ server.registerTool('journal_recent', {
319
+ description: 'Get recent journal entries',
291
320
  inputSchema: {
292
- days: z.number().optional().describe("Days to look back (default: 7)"),
321
+ days: z.number().optional().describe('Days to look back (default: 7)'),
293
322
  },
294
323
  }, async ({ days = 7 }) => {
295
324
  const entries = queries.getRecentJournals(db).all(`-${days} days`);
296
325
  return {
297
- content: [{ type: "text", text: JSON.stringify(entries, null, 2) }],
326
+ content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }],
298
327
  };
299
328
  });
300
329
  // ---------------------------------------------------------------------------
301
330
  // task tools
302
331
  // ---------------------------------------------------------------------------
303
- server.registerTool("task_create", {
304
- description: "Create a new task. Use session_id to group tasks by project or feature.",
332
+ server.registerTool('task_create', {
333
+ description: 'Create a new task. Use session_id to group tasks by project or feature.',
305
334
  inputSchema: {
306
- title: z.string().describe("Short task title"),
307
- description: z.string().optional().describe("Detailed description of what needs to be done"),
335
+ title: z.string().describe('Short task title'),
336
+ description: z.string().optional().describe('Detailed description of what needs to be done'),
308
337
  session_id: z.string().optional().describe("Project or feature label to group related tasks (e.g. 'postnesia-mcp', 'auth-refactor')"),
309
- memory_id: z.number().optional().describe("Optional ID of a related memory"),
338
+ memory_id: z.number().optional().describe('Optional ID of a related memory'),
310
339
  },
311
340
  }, async ({ title, description, session_id, memory_id }) => {
312
341
  const result = queries.insertTask(db).run(title, description || null, session_id || null, memory_id || null);
@@ -314,19 +343,19 @@ server.registerTool("task_create", {
314
343
  return {
315
344
  content: [
316
345
  {
317
- type: "text",
318
- text: `Created task #${id}: ${title}${session_id ? `\n Session: ${session_id}` : ""}`,
346
+ type: 'text',
347
+ text: `Created task #${id}: ${title}${session_id ? `\n Session: ${session_id}` : ''}`,
319
348
  },
320
349
  ],
321
350
  };
322
351
  });
323
- server.registerTool("task_update", {
352
+ server.registerTool('task_update', {
324
353
  description: "Update a task's status, title, or description",
325
354
  inputSchema: {
326
- taskId: z.number().describe("ID of the task to update"),
327
- status: z.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("New status"),
328
- title: z.string().optional().describe("Updated title"),
329
- description: z.string().optional().describe("Updated description"),
355
+ taskId: z.number().describe('ID of the task to update'),
356
+ status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional().describe('New status'),
357
+ title: z.string().optional().describe('Updated title'),
358
+ description: z.string().optional().describe('Updated description'),
330
359
  },
331
360
  }, async ({ taskId, status, title, description }) => {
332
361
  const existing = queries.getTaskById(db).get(taskId);
@@ -337,31 +366,31 @@ server.registerTool("task_update", {
337
366
  return {
338
367
  content: [
339
368
  {
340
- type: "text",
369
+ type: 'text',
341
370
  text: `Task #${taskId} updated\n Status: ${updated.status}\n Title: ${updated.title}`,
342
371
  },
343
372
  ],
344
373
  };
345
374
  });
346
- server.registerTool("task_list", {
347
- description: "List tasks, optionally filtered by status and/or session_id. Use at session start to resume open work.",
375
+ server.registerTool('task_list', {
376
+ description: 'List tasks, optionally filtered by status and/or session_id. Use at session start to resume open work.',
348
377
  inputSchema: {
349
- status: z.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe("Filter by status"),
350
- session_id: z.string().optional().describe("Filter by project/feature label"),
351
- limit: z.number().optional().describe("Maximum results (default: 50)"),
378
+ status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional().describe('Filter by status'),
379
+ session_id: z.string().optional().describe('Filter by project/feature label'),
380
+ limit: z.number().optional().describe('Maximum results (default: 50)'),
352
381
  },
353
382
  }, async ({ status, session_id, limit = 50 }) => {
354
383
  const conditions = [];
355
384
  const params = [];
356
385
  if (status) {
357
- conditions.push("status = ?");
386
+ conditions.push('status = ?');
358
387
  params.push(status);
359
388
  }
360
389
  if (session_id) {
361
- conditions.push("session_id = ?");
390
+ conditions.push('session_id = ?');
362
391
  params.push(session_id);
363
392
  }
364
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
393
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
365
394
  params.push(limit);
366
395
  const tasks = db.prepare(`
367
396
  SELECT * FROM task
@@ -370,7 +399,7 @@ server.registerTool("task_list", {
370
399
  LIMIT ?
371
400
  `).all(...params);
372
401
  return {
373
- content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }],
402
+ content: [{ type: 'text', text: JSON.stringify(tasks, null, 2) }],
374
403
  };
375
404
  });
376
405
  // ---------------------------------------------------------------------------
@@ -379,12 +408,16 @@ server.registerTool("task_list", {
379
408
  async function main() {
380
409
  const transport = new StdioServerTransport();
381
410
  await server.connect(transport);
382
- console.error("Postnesia MCP Server running on stdio");
411
+ console.error('Postnesia MCP Server running on stdio');
383
412
  }
413
+ const exit = () => {
414
+ closeDb();
415
+ process.exit(0);
416
+ };
384
417
  main().catch((error) => {
385
- console.error("Fatal error:", error);
418
+ console.error('Fatal error:', error);
386
419
  closeDb();
387
420
  process.exit(1);
388
421
  });
389
- process.on('SIGINT', () => { closeDb(); process.exit(0); });
390
- process.on('SIGTERM', () => { closeDb(); process.exit(0); });
422
+ process.on('SIGINT', exit);
423
+ process.on('SIGTERM', exit);
package/dist/install.js CHANGED
@@ -24,11 +24,11 @@ const config = {
24
24
  claude: {
25
25
  mcpServers: {
26
26
  postnesia: {
27
- type: "stdio",
27
+ type: 'stdio',
28
28
  enabled: true,
29
- command: "npx",
29
+ command: 'npx',
30
30
  args: [
31
- "pn-mcp"
31
+ 'pn-mcp'
32
32
  ],
33
33
  env: {
34
34
  DATABASE_URL: process.env['DATABASE_URL'],
@@ -40,11 +40,11 @@ const config = {
40
40
  opencode: {
41
41
  mcp: {
42
42
  postnesia: {
43
- type: "local",
43
+ type: 'local',
44
44
  enabled: false,
45
45
  command: [
46
- "npx",
47
- "pn-mcp"
46
+ 'npx',
47
+ 'pn-mcp'
48
48
  ],
49
49
  environment: {
50
50
  DATABASE_URL: process.env['DATABASE_URL'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postnesia/mcp",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "An MCP server to interact with the Postnesia database",
5
5
  "type": "module",
6
6
  "private": false,
@@ -14,10 +14,10 @@
14
14
  "dependencies": {
15
15
  "@modelcontextprotocol/sdk": "^1.26.0",
16
16
  "zod": "^4.3.6",
17
- "@postnesia/db": "0.1.14"
17
+ "@postnesia/db": "0.1.16"
18
18
  },
19
19
  "devDependencies": {
20
- "@types/node": "^22.10.5",
20
+ "@types/node": "^24.12.0",
21
21
  "dotenv": "^17.3.1",
22
22
  "tsx": "^4.19.2",
23
23
  "typescript": "^5.7.3"
@@ -25,6 +25,8 @@
25
25
  "scripts": {
26
26
  "build": "pnpm clean && tsc -p tsconfig.json",
27
27
  "clean": "rm -rf ./dist",
28
- "start": "tsx src/index.ts"
28
+ "start": "tsx src/index.ts",
29
+ "lint": "pnpm eslint .",
30
+ "lint:fix": "pnpm eslint --fix ."
29
31
  }
30
32
  }