@nuzo/mcp-server 0.1.0 → 0.1.2

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
@@ -12,7 +12,7 @@ diagnostics.
12
12
  Run a published version with:
13
13
 
14
14
  ```bash
15
- npx --yes @nuzo/mcp-server@<version>
15
+ npm exec --yes --package=@nuzo/mcp-server@<version> -- nuzo-mcp-server
16
16
  ```
17
17
 
18
18
  Use a fixed version in host plugins and automated configuration. Do not depend
@@ -18,6 +18,15 @@ export interface RecallHookToolInput {
18
18
  project_scope?: string;
19
19
  limit?: number;
20
20
  }
21
+ export interface SuggestCaptureToolInput {
22
+ content: string;
23
+ kind: MemoryKind;
24
+ scope: string;
25
+ tags: string[];
26
+ source: string;
27
+ confidence?: number;
28
+ reason: string;
29
+ }
21
30
  export interface ListToolInput {
22
31
  scope?: string;
23
32
  tags: string[];
@@ -25,6 +34,7 @@ export interface ListToolInput {
25
34
  }
26
35
  export interface UpdateToolInput {
27
36
  id: string;
37
+ expected_revision?: number;
28
38
  content?: string;
29
39
  kind?: MemoryKind;
30
40
  scope?: string;
@@ -36,6 +46,7 @@ export interface HistoryToolInput {
36
46
  }
37
47
  export interface ForgetToolInput {
38
48
  id: string;
49
+ expected_revision?: number;
39
50
  mode: "archive" | "delete";
40
51
  confirm: boolean;
41
52
  reason?: string;
@@ -79,6 +90,7 @@ export interface MemoryToolHandlers {
79
90
  recall(input: RecallToolInput): Promise<{
80
91
  results: Array<{
81
92
  id: string;
93
+ revision: number;
82
94
  content: string;
83
95
  kind: MemoryKind;
84
96
  scope: MemoryScope;
@@ -97,6 +109,7 @@ export interface MemoryToolHandlers {
97
109
  limit: number;
98
110
  results: Array<{
99
111
  id: string;
112
+ revision: number;
100
113
  content: string;
101
114
  kind: MemoryKind;
102
115
  scope: MemoryScope;
@@ -105,6 +118,13 @@ export interface MemoryToolHandlers {
105
118
  reason: string;
106
119
  }>;
107
120
  }>;
121
+ suggestCapture(input: SuggestCaptureToolInput): Promise<{
122
+ status: "ready" | "duplicate";
123
+ memory_writes: false;
124
+ requires_confirmation: true;
125
+ draft: CaptureSuggestionToolDraft;
126
+ duplicate: MemoryToolRecord | null;
127
+ }>;
108
128
  list(input: ListToolInput): Promise<{
109
129
  memories: MemoryToolRecord[];
110
130
  }>;
@@ -156,6 +176,7 @@ export interface MemoryToolHandlers {
156
176
  }
157
177
  export type MemoryToolRecord = {
158
178
  id: string;
179
+ revision: number;
159
180
  content: string;
160
181
  kind: MemoryKind;
161
182
  scope: MemoryScope;
@@ -175,4 +196,13 @@ export type MemoryToolEvent = {
175
196
  payload: Record<string, unknown>;
176
197
  created_at: string;
177
198
  };
199
+ export type CaptureSuggestionToolDraft = {
200
+ content: string;
201
+ kind: MemoryKind;
202
+ scope: MemoryScope;
203
+ tags: string[];
204
+ source: string;
205
+ confidence: number;
206
+ reason: string;
207
+ };
178
208
  export declare function createMemoryToolHandlers(service: MemoryService, options?: MemoryToolHandlerOptions): MemoryToolHandlers;
package/dist/handlers.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { memoryToolNames } from "./tool-contract.js";
1
2
  export function createMemoryToolHandlers(service, options = {}) {
2
3
  return {
3
4
  async remember(input) {
@@ -51,6 +52,27 @@ export function createMemoryToolHandlers(service, options = {}) {
51
52
  results: results.map(toRecallOutput),
52
53
  };
53
54
  },
55
+ async suggestCapture(input) {
56
+ const suggestInput = {
57
+ content: input.content,
58
+ kind: input.kind,
59
+ scope: input.scope,
60
+ tags: input.tags,
61
+ source: input.source,
62
+ reason: input.reason,
63
+ };
64
+ if (input.confidence !== undefined) {
65
+ suggestInput.confidence = input.confidence;
66
+ }
67
+ const result = await service.suggestCapture(suggestInput);
68
+ return {
69
+ status: result.status,
70
+ memory_writes: false,
71
+ requires_confirmation: true,
72
+ draft: toSuggestionDraftOutput(result.draft),
73
+ duplicate: result.duplicate ? toToolRecord(result.duplicate) : null,
74
+ };
75
+ },
54
76
  async list(input) {
55
77
  const listInput = {
56
78
  includeArchived: input.include_archived,
@@ -71,6 +93,9 @@ export function createMemoryToolHandlers(service, options = {}) {
71
93
  id: input.id,
72
94
  actor: "nuzo:mcp",
73
95
  };
96
+ if (input.expected_revision !== undefined) {
97
+ updateInput.expectedRevision = input.expected_revision;
98
+ }
74
99
  if (input.content !== undefined) {
75
100
  updateInput.content = input.content;
76
101
  }
@@ -111,6 +136,9 @@ export function createMemoryToolHandlers(service, options = {}) {
111
136
  confirm: input.confirm,
112
137
  actor: "nuzo:mcp",
113
138
  };
139
+ if (input.expected_revision !== undefined) {
140
+ forgetInput.expectedRevision = input.expected_revision;
141
+ }
114
142
  if (input.reason !== undefined) {
115
143
  forgetInput.reason = input.reason;
116
144
  }
@@ -222,19 +250,7 @@ export function createMemoryToolHandlers(service, options = {}) {
222
250
  archived_memories: archivedMemories,
223
251
  total_memories: totalMemories,
224
252
  },
225
- tools: [
226
- "memory.remember",
227
- "memory.recall",
228
- "memory.recall_hook",
229
- "memory.list",
230
- "memory.update",
231
- "memory.history",
232
- "memory.forget",
233
- "memory.forget_many",
234
- "memory.export",
235
- "memory.import",
236
- "memory.doctor",
237
- ],
253
+ tools: [...memoryToolNames],
238
254
  warnings,
239
255
  };
240
256
  },
@@ -276,6 +292,7 @@ function clampRecallHookLimit(limit) {
276
292
  function toRecallOutput(result) {
277
293
  return {
278
294
  id: result.memory.id,
295
+ revision: result.memory.revision,
279
296
  content: result.memory.content,
280
297
  kind: result.memory.kind,
281
298
  scope: result.memory.scope,
@@ -284,9 +301,21 @@ function toRecallOutput(result) {
284
301
  reason: result.reason,
285
302
  };
286
303
  }
304
+ function toSuggestionDraftOutput(draft) {
305
+ return {
306
+ content: draft.content,
307
+ kind: draft.kind,
308
+ scope: draft.scope,
309
+ tags: draft.tags,
310
+ source: draft.source,
311
+ confidence: draft.confidence,
312
+ reason: draft.reason,
313
+ };
314
+ }
287
315
  function toToolRecord(memory) {
288
316
  return {
289
317
  id: memory.id,
318
+ revision: memory.revision,
290
319
  content: memory.content,
291
320
  kind: memory.kind,
292
321
  scope: memory.scope,
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { type MemoryService } from "@nuzo/memory-core";
3
+ import { type MemoryService, type MemoryScope } from "@nuzo/memory-core";
4
4
  import type { MemoryDoctorDiagnostics } from "./handlers.js";
5
5
  export interface NuzoMcpServerOptions {
6
6
  storePath?: string;
7
+ authorizedScopes?: readonly MemoryScope[];
7
8
  doctorDiagnostics?: MemoryDoctorDiagnostics;
8
9
  }
9
10
  export interface NuzoMcpServerRuntime {
package/dist/index.js CHANGED
@@ -6,22 +6,28 @@ import { fileURLToPath } from "node:url";
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 { createMemoryService, DefaultPolicyEngine, RandomIdGenerator, RegexSecretScanner, SQLiteMemoryDatabase, SystemClock, memoryScopePattern, memoryTagPattern, schemaVersion, } from "@nuzo/memory-core";
9
+ import { createMemoryService, DefaultPolicyEngine, RandomIdGenerator, RegexSecretScanner, SQLiteMemoryDatabase, SystemClock, memoryLimits, memoryScopePattern, memoryTagPattern, schemaVersion, } from "@nuzo/memory-core";
10
10
  import { createMemoryToolHandlers } from "./handlers.js";
11
11
  const defaultStorePath = resolve(homedir(), ".nuzo", "memory", "memories.sqlite");
12
- const scopeSchema = z.string().regex(memoryScopePattern);
12
+ const scopeSchema = z.string().max(memoryLimits.scopeLength).regex(memoryScopePattern);
13
13
  const tagSchema = z.string().regex(memoryTagPattern);
14
+ const memoryIdSchema = z.string().min(1).max(memoryLimits.identifierLength);
15
+ const exportDateSchema = z.string().max(memoryLimits.dateLength);
14
16
  export function createNuzoMcpServer(options = {}) {
15
17
  return createNuzoMcpServerRuntime(options).server;
16
18
  }
17
19
  export function createNuzoMcpServerRuntime(options = {}) {
18
20
  const storePath = options.storePath ?? defaultStorePath;
19
21
  const database = openDatabase(storePath);
20
- const service = createService(database);
22
+ const serviceOptions = {};
23
+ if (options.authorizedScopes !== undefined) {
24
+ serviceOptions.authorizedScopes = options.authorizedScopes;
25
+ }
26
+ const service = createService(database, serviceOptions);
21
27
  let closed = false;
22
28
  const server = new McpServer({
23
29
  name: "nuzo",
24
- version: "0.1.0",
30
+ version: "0.1.2",
25
31
  });
26
32
  registerMemoryTools(server, service, {
27
33
  storePath,
@@ -54,11 +60,11 @@ export function registerMemoryTools(server, service, options = {}) {
54
60
  server.registerTool("memory.remember", {
55
61
  description: "Store a local Nuzo memory.",
56
62
  inputSchema: {
57
- content: z.string().min(1),
63
+ content: z.string().min(1).max(memoryLimits.contentLength),
58
64
  kind: z.enum(["preference", "project_decision", "fact", "instruction", "note"]),
59
65
  scope: scopeSchema.default("user:default"),
60
- tags: z.array(tagSchema).default([]),
61
- source: z.string().min(1).default("nuzo:mcp"),
66
+ tags: z.array(tagSchema).max(memoryLimits.tags).default([]),
67
+ source: z.string().min(1).max(memoryLimits.sourceLength).default("nuzo:mcp"),
62
68
  confidence: z.number().min(0).max(1).optional(),
63
69
  },
64
70
  }, async (input) => {
@@ -77,7 +83,7 @@ export function registerMemoryTools(server, service, options = {}) {
77
83
  server.registerTool("memory.recall", {
78
84
  description: "Recall relevant local Nuzo memories.",
79
85
  inputSchema: {
80
- query: z.string().min(1),
86
+ query: z.string().min(1).max(memoryLimits.queryLength),
81
87
  scope: scopeSchema.default("user:default"),
82
88
  limit: z.number().int().min(1).max(50).default(8),
83
89
  include_global: z.boolean().default(false),
@@ -88,7 +94,7 @@ export function registerMemoryTools(server, service, options = {}) {
88
94
  server.registerTool("memory.recall_hook", {
89
95
  description: "Prototype read-only recall entrypoint for host lifecycle hooks. It never captures or creates memories.",
90
96
  inputSchema: {
91
- task_context: z.string().min(1),
97
+ task_context: z.string().min(1).max(8000),
92
98
  project_scope: scopeSchema.optional(),
93
99
  limit: z.number().int().min(1).max(8).default(5),
94
100
  },
@@ -102,11 +108,36 @@ export function registerMemoryTools(server, service, options = {}) {
102
108
  }
103
109
  return jsonToolResult(await handlers.recallHook(recallHookInput));
104
110
  });
111
+ server.registerTool("memory.suggest_capture", {
112
+ description: "Validate a proposed capture draft without creating memory. Confirmed writes still use memory.remember.",
113
+ inputSchema: {
114
+ content: z.string().min(1).max(memoryLimits.contentLength),
115
+ kind: z.enum(["preference", "project_decision", "fact", "instruction", "note"]),
116
+ scope: scopeSchema.default("user:default"),
117
+ tags: z.array(tagSchema).max(memoryLimits.tags).default([]),
118
+ source: z.string().min(1).max(memoryLimits.sourceLength).default("nuzo:capture-suggestion"),
119
+ confidence: z.number().min(0).max(1).optional(),
120
+ reason: z.string().min(1).max(memoryLimits.reasonLength),
121
+ },
122
+ }, async (input) => {
123
+ const suggestInput = {
124
+ content: input.content,
125
+ kind: input.kind,
126
+ scope: input.scope,
127
+ tags: input.tags,
128
+ source: input.source,
129
+ reason: input.reason,
130
+ };
131
+ if (input.confidence !== undefined) {
132
+ suggestInput.confidence = input.confidence;
133
+ }
134
+ return jsonToolResult(await handlers.suggestCapture(suggestInput));
135
+ });
105
136
  server.registerTool("memory.list", {
106
137
  description: "List local Nuzo memories.",
107
138
  inputSchema: {
108
139
  scope: scopeSchema.optional(),
109
- tags: z.array(tagSchema).default([]),
140
+ tags: z.array(tagSchema).max(memoryLimits.tags).default([]),
110
141
  include_archived: z.boolean().default(false),
111
142
  },
112
143
  }, async (input) => {
@@ -122,17 +153,21 @@ export function registerMemoryTools(server, service, options = {}) {
122
153
  server.registerTool("memory.update", {
123
154
  description: "Update a local Nuzo memory.",
124
155
  inputSchema: {
125
- id: z.string().min(1),
126
- content: z.string().optional(),
156
+ id: memoryIdSchema,
157
+ expected_revision: z.number().int().min(1).optional(),
158
+ content: z.string().max(memoryLimits.contentLength).optional(),
127
159
  kind: z.enum(["preference", "project_decision", "fact", "instruction", "note"]).optional(),
128
160
  scope: scopeSchema.optional(),
129
- tags: z.array(tagSchema).optional(),
161
+ tags: z.array(tagSchema).max(memoryLimits.tags).optional(),
130
162
  confidence: z.number().min(0).max(1).optional(),
131
163
  },
132
164
  }, async (input) => {
133
165
  const updateInput = {
134
166
  id: input.id,
135
167
  };
168
+ if (input.expected_revision !== undefined) {
169
+ updateInput.expected_revision = input.expected_revision;
170
+ }
136
171
  if (input.content !== undefined) {
137
172
  updateInput.content = input.content;
138
173
  }
@@ -153,7 +188,7 @@ export function registerMemoryTools(server, service, options = {}) {
153
188
  server.registerTool("memory.history", {
154
189
  description: "List audit events for one Nuzo memory ID.",
155
190
  inputSchema: {
156
- id: z.string().min(1),
191
+ id: memoryIdSchema,
157
192
  },
158
193
  }, async (input) => {
159
194
  const historyInput = {
@@ -164,10 +199,11 @@ export function registerMemoryTools(server, service, options = {}) {
164
199
  server.registerTool("memory.forget", {
165
200
  description: "Archive or delete a local Nuzo memory.",
166
201
  inputSchema: {
167
- id: z.string().min(1),
202
+ id: memoryIdSchema,
203
+ expected_revision: z.number().int().min(1).optional(),
168
204
  mode: z.enum(["archive", "delete"]).default("archive"),
169
205
  confirm: z.boolean().default(false),
170
- reason: z.string().optional(),
206
+ reason: z.string().max(memoryLimits.reasonLength).optional(),
171
207
  },
172
208
  }, async (input) => {
173
209
  const forgetInput = {
@@ -175,6 +211,9 @@ export function registerMemoryTools(server, service, options = {}) {
175
211
  mode: input.mode,
176
212
  confirm: input.confirm,
177
213
  };
214
+ if (input.expected_revision !== undefined) {
215
+ forgetInput.expected_revision = input.expected_revision;
216
+ }
178
217
  if (input.reason !== undefined) {
179
218
  forgetInput.reason = input.reason;
180
219
  }
@@ -184,12 +223,12 @@ export function registerMemoryTools(server, service, options = {}) {
184
223
  description: "Preview or apply a filtered bulk archive/delete operation.",
185
224
  inputSchema: {
186
225
  scope: scopeSchema.optional(),
187
- tags: z.array(tagSchema).default([]),
226
+ tags: z.array(tagSchema).max(memoryLimits.tags).default([]),
188
227
  all: z.boolean().default(false),
189
228
  mode: z.enum(["archive", "delete"]).default("archive"),
190
229
  confirm: z.boolean().default(false),
191
230
  dry_run: z.boolean().default(true),
192
- reason: z.string().optional(),
231
+ reason: z.string().max(memoryLimits.reasonLength).optional(),
193
232
  },
194
233
  }, async (input) => {
195
234
  const forgetInput = {
@@ -211,7 +250,7 @@ export function registerMemoryTools(server, service, options = {}) {
211
250
  description: "Export local Nuzo memories as a versioned JSON document.",
212
251
  inputSchema: {
213
252
  scope: scopeSchema.optional(),
214
- tags: z.array(tagSchema).default([]),
253
+ tags: z.array(tagSchema).max(memoryLimits.tags).default([]),
215
254
  include_archived: z.boolean().default(false),
216
255
  },
217
256
  }, async (input) => {
@@ -230,19 +269,19 @@ export function registerMemoryTools(server, service, options = {}) {
230
269
  document: z.object({
231
270
  format: z.literal("nuzo-memory-export"),
232
271
  version: z.literal(1),
233
- exported_at: z.string(),
272
+ exported_at: exportDateSchema,
234
273
  memories: z.array(z.object({
235
274
  scope: scopeSchema,
236
275
  kind: z.enum(["preference", "project_decision", "fact", "instruction", "note"]),
237
- content: z.string(),
238
- tags: z.array(tagSchema),
239
- source: z.string().min(1),
276
+ content: z.string().max(memoryLimits.contentLength),
277
+ tags: z.array(tagSchema).max(memoryLimits.tags),
278
+ source: z.string().min(1).max(memoryLimits.sourceLength),
240
279
  confidence: z.number().min(0).max(1),
241
- created_at: z.string(),
242
- updated_at: z.string(),
243
- last_used_at: z.string().nullable(),
244
- archived_at: z.string().nullable(),
245
- })),
280
+ created_at: exportDateSchema,
281
+ updated_at: exportDateSchema,
282
+ last_used_at: exportDateSchema.nullable(),
283
+ archived_at: exportDateSchema.nullable(),
284
+ })).max(memoryLimits.importItems),
246
285
  }),
247
286
  scope: scopeSchema.optional(),
248
287
  dry_run: z.boolean().default(false),
@@ -297,7 +336,7 @@ function isMainModule() {
297
336
  }
298
337
  }
299
338
  function openDatabase(storePath) {
300
- mkdirSync(dirname(storePath), { recursive: true });
339
+ mkdirSync(dirname(storePath), { recursive: true, mode: 0o700 });
301
340
  return new SQLiteMemoryDatabase({ path: storePath });
302
341
  }
303
342
  function isStoreWritable(storePath) {
@@ -310,17 +349,24 @@ function isStoreWritable(storePath) {
310
349
  return false;
311
350
  }
312
351
  }
313
- function createService(database) {
352
+ function createService(database, options = {}) {
314
353
  return createMemoryService({
315
354
  store: database,
316
355
  searchIndex: database,
317
356
  auditLog: database,
318
357
  clock: new SystemClock(),
319
358
  ids: new RandomIdGenerator(),
320
- policy: new DefaultPolicyEngine(new RegexSecretScanner()),
359
+ policy: new DefaultPolicyEngine(new RegexSecretScanner(), toPolicyOptions(options)),
321
360
  transactions: database,
322
361
  });
323
362
  }
363
+ function toPolicyOptions(options) {
364
+ const policyOptions = {};
365
+ if (options.authorizedScopes !== undefined) {
366
+ policyOptions.allowedScopes = options.authorizedScopes;
367
+ }
368
+ return policyOptions;
369
+ }
324
370
  function jsonToolResult(value) {
325
371
  return {
326
372
  content: [
@@ -0,0 +1,3 @@
1
+ export declare const memoryToolNames: readonly ["memory.remember", "memory.recall", "memory.recall_hook", "memory.suggest_capture", "memory.list", "memory.update", "memory.history", "memory.forget", "memory.forget_many", "memory.export", "memory.import", "memory.doctor"];
2
+ export declare const sortedMemoryToolNames: ("memory.remember" | "memory.recall" | "memory.recall_hook" | "memory.suggest_capture" | "memory.list" | "memory.update" | "memory.history" | "memory.forget" | "memory.forget_many" | "memory.export" | "memory.import" | "memory.doctor")[];
3
+ export type MemoryToolName = typeof memoryToolNames[number];
@@ -0,0 +1,15 @@
1
+ export const memoryToolNames = [
2
+ "memory.remember",
3
+ "memory.recall",
4
+ "memory.recall_hook",
5
+ "memory.suggest_capture",
6
+ "memory.list",
7
+ "memory.update",
8
+ "memory.history",
9
+ "memory.forget",
10
+ "memory.forget_many",
11
+ "memory.export",
12
+ "memory.import",
13
+ "memory.doctor",
14
+ ];
15
+ export const sortedMemoryToolNames = [...memoryToolNames].sort();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuzo/mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for Nuzo.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -33,7 +33,7 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "dependencies": {
36
- "@nuzo/memory-core": "0.1.0",
36
+ "@nuzo/memory-core": "0.1.2",
37
37
  "@modelcontextprotocol/sdk": "^1.29.0",
38
38
  "zod": "^4.1.13"
39
39
  },