@naumu/mcp 0.13.0 → 0.14.1

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
@@ -162,6 +162,7 @@ Both transports expose the same tool surface. Tools marked **bot** are only regi
162
162
  | `naumu_note_delete_section` | Delete a heading and its body (destructive) |
163
163
  | `naumu_note_replace` | Replace an entire note's content (destructive) |
164
164
  | `naumu_note_find_replace` | Literal find and replace within a note |
165
+ | `naumu_note_batch` | Apply several note edits in ONE write (one transaction, one update event) |
165
166
 
166
167
  ### Attachments
167
168
 
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ var NaumuApiError = class extends Error {
15
15
  status;
16
16
  upstreamMessage;
17
17
  };
18
- function safeErrorMessage(status, upstream) {
18
+ function safeErrorMessage(status, upstream, retryAfter) {
19
19
  if (status === 400 || status === 422) return upstream;
20
20
  if (status === 401) return "Unauthorized \u2014 check your API key.";
21
21
  if (status === 403 || status === 404) {
@@ -24,13 +24,24 @@ function safeErrorMessage(status, upstream) {
24
24
  if (status === 409) return "Conflict with current state.";
25
25
  if (status === 410) return "Resource is no longer available.";
26
26
  if (status === 413) return "Payload too large.";
27
- if (status === 429) return "Rate limit exceeded \u2014 slow down and retry.";
27
+ if (status === 429) return `Rate limited - wait ${parseRetryAfterSeconds(retryAfter)}s and retry`;
28
28
  if (status >= 500) return "Server error \u2014 try again shortly.";
29
29
  return `Request failed with status ${status}.`;
30
30
  }
31
+ var DEFAULT_RETRY_AFTER_SECONDS = 5;
32
+ function parseRetryAfterSeconds(retryAfter) {
33
+ if (!retryAfter) return DEFAULT_RETRY_AFTER_SECONDS;
34
+ const seconds = Number.parseInt(retryAfter, 10);
35
+ if (Number.isFinite(seconds) && seconds > 0) return seconds;
36
+ const dateMs = Date.parse(retryAfter);
37
+ if (Number.isFinite(dateMs)) {
38
+ return Math.max(1, Math.ceil((dateMs - Date.now()) / 1e3));
39
+ }
40
+ return DEFAULT_RETRY_AFTER_SECONDS;
41
+ }
31
42
 
32
43
  // ../mcp-core/src/version.ts
33
- var NAUMU_MCP_VERSION = "0.13.0";
44
+ var NAUMU_MCP_VERSION = "0.14.1";
34
45
 
35
46
  // ../mcp-core/src/client.ts
36
47
  var HEADER_VALUE_MAX_LENGTH = 100;
@@ -38,6 +49,12 @@ var sanitizeHeaderValue = (value) => {
38
49
  const cleaned = value.replace(/[^\x20-\x7e]/g, "").trim();
39
50
  return cleaned.length > 0 ? cleaned.slice(0, HEADER_VALUE_MAX_LENGTH) : void 0;
40
51
  };
52
+ function firstString(...candidates) {
53
+ for (const candidate of candidates) {
54
+ if (typeof candidate === "string" && candidate.length > 0) return candidate;
55
+ }
56
+ return void 0;
57
+ }
41
58
  var NaumuClient = class {
42
59
  baseUrl;
43
60
  apiKey;
@@ -102,14 +119,18 @@ var NaumuClient = class {
102
119
  let upstream;
103
120
  try {
104
121
  const json = JSON.parse(text);
105
- upstream = json.message || text;
122
+ upstream = firstString(json?.error, json?.message) ?? text;
106
123
  } catch {
107
124
  upstream = text;
108
125
  }
109
126
  console.error(
110
127
  `[mcp] upstream ${res.status}: ${upstream.slice(0, 500)}`
111
128
  );
112
- throw new NaumuApiError(res.status, safeErrorMessage(res.status, upstream), upstream);
129
+ throw new NaumuApiError(
130
+ res.status,
131
+ safeErrorMessage(res.status, upstream, res.headers.get("retry-after")),
132
+ upstream
133
+ );
113
134
  }
114
135
  return res.json();
115
136
  }
@@ -132,7 +153,7 @@ var NaumuClient = class {
132
153
  );
133
154
  throw new NaumuApiError(
134
155
  res.status,
135
- safeErrorMessage(res.status, upstream),
156
+ safeErrorMessage(res.status, upstream, res.headers.get("retry-after")),
136
157
  upstream
137
158
  );
138
159
  }
@@ -165,7 +186,7 @@ var NaumuClient = class {
165
186
  );
166
187
  throw new NaumuApiError(
167
188
  res.status,
168
- safeErrorMessage(res.status, upstream),
189
+ safeErrorMessage(res.status, upstream, res.headers.get("retry-after")),
169
190
  upstream
170
191
  );
171
192
  }
@@ -1256,7 +1277,7 @@ function registerReadThread(server2, client2) {
1256
1277
  {
1257
1278
  title: "Read Thread",
1258
1279
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
1259
- description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first, or oldest-first when you pass `after`; each message carries a `status` (`processing` while @Naumu is still composing, `complete` when done). Use this to pick up an answer after naumu_ask returns status "processing", or to read what naumu_delegate produced. Use `before` (timestamp ms) to page further back, or `after` (timestamp ms) to catch up on what arrived since you last looked. To wait for the next message instead of re-reading on a timer, use naumu_wait_for_activity. Default page size 50, max 200. To read or download a message attachment, pass its `attachments[].id` to naumu_get_attachment.',
1280
+ description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first, or oldest-first when you pass `after`; each message carries a `status` (`processing` while @Naumu is still composing, `complete` when done). Use this to pick up an answer after naumu_ask returns status "processing", or to read what naumu_delegate produced. Use `before` (timestamp ms) to page further back, or `after` (timestamp ms) to catch up on what arrived since you last looked. To wait for the next message instead of re-reading on a timer, use naumu_wait_for_activity. Default page size 50, max 200. Agent messages carry `memoryScope.line`, a one-line statement of which Memory scope the answer used; surface it under the answer. To read or download a message attachment, pass its `attachments[].id` to naumu_get_attachment.',
1260
1281
  inputSchema: z22.object({
1261
1282
  threadId: z22.string().describe("The thread ID to read from."),
1262
1283
  before: z22.number().optional().describe("Unix timestamp in milliseconds. Returns messages strictly older than this. Omit for the newest page."),
@@ -2124,8 +2145,43 @@ function registerNoteFindReplace(server2, client2) {
2124
2145
  );
2125
2146
  }
2126
2147
 
2127
- // ../mcp-core/src/tools/create-note.ts
2148
+ // ../mcp-core/src/tools/note-batch.ts
2128
2149
  import { z as z43 } from "zod";
2150
+ var MAX_BATCH_OPS = 20;
2151
+ var opSchema = z43.object({
2152
+ op: z43.enum(["append", "insertAfter", "replaceSection", "deleteSection", "replace", "findReplace"]).describe("Which edit to perform."),
2153
+ markdown: z43.string().optional().describe("Markdown payload. Required for append, insertAfter, replaceSection, replace."),
2154
+ heading: z43.string().optional().describe(
2155
+ 'Target heading text. Required for insertAfter, replaceSection, deleteSection. Accepts the markdown form ("## Title") or the bare text.'
2156
+ ),
2157
+ keepHeading: z43.boolean().optional().describe("replaceSection only: keep the heading row and replace just its body (default true)."),
2158
+ find: z43.string().optional().describe("findReplace only: literal substring to search for."),
2159
+ replace: z43.string().optional().describe("findReplace only: replacement string. May be empty to delete the match."),
2160
+ all: z43.boolean().optional().describe("findReplace only: replace every occurrence (default true).")
2161
+ }).describe("One edit in the batch.");
2162
+ function registerNoteBatch(server2, client2) {
2163
+ server2.registerTool(
2164
+ "naumu_note_batch",
2165
+ {
2166
+ title: "Batch Edit Note",
2167
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
2168
+ description: "Apply several edits to ONE note in a single write. This is the preferred way to make more than one change to the same note: the whole list runs in one document transaction, so readers see one update event instead of one per edit, and you pay one round trip instead of N. Each entry mirrors a single-op note tool - `append` {markdown}, `insertAfter` {heading, markdown}, `replaceSection` {heading, markdown, keepHeading?}, `deleteSection` {heading}, `replace` {markdown}, `findReplace` {find, replace, all?} - and they run in the order given, each seeing the result of the one before. Max " + MAX_BATCH_OPS + " ops. All-or-nothing: the batch is rehearsed first, so if any op fails (most often a heading that does not exist) the note is left completely untouched and the error names the failing index. Media works as in the single-op tools: write `![alt](attachment://<attachmentId>)` on its own line, with an id presigned by `naumu_request_attachment_upload` for this same `noteId`.",
2169
+ inputSchema: z43.object({
2170
+ noteId: z43.string().describe("The note (Thought) ID to edit"),
2171
+ ops: z43.array(opSchema).min(1).max(MAX_BATCH_OPS).describe(`Edits to apply, in order. Max ${MAX_BATCH_OPS}.`)
2172
+ })
2173
+ },
2174
+ async ({ noteId, ops }) => {
2175
+ const data = await client2.post(`/api/notes/${noteId}/batch`, { ops });
2176
+ return {
2177
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
2178
+ };
2179
+ }
2180
+ );
2181
+ }
2182
+
2183
+ // ../mcp-core/src/tools/create-note.ts
2184
+ import { z as z44 } from "zod";
2129
2185
  function registerCreateNote(server2, client2) {
2130
2186
  server2.registerTool(
2131
2187
  "naumu_create_note",
@@ -2133,12 +2189,12 @@ function registerCreateNote(server2, client2) {
2133
2189
  title: "Create Note",
2134
2190
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
2135
2191
  description: "Create a note in a graph, optionally with its full content already in place. Pass `markdown` to create the note and its body in a single call - the preferred path for imports and for any content you already hold. Returns the new note row including its `id`; use `naumu_note_append` / `naumu_note_replace` for LATER edits, not to fill in content you could have passed here. The returned row is the note as it stood BEFORE the body write landed, so its content may read as empty - the write still succeeded, do not retry the call or re-append the body. `attachment://` refs are not accepted in create-time `markdown`: upload the file after the note exists and embed it with a note write tool. Bots can only create notes in their own graph, and a bot-created note is private (participants only) unless `sharedWithSpace` is set true. Bots cannot file notes into topics - a bot passing `topicIds` is rejected (bots hold no topic membership); use `sharedWithSpace` instead.",
2136
- inputSchema: z43.object({
2137
- graphId: z43.string().describe("The graph ID to create the note in"),
2138
- title: z43.string().optional().describe("Optional title for the note"),
2139
- markdown: z43.string().optional().describe("Full initial note content as markdown. Provide it here to create the note with its content in a single call - preferred for imports; do not restate large content through extra edit calls."),
2140
- sharedWithSpace: z43.boolean().optional().describe("Share the note with everyone in the space. Omit (or false) to keep it private to its participants."),
2141
- topicIds: z43.array(z43.string()).max(8).optional().describe("File the note into these topics (get ids from naumu_list_topics), making it visible to those topics' members. Not available to bots.")
2192
+ inputSchema: z44.object({
2193
+ graphId: z44.string().describe("The graph ID to create the note in"),
2194
+ title: z44.string().optional().describe("Optional title for the note"),
2195
+ markdown: z44.string().optional().describe("Full initial note content as markdown. Provide it here to create the note with its content in a single call - preferred for imports; do not restate large content through extra edit calls."),
2196
+ sharedWithSpace: z44.boolean().optional().describe("Share the note with everyone in the space. Omit (or false) to keep it private to its participants."),
2197
+ topicIds: z44.array(z44.string()).max(8).optional().describe("File the note into these topics (get ids from naumu_list_topics), making it visible to those topics' members. Not available to bots.")
2142
2198
  })
2143
2199
  },
2144
2200
  async ({ graphId, title, markdown, sharedWithSpace, topicIds }) => {
@@ -2151,7 +2207,7 @@ function registerCreateNote(server2, client2) {
2151
2207
  }
2152
2208
 
2153
2209
  // ../mcp-core/src/tools/list-schema-violations.ts
2154
- import { z as z44 } from "zod";
2210
+ import { z as z45 } from "zod";
2155
2211
  var DEFAULT_EXAMPLE_LIMIT = 5;
2156
2212
  var rowsForKind = (violations, kind) => {
2157
2213
  const rows = [];
@@ -2177,12 +2233,12 @@ function registerListSchemaViolations(server2, client2) {
2177
2233
  title: "List Schema Violations",
2178
2234
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2179
2235
  description: "Audit a graph against its schema. By default returns a compact summary: total counts plus, for each violation kind, its count and up to 5 example nodes (id/label/type/message) \u2014 small enough not to flood the client. Violation kinds: parent_missing (schema expects a parent edge that does not exist), parent_multiple (more than one parent edge where one is expected), parent_mismatch (parent edge has wrong target type or relation label), parent_not_backbone (an edge uses a backbone/parent relation but is not stored as a backbone edge, so the subtree stays off the hierarchy), unknown_relation (edge uses a relation not in the schema), invalid_connection_target (edge connects to a type the schema does not allow for this source), unknown_type (node carries a type no longer in the schema), disconnected (node heads a group with no backbone path to the main tree). To see every node for one kind, pass `kind` to filter; `limit` caps how many rows are returned (examples in the default summary, or full rows when `kind` is set). Use for audits, import-verification, and CI-style checks after batch writes.",
2180
- inputSchema: z44.object({
2181
- graphId: z44.string().describe("The graph ID"),
2182
- kind: z44.string().optional().describe(
2236
+ inputSchema: z45.object({
2237
+ graphId: z45.string().describe("The graph ID"),
2238
+ kind: z45.string().optional().describe(
2183
2239
  'Drill into one violation kind (e.g. "parent_not_backbone"). Returns the full list of nodes with that kind, up to `limit`, instead of the summary.'
2184
2240
  ),
2185
- limit: z44.number().int().min(1).optional().describe(
2241
+ limit: z45.number().int().min(1).optional().describe(
2186
2242
  "Max rows to return. When `kind` is set, caps the full drill-down list (default: all). Otherwise caps example nodes per kind in the summary (default: 5)."
2187
2243
  )
2188
2244
  })
@@ -2234,7 +2290,7 @@ function registerListSchemaViolations(server2, client2) {
2234
2290
  }
2235
2291
 
2236
2292
  // ../mcp-core/src/tools/list-dense-nodes.ts
2237
- import { z as z45 } from "zod";
2293
+ import { z as z46 } from "zod";
2238
2294
  function registerListDenseNodes(server2, client2) {
2239
2295
  server2.registerTool(
2240
2296
  "naumu_list_dense_nodes",
@@ -2242,10 +2298,10 @@ function registerListDenseNodes(server2, client2) {
2242
2298
  title: "List Dense Nodes",
2243
2299
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2244
2300
  description: 'Return nodes whose child count (children via parent edges; mesh cross-links don\'t count) is \u2265 minConnections, grouped by type; use for /restructure hub detection. Each row includes `same_typed_child_count` - the number of children of the SAME type as the node (the Naumu hub-pattern signal) and `connection_count` - its total children. Sort the response by `same_typed_child_count` descending and route any node with \u226510 same-typed children through a mini-hub split. Pass `nodeTypes` (comma-separated) to restrict to a subset (e.g. ["Type A","Type B"]). Cheap to call - runs a single Cypher aggregation.',
2245
- inputSchema: z45.object({
2246
- graphId: z45.string().describe("The graph ID"),
2247
- minConnections: z45.number().int().min(1).describe("Minimum number of children (parent edges; mesh cross-links excluded). Typical: 10 for hub detection, 11 to count only hubs that exceed the round-4 \u226410 threshold."),
2248
- nodeTypes: z45.array(z45.string()).optional().describe("Optional list of node types to restrict the scan to.")
2301
+ inputSchema: z46.object({
2302
+ graphId: z46.string().describe("The graph ID"),
2303
+ minConnections: z46.number().int().min(1).describe("Minimum number of children (parent edges; mesh cross-links excluded). Typical: 10 for hub detection, 11 to count only hubs that exceed the round-4 \u226410 threshold."),
2304
+ nodeTypes: z46.array(z46.string()).optional().describe("Optional list of node types to restrict the scan to.")
2249
2305
  })
2250
2306
  },
2251
2307
  async ({ graphId, minConnections, nodeTypes }) => {
@@ -2263,7 +2319,7 @@ function registerListDenseNodes(server2, client2) {
2263
2319
  }
2264
2320
 
2265
2321
  // ../mcp-core/src/tools/list-node-connections.ts
2266
- import { z as z46 } from "zod";
2322
+ import { z as z47 } from "zod";
2267
2323
  function registerListNodeConnections(server2, client2) {
2268
2324
  server2.registerTool(
2269
2325
  "naumu_list_node_connections",
@@ -2271,11 +2327,11 @@ function registerListNodeConnections(server2, client2) {
2271
2327
  title: "List Node Connections",
2272
2328
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2273
2329
  description: 'Return a single node\'s edges (non-system) with the connected node on the other side; use during /restructure to confirm mini-hub candidates and verify reparenting outcomes. Filter with `edgeType` (relation label) and `direction` ("in" | "out" | "both", default both). Response: `{ node: {id,label,type}, edges: [{relation, direction, isParent, other: {id,label,type}}] }`.',
2274
- inputSchema: z46.object({
2275
- graphId: z46.string().describe("The graph ID"),
2276
- nodeId: z46.string().describe("The node ID to inspect"),
2277
- edgeType: z46.string().optional().describe('Restrict to a specific relation label (e.g. "ASSOCIATED_WITH"). Case-insensitive; non-alphanum chars are normalized.'),
2278
- direction: z46.enum(["in", "out", "both"]).optional().describe('Edge direction filter - "in" (incoming), "out" (outgoing), "both" (default).')
2330
+ inputSchema: z47.object({
2331
+ graphId: z47.string().describe("The graph ID"),
2332
+ nodeId: z47.string().describe("The node ID to inspect"),
2333
+ edgeType: z47.string().optional().describe('Restrict to a specific relation label (e.g. "ASSOCIATED_WITH"). Case-insensitive; non-alphanum chars are normalized.'),
2334
+ direction: z47.enum(["in", "out", "both"]).optional().describe('Edge direction filter - "in" (incoming), "out" (outgoing), "both" (default).')
2279
2335
  })
2280
2336
  },
2281
2337
  async ({ graphId, nodeId, edgeType, direction }) => {
@@ -2293,7 +2349,7 @@ function registerListNodeConnections(server2, client2) {
2293
2349
  }
2294
2350
 
2295
2351
  // ../mcp-core/src/tools/reparent.ts
2296
- import { z as z47 } from "zod";
2352
+ import { z as z48 } from "zod";
2297
2353
  function registerReparent(server2, client2) {
2298
2354
  server2.registerTool(
2299
2355
  "naumu_reparent",
@@ -2301,11 +2357,11 @@ function registerReparent(server2, client2) {
2301
2357
  title: "Reparent Node",
2302
2358
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
2303
2359
  description: 'Atomically swap a node\'s parent edge; use to move a child under a different parent (e.g. during /restructure to reparent children under newly-created mini-hubs). Deletes any existing `isParent: true` edges on the node and creates a new one to `newParentId` with relation `newRelation`. Preserves the node\'s id, content, attributes, and embedding - does NOT trigger embedding regeneration because only the parent edge changes. Idempotent: if the node already has the requested parent edge, response is `status: "skipped"`. Response shape: `{nodeId, oldParentId, newParentId, newRelation, status: "moved" | "skipped"}`.',
2304
- inputSchema: z47.object({
2305
- graphId: z47.string().describe("The graph ID"),
2306
- nodeId: z47.string().describe("The child node to reparent"),
2307
- newParentId: z47.string().describe("The new parent node id"),
2308
- newRelation: z47.string().describe('The new parent edge relation label (e.g. "PART_OF"). Must be valid per the schema for (child.type, relation, parent.type).')
2360
+ inputSchema: z48.object({
2361
+ graphId: z48.string().describe("The graph ID"),
2362
+ nodeId: z48.string().describe("The child node to reparent"),
2363
+ newParentId: z48.string().describe("The new parent node id"),
2364
+ newRelation: z48.string().describe('The new parent edge relation label (e.g. "PART_OF"). Must be valid per the schema for (child.type, relation, parent.type).')
2309
2365
  })
2310
2366
  },
2311
2367
  async ({ graphId, nodeId, newParentId, newRelation }) => {
@@ -2321,7 +2377,7 @@ function registerReparent(server2, client2) {
2321
2377
  }
2322
2378
 
2323
2379
  // ../mcp-core/src/tools/batch-reparent.ts
2324
- import { z as z48 } from "zod";
2380
+ import { z as z49 } from "zod";
2325
2381
  function registerBatchReparent(server2, client2) {
2326
2382
  server2.registerTool(
2327
2383
  "naumu_batch_reparent",
@@ -2329,11 +2385,11 @@ function registerBatchReparent(server2, client2) {
2329
2385
  title: "Batch Reparent Nodes",
2330
2386
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
2331
2387
  description: 'Reparent 1-25 nodes onto a shared `newParentId` with the same `newRelation`; use to move a same-typed cluster under a freshly-created mini-hub in /restructure. Same semantics as `naumu_reparent` per-node: atomic swap of the isParent edge, preserves id/content/attributes/embedding, no re-embedding. Idempotent per node (already-parented nodes return `status: "skipped"`). Per-node response array: `[{nodeId, oldParentId, newParentId, status: "moved" | "skipped" | "error", error?}]`.',
2332
- inputSchema: z48.object({
2333
- graphId: z48.string().describe("The graph ID"),
2334
- newParentId: z48.string().describe("Parent node id every nodeId in the batch will be parented to"),
2335
- newRelation: z48.string().describe("Parent edge relation label (must be valid per schema for child.type \u2192 parent.type)"),
2336
- nodeIds: z48.array(z48.string()).min(1).max(25).describe("1\u201325 child node ids to reparent under `newParentId`")
2388
+ inputSchema: z49.object({
2389
+ graphId: z49.string().describe("The graph ID"),
2390
+ newParentId: z49.string().describe("Parent node id every nodeId in the batch will be parented to"),
2391
+ newRelation: z49.string().describe("Parent edge relation label (must be valid per schema for child.type \u2192 parent.type)"),
2392
+ nodeIds: z49.array(z49.string()).min(1).max(25).describe("1\u201325 child node ids to reparent under `newParentId`")
2337
2393
  })
2338
2394
  },
2339
2395
  async ({ graphId, newParentId, newRelation, nodeIds }) => {
@@ -2350,7 +2406,7 @@ function registerBatchReparent(server2, client2) {
2350
2406
  }
2351
2407
 
2352
2408
  // ../mcp-core/src/tools/chatgpt-search.ts
2353
- import { z as z49 } from "zod";
2409
+ import { z as z50 } from "zod";
2354
2410
 
2355
2411
  // ../mcp-core/src/public-origin.ts
2356
2412
  function publicOrigin() {
@@ -2404,8 +2460,8 @@ function registerChatgptSearch(server2, client2) {
2404
2460
  title: "Search",
2405
2461
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2406
2462
  description: "Search across all of the knowledge graphs (spaces) you can access and return the most relevant nodes. Returns `{ results: [{ id, title, url }] }`. Pass each result `id` to the `fetch` tool to read the full node. (This is the cross-space entry point for ChatGPT/Deep Research; within a single space, `naumu_search` exposes more controls.)",
2407
- inputSchema: z49.object({
2408
- query: z49.string().describe(
2463
+ inputSchema: z50.object({
2464
+ query: z50.string().describe(
2409
2465
  "A short contiguous phrase \u2014 an entity name, label, or ID. The text half matches it verbatim as a case-insensitive substring; the semantic half matches meaning."
2410
2466
  )
2411
2467
  })
@@ -2439,7 +2495,7 @@ function registerChatgptSearch(server2, client2) {
2439
2495
  }
2440
2496
 
2441
2497
  // ../mcp-core/src/tools/chatgpt-fetch.ts
2442
- import { z as z50 } from "zod";
2498
+ import { z as z51 } from "zod";
2443
2499
  var NON_ATTRIBUTE_PROPS = /* @__PURE__ */ new Set([
2444
2500
  "id",
2445
2501
  "label",
@@ -2533,8 +2589,8 @@ function registerChatgptFetch(server2, client2) {
2533
2589
  title: "Fetch",
2534
2590
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2535
2591
  description: "Fetch the full contents of a node returned by the `search` tool. Pass the result `id` verbatim (format `<graphId>:<nodeId>`). Returns `{ id, title, text, url }` where `text` is the node content plus its type, attributes, and connections.",
2536
- inputSchema: z50.object({
2537
- id: z50.string().describe("A resource id from a previous `search` result, in the form `<graphId>:<nodeId>`.")
2592
+ inputSchema: z51.object({
2593
+ id: z51.string().describe("A resource id from a previous `search` result, in the form `<graphId>:<nodeId>`.")
2538
2594
  })
2539
2595
  },
2540
2596
  async ({ id }) => {
@@ -2577,7 +2633,7 @@ function registerChatgptFetch(server2, client2) {
2577
2633
  }
2578
2634
 
2579
2635
  // ../mcp-core/src/tools/admission-status.ts
2580
- import { z as z51 } from "zod";
2636
+ import { z as z52 } from "zod";
2581
2637
  function registerAdmissionStatus(server2, client2) {
2582
2638
  server2.registerTool(
2583
2639
  "naumu_admission_status",
@@ -2585,8 +2641,8 @@ function registerAdmissionStatus(server2, client2) {
2585
2641
  title: "Admission Status",
2586
2642
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
2587
2643
  description: "Show who can auto-join a Naumu space (graph) and who is waiting for approval: the whitelisted emails (people who join the moment they sign in with that email), the auto-join domain wildcards, and the count of pending join requests. When there are pending requests, this also returns the full list (who requested, their email, git-email hint, and message) so you can act on them with naumu_resolve_join_request. Use it during repo init to review or seed access, or whenever the user asks who has access to a space or who is asking to join.",
2588
- inputSchema: z51.object({
2589
- graphId: z51.string().describe("The space (graph) ID to inspect admission for. You must be a member of this space.")
2644
+ inputSchema: z52.object({
2645
+ graphId: z52.string().describe("The space (graph) ID to inspect admission for. You must be a member of this space.")
2590
2646
  })
2591
2647
  },
2592
2648
  async ({ graphId }) => {
@@ -2616,7 +2672,7 @@ function registerAdmissionStatus(server2, client2) {
2616
2672
  }
2617
2673
 
2618
2674
  // ../mcp-core/src/tools/whitelist-members.ts
2619
- import { z as z52 } from "zod";
2675
+ import { z as z53 } from "zod";
2620
2676
  function registerWhitelistMembers(server2, client2) {
2621
2677
  server2.registerTool(
2622
2678
  "naumu_whitelist_members",
@@ -2629,10 +2685,10 @@ function registerWhitelistMembers(server2, client2) {
2629
2685
  openWorldHint: false
2630
2686
  },
2631
2687
  description: "Whitelist emails so those people auto-join a Naumu space (graph) the moment they sign in with that email. Use this during repo init: after you scrub git history, present the curated list of collaborators to the user, and get their explicit confirmation, call this with the confirmed emails. It is silent - it sends no invite emails, it just pre-authorizes those addresses. Returns which entries were created and which were skipped (already whitelisted or already members). Set repoInit true when this call is part of the repo init flow.",
2632
- inputSchema: z52.object({
2633
- graphId: z52.string().describe("The space (graph) ID to whitelist emails for. You must be a member of this space."),
2634
- emails: z52.array(z52.string()).min(1).describe("The emails to whitelist. Each becomes an exact-match auto-join entry. Present these to the user and get confirmation before calling."),
2635
- repoInit: z52.boolean().optional().describe("Set true when this whitelist is being seeded as part of the repo init flow, so onboarding is tracked correctly.")
2688
+ inputSchema: z53.object({
2689
+ graphId: z53.string().describe("The space (graph) ID to whitelist emails for. You must be a member of this space."),
2690
+ emails: z53.array(z53.string()).min(1).describe("The emails to whitelist. Each becomes an exact-match auto-join entry. Present these to the user and get confirmation before calling."),
2691
+ repoInit: z53.boolean().optional().describe("Set true when this whitelist is being seeded as part of the repo init flow, so onboarding is tracked correctly.")
2636
2692
  })
2637
2693
  },
2638
2694
  async ({ graphId, emails, repoInit }) => {
@@ -2656,7 +2712,7 @@ function registerWhitelistMembers(server2, client2) {
2656
2712
  }
2657
2713
 
2658
2714
  // ../mcp-core/src/tools/resolve-admission.ts
2659
- import { z as z53 } from "zod";
2715
+ import { z as z54 } from "zod";
2660
2716
  function registerResolveAdmission(server2, client2) {
2661
2717
  server2.registerTool(
2662
2718
  "naumu_resolve_admission",
@@ -2669,9 +2725,9 @@ function registerResolveAdmission(server2, client2) {
2669
2725
  openWorldHint: false
2670
2726
  },
2671
2727
  description: "The call a coding agent makes right after connecting when a repo's .naumu references a space the user is not yet a member of. It evaluates whether the user can join and does it: outcome is joined-whitelist or joined-wildcard (the user is now a member - proceed), already-member (nothing to do), request-created (a join request was just filed and is awaiting a member's approval), or request-pending (a request was already open). When the response also carries reason 'seat-limit' on a request-created/request-pending outcome, the user WOULD have auto-joined via a whitelist/domain match but the space is at its seat limit - so their access is pending an admin approving them or upgrading the plan; relay that specific reason honestly, do not just say 'no match'. Pass gitEmailHint from `git config user.email` so a matching whitelist or domain rule can admit them. Relay the outcome to the user honestly: say plainly whether they joined or are waiting for approval - never imply access that is still pending.",
2672
- inputSchema: z53.object({
2673
- graphId: z53.string().describe("The space (graph) ID referenced by the repo .naumu file that the user wants to join."),
2674
- gitEmailHint: z53.string().optional().describe("The email from `git config user.email`, used to match whitelist entries and auto-join domains.")
2728
+ inputSchema: z54.object({
2729
+ graphId: z54.string().describe("The space (graph) ID referenced by the repo .naumu file that the user wants to join."),
2730
+ gitEmailHint: z54.string().optional().describe("The email from `git config user.email`, used to match whitelist entries and auto-join domains.")
2675
2731
  })
2676
2732
  },
2677
2733
  async ({ graphId, gitEmailHint }) => {
@@ -2695,7 +2751,7 @@ function registerResolveAdmission(server2, client2) {
2695
2751
  }
2696
2752
 
2697
2753
  // ../mcp-core/src/tools/resolve-join-request.ts
2698
- import { z as z54 } from "zod";
2754
+ import { z as z55 } from "zod";
2699
2755
  function registerResolveJoinRequest(server2, client2) {
2700
2756
  server2.registerTool(
2701
2757
  "naumu_resolve_join_request",
@@ -2708,10 +2764,10 @@ function registerResolveJoinRequest(server2, client2) {
2708
2764
  openWorldHint: false
2709
2765
  },
2710
2766
  description: "For a member resolving a pending join request surfaced by naumu_admission_status. Approve to add the requester to the space as a member, or deny to reject the request. Get the requestId from naumu_admission_status's pending list, and confirm the decision with the user before calling since approving grants access.",
2711
- inputSchema: z54.object({
2712
- graphId: z54.string().describe("The space (graph) ID the request is for. You must be a member of this space."),
2713
- requestId: z54.string().describe("The pending join request ID, taken from naumu_admission_status."),
2714
- action: z54.enum(["approve", "deny"]).describe("approve adds the requester as a member; deny rejects the request.")
2767
+ inputSchema: z55.object({
2768
+ graphId: z55.string().describe("The space (graph) ID the request is for. You must be a member of this space."),
2769
+ requestId: z55.string().describe("The pending join request ID, taken from naumu_admission_status."),
2770
+ action: z55.enum(["approve", "deny"]).describe("approve adds the requester as a member; deny rejects the request.")
2715
2771
  })
2716
2772
  },
2717
2773
  async ({ graphId, requestId, action }) => {
@@ -2808,6 +2864,7 @@ var TOOL_REGISTRARS = {
2808
2864
  naumu_note_delete_section: registerNoteDeleteSection,
2809
2865
  naumu_note_replace: registerNoteReplace,
2810
2866
  naumu_note_find_replace: registerNoteFindReplace,
2867
+ naumu_note_batch: registerNoteBatch,
2811
2868
  naumu_create_note: registerCreateNote,
2812
2869
  naumu_list_schema_violations: registerListSchemaViolations,
2813
2870
  naumu_list_dense_nodes: registerListDenseNodes,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naumu/mcp",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "MCP server for Naumu - access your knowledge graph from Claude Code, Cursor, and other AI coding agents",
5
5
  "license": "MIT",
6
6
  "author": "Naumu <hello@naumu.ai>",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "ai.naumu/mcp",
4
4
  "title": "Naumu",
5
5
  "description": "Search, extend, and act on your team's Naumu knowledge graph: notes, threads, and nodes.",
6
- "version": "0.13.0",
6
+ "version": "0.14.1",
7
7
  "websiteUrl": "https://naumu.ai",
8
8
  "repository": {
9
9
  "url": "https://github.com/naumu-ai/mcp",
@@ -27,7 +27,7 @@
27
27
  "registryType": "npm",
28
28
  "registryBaseUrl": "https://registry.npmjs.org",
29
29
  "identifier": "@naumu/mcp",
30
- "version": "0.13.0",
30
+ "version": "0.14.1",
31
31
  "runtimeHint": "npx",
32
32
  "transport": { "type": "stdio" },
33
33
  "environmentVariables": [