@naumu/mcp 0.15.0 → 0.17.0

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
@@ -41,7 +41,7 @@ function parseRetryAfterSeconds(retryAfter) {
41
41
  }
42
42
 
43
43
  // ../mcp-core/src/version.ts
44
- var NAUMU_MCP_VERSION = "0.15.0";
44
+ var NAUMU_MCP_VERSION = "0.17.0";
45
45
 
46
46
  // ../mcp-core/src/client.ts
47
47
  var HEADER_VALUE_MAX_LENGTH = 100;
@@ -385,6 +385,54 @@ function registerGetSchema(server2, client2) {
385
385
 
386
386
  // ../mcp-core/src/tools/update-schema.ts
387
387
  import { z as z5 } from "zod";
388
+
389
+ // ../mcp-core/src/tools/reserved-names.ts
390
+ var RESERVED_ATTRIBUTE_NAMES = /* @__PURE__ */ new Set([
391
+ // RESERVED_ATTRIBUTE_KEYS
392
+ "id",
393
+ "label",
394
+ "type",
395
+ "content",
396
+ "embedding",
397
+ "visibility",
398
+ "source",
399
+ "graphid",
400
+ // CASED_SYSTEM_NODE_KEYS (lowercased)
401
+ "sortkey",
402
+ "createdat",
403
+ "updatedat",
404
+ "createdby",
405
+ "createdbyuserid",
406
+ "editableby",
407
+ "lastmodifiedat",
408
+ "lastmodifiedby",
409
+ "lastactivityat",
410
+ "heatscore",
411
+ "heatobserved",
412
+ "heatexpected",
413
+ "heatsurprise",
414
+ "heatcomputedat",
415
+ "heatconvsignal",
416
+ "heatconvcomputedat",
417
+ "__indexcolor",
418
+ // Canvas / force-layout scratch props
419
+ "x",
420
+ "y",
421
+ "vx",
422
+ "vy",
423
+ "fx",
424
+ "fy",
425
+ "index"
426
+ ]);
427
+ function isReservedAttributeName(name) {
428
+ return RESERVED_ATTRIBUTE_NAMES.has(name.toLowerCase());
429
+ }
430
+ function reservedAttributeNameError(name, where) {
431
+ const scope = where ? ` (type "${where}")` : "";
432
+ return `Attribute name "${name}" is reserved by Naumu (system property)${scope}. Choose a different name.`;
433
+ }
434
+
435
+ // ../mcp-core/src/tools/update-schema.ts
388
436
  var ConnectionSchema = z5.object({
389
437
  relation: z5.string().describe("UPPER_SNAKE_CASE relation name (e.g. WORKS_AT, BUILT_BY, BELONGS_TO)"),
390
438
  target_node: z5.string().optional().describe("Target type name. Omit when polymorphic=true."),
@@ -396,7 +444,12 @@ var AttributeValueSchema = z5.object({
396
444
  description: z5.string().optional().describe('Short note distinguishing this value from sibling values (e.g. "closed-won - signed and revenue committed"). Encouraged when the label alone is ambiguous.')
397
445
  });
398
446
  var AttributeSchema = z5.object({
399
- name: z5.string().describe('Attribute key (e.g. "stage", "status", "category")'),
447
+ // Reserved system property names (`source`, `type`, `createdAt`, `x`, ...)
448
+ // are rejected here so the agent gets a field-level error instead of a
449
+ // blanket 400 from the backend. See ./reserved-names.ts.
450
+ name: z5.string().describe('Attribute key (e.g. "stage", "status", "category")').refine((n) => !isReservedAttributeName(n), {
451
+ error: (iss) => reservedAttributeNameError(String(iss.input))
452
+ }),
400
453
  type: z5.enum(["select", "multiselect", "string", "number", "date"]).describe('Attribute type (required). select/multiselect define a closed set of categories with values; string/number/date hold free-form per-node data with values []. Calendar dates use "date", not select values.'),
401
454
  values: z5.array(AttributeValueSchema).describe("Allowed enum values for select/multiselect; pass [] for string/number/date."),
402
455
  description: z5.string().optional().describe("Short note explaining what this attribute captures and how it differs from similarly-named attributes elsewhere in the schema. Strongly encouraged.")
@@ -594,6 +647,12 @@ function registerAddAttribute(server2, client2) {
594
647
  })
595
648
  },
596
649
  async ({ graphId, node_type, name, type, values, description }) => {
650
+ if (isReservedAttributeName(name)) {
651
+ return {
652
+ content: [{ type: "text", text: JSON.stringify({ error: reservedAttributeNameError(name, node_type) }, null, 2) }],
653
+ isError: true
654
+ };
655
+ }
597
656
  const current = await client2.get(`/api/graphs/${graphId}/schema`);
598
657
  const schema = current.definition ? JSON.parse(current.definition) : { nodes: [] };
599
658
  const node = schema.nodes.find((n) => n.type === node_type);
@@ -955,7 +1014,7 @@ Use naumu_get_schema to check valid attributes and values for this node type.`
955
1014
  }
956
1015
 
957
1016
  // ../mcp-core/src/tools/add-node.ts
958
- var RESERVED_KEYS = /* @__PURE__ */ new Set(["id", "label", "type", "content"]);
1017
+ var RESERVED_KEYS = /* @__PURE__ */ new Set(["id", "label", "type", "content", "source"]);
959
1018
  function normalizeNodeAttributes(schema, nodeType, attributes) {
960
1019
  const attrMap = /* @__PURE__ */ new Map();
961
1020
  const nodeDef = schema.nodes.find((n) => n.type === nodeType);
@@ -1237,23 +1296,27 @@ function registerDelegate(server2, client2) {
1237
1296
  idempotentHint: false,
1238
1297
  openWorldHint: true
1239
1298
  },
1240
- description: 'Hand @Naumu a task to carry out asynchronously: add knowledge, make graph-aware changes, or record a status update (e.g. "mark task X done", "log this deployment"). Returns immediately with a threadId; @Naumu works in the background and may take seconds to minutes. For status reports you can fire and forget. To read what @Naumu did, poll naumu_read_thread with the returned threadId. When you instead need an answer synchronously, use naumu_ask. File the new thread into topics by passing `topicIds` (see naumu_list_topics): omitting them keeps the default private thread, providing them makes it visible to those topics\' members from birth. This always invokes @Naumu, even in threads where auto-response is paused, so the task text must never contain a literal "@Naumu" mention (plain text never renders or triggers a mention).',
1299
+ description: 'Hand @Naumu a task to carry out asynchronously: add knowledge, make graph-aware changes, or record a status update (e.g. "mark task X done", "log this deployment"). Returns immediately with a threadId; @Naumu works in the background and may take seconds to minutes. For status reports you can fire and forget. To read what @Naumu did, poll naumu_read_thread with the returned threadId. When you instead need an answer synchronously, use naumu_ask. A new thread is visible to the whole space by default (it lands in the space\'s #misc feed), so delegated work reads as a shared work log. File it into specific topics instead by passing `topicIds` (see naumu_list_topics), or pass `private: true` to keep it between you and @Naumu. This always invokes @Naumu, even in threads where auto-response is paused, so the task text must never contain a literal "@Naumu" mention (plain text never renders or triggers a mention).',
1241
1300
  inputSchema: z20.object({
1242
1301
  graphId: z20.string().describe("The space (graph) id to act in."),
1243
1302
  task: z20.string().describe("What you want @Naumu to do, add, or record."),
1244
1303
  threadId: z20.string().optional().describe("Continue an existing conversation; omit to start a new one."),
1245
1304
  topicIds: z20.array(z20.string()).max(8).optional().describe(
1246
- "Topic ids (from naumu_list_topics) to file a NEW thread into, making it visible to those topics' members. Only used at creation; never adds topics to an existing thread, so it is ignored when threadId is provided. Omit to keep the default private thread."
1305
+ "Topic ids (from naumu_list_topics) to file a NEW thread into, making it visible to those topics' members. Only used at creation; never adds topics to an existing thread, so it is ignored when threadId is provided. Omit to share the thread with the whole space instead."
1306
+ ),
1307
+ private: z20.boolean().optional().describe(
1308
+ "Keep a NEW thread between you and @Naumu instead of sharing it with the space. Default false. Only used at creation; ignored when threadId or topicIds are provided."
1247
1309
  )
1248
1310
  })
1249
1311
  },
1250
- async ({ graphId, task, threadId, topicIds }) => {
1312
+ async ({ graphId, task, threadId, topicIds, private: isPrivate }) => {
1251
1313
  try {
1252
1314
  let resolvedThreadId = threadId;
1253
1315
  if (!resolvedThreadId) {
1316
+ const hasTopics = Boolean(topicIds && topicIds.length > 0);
1254
1317
  const thread = await client2.post("/api/threads", {
1255
1318
  graphId,
1256
- ...topicIds && topicIds.length > 0 ? { topicIds } : {}
1319
+ ...hasTopics ? { topicIds } : { sharedWithSpace: !isPrivate }
1257
1320
  });
1258
1321
  resolvedThreadId = thread.id;
1259
1322
  }
@@ -1385,7 +1448,7 @@ function registerReadThread(server2, client2) {
1385
1448
  {
1386
1449
  title: "Read Thread",
1387
1450
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
1388
- 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.',
1451
+ 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. Audio attachments are transcribed by Naumu at post time: read `attachments[].transcription` (plus `transcriptionSummary` and `transcriptionStatus`) straight from the message instead of downloading and transcribing the file. To read or download any other attachment, pass its `attachments[].id` to naumu_get_attachment.',
1389
1452
  inputSchema: z23.object({
1390
1453
  threadId: z23.string().describe("The thread ID to read from."),
1391
1454
  before: z23.number().optional().describe("Unix timestamp in milliseconds. Returns messages strictly older than this. Omit for the newest page."),
@@ -1643,7 +1706,7 @@ function registerCreateTopic(server2, client2) {
1643
1706
  {
1644
1707
  title: "Create Topic",
1645
1708
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
1646
- description: 'Create a new topic (filing destination) in a space; use when the topic a thread or note should be filed into does not exist yet. Check `naumu_list_topics` first so you reuse an existing topic instead of creating a near-duplicate. Returns the created topic `{id, name, color, visibilityMode, archived, openToWeb, webParticipation, createdAt, createdBy, memberIds, isMember}` - pass the returned `id` to the `topicIds` param of naumu_delegate, naumu_ask or naumu_create_note to file work into it. Name rules (validated before the call): lowercase letters, numbers and "-" only (channel-slug style, e.g. "core-team"), at most 50 characters, and never one of the reserved names all, everyone, naumu, here, misc, hidden, space, shared-with-you. Names are unique per space, case-insensitively. Requires admin rights on the space: editors and bot identities always get a 403, so do not attempt this on behalf of a bot. `visibilityMode` defaults to "open"; the caller is always added as a member, and `openToWeb` cannot be combined with a "closed" topic. Resolve `graphId` via naumu_list_graphs first.',
1709
+ description: 'Create a new topic (filing destination) in a space; use when the topic a thread or note should be filed into does not exist yet. Check `naumu_list_topics` first so you reuse an existing topic instead of creating a near-duplicate. Returns the created topic `{id, name, color, visibilityMode, archived, openToWeb, webParticipation, createdAt, createdBy, memberIds, isMember}` - pass the returned `id` to the `topicIds` param of naumu_delegate, naumu_ask or naumu_create_note to file work into it. Name rules (validated before the call): lowercase letters, numbers and "-" only (channel-slug style, e.g. "core-team"), at most 50 characters, and never one of the reserved names all, everyone, naumu, here, misc, hidden, space, shared-with-you. Names are unique per space, case-insensitively. Requires admin rights on the space: editors and bot identities always get a 403, so do not attempt this on behalf of a bot. `visibilityMode` defaults to "open"; the caller is always added as a member, and `openToWeb` cannot be combined with a "closed" topic. Pass `notifyOn: "people"` for topics that will mostly hold automated posts - work logs, alerts, imports, scheduled digests - so members are not notified on every API/MCP or agent message; a person writing in a thread there, or tagging someone, still notifies. Resolve `graphId` via naumu_list_graphs first.',
1647
1710
  inputSchema: z28.object({
1648
1711
  graphId: z28.string().describe("The space (graph) id to create the topic in."),
1649
1712
  name: z28.string().min(1).describe(
@@ -1655,12 +1718,15 @@ function registerCreateTopic(server2, client2) {
1655
1718
  ),
1656
1719
  openToWeb: z28.boolean().optional().describe('Expose the topic publicly on the web. Invalid together with visibilityMode "closed".'),
1657
1720
  webParticipation: z28.enum(["participate", "view-only"]).optional().describe('What public web visitors may do when openToWeb is true. Defaults to "view-only".'),
1721
+ notifyOn: z28.enum(["everyone", "people"]).optional().describe(
1722
+ `Who the topic notifies. "everyone" (the default) notifies members on every message. "people" keeps automated posts silent: a thread with no human-typed message that is filed only in people-only topics never notifies, badges or enters anyone's Focus until a person writes in it or someone is tagged. Use "people" for work-log, alert, import and digest topics.`
1723
+ ),
1658
1724
  memberIds: z28.array(z28.string()).optional().describe(
1659
1725
  "User ids to add as topic members (get them from naumu_list_members). Ids that are not current space members are silently dropped. The caller is always added regardless."
1660
1726
  )
1661
1727
  })
1662
1728
  },
1663
- async ({ graphId, name, color, visibilityMode, openToWeb, webParticipation, memberIds }) => {
1729
+ async ({ graphId, name, color, visibilityMode, openToWeb, webParticipation, notifyOn, memberIds }) => {
1664
1730
  const nameError = validateTopicName(name);
1665
1731
  if (nameError) {
1666
1732
  return {
@@ -1691,6 +1757,7 @@ function registerCreateTopic(server2, client2) {
1691
1757
  visibilityMode,
1692
1758
  openToWeb,
1693
1759
  webParticipation,
1760
+ notifyOn,
1694
1761
  memberIds
1695
1762
  });
1696
1763
  return {
@@ -1920,7 +1987,7 @@ function registerGetAttachment(server2, client2) {
1920
1987
  {
1921
1988
  title: "Get Attachment",
1922
1989
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
1923
- description: "Read a chat attachment. Pass the `attachmentId` from a message's `attachments[].id` in naumu_read_thread (its `url` field works too - the id is extracted from it), and this resolves it into a short-lived download URL for the actual bytes.\n\nReturns a JSON text block with `{ attachmentId, downloadUrl, expiresInSeconds, note }`. Use `downloadUrl` EXACTLY as given:\n\u2022 GET it with no Authorization header - the URL itself is the auth, and adding one makes S3 answer 403.\n\u2022 It expires about 15 minutes after this call. Fetch it now; re-call this tool for a fresh URL rather than holding one.\n\u2022 Do not log it, quote it back to the user, or store it - it is a bearer capability for its whole lifetime.\n\nWhen the attachment is an image (or a video or PDF that has a generated poster), a downsized preview is also returned inline as an image block, so a visual attachment can often be understood without fetching anything. The preview is a thumbnail, not the original - fetch `downloadUrl` when you need full resolution or the exact file.\n\nAccess is checked the same way it is for a person: you only resolve attachments in threads you can already read.",
1990
+ description: "Read a chat attachment. Pass the `attachmentId` from a message's `attachments[].id` in naumu_read_thread (its `url` field works too - the id is extracted from it), and this resolves it into a short-lived download URL for the actual bytes.\n\nReturns a JSON text block with `{ attachmentId, downloadUrl, expiresInSeconds, note }`. Use `downloadUrl` EXACTLY as given:\n\u2022 GET it with no Authorization header - the URL itself is the auth, and adding one makes S3 answer 403.\n\u2022 It expires about 15 minutes after this call. Fetch it now; re-call this tool for a fresh URL rather than holding one.\n\u2022 Do not log it, quote it back to the user, or store it - it is a bearer capability for its whole lifetime.\n\nWhen the attachment is an image (or a video or PDF that has a generated poster), a downsized preview is also returned inline as an image block, so a visual attachment can often be understood without fetching anything. The preview is a thumbnail, not the original - fetch `downloadUrl` when you need full resolution or the exact file.\n\nAudio attachments do not need this tool for their contents: Naumu transcribes them at post time and naumu_read_thread returns the text as `attachments[].transcription` on the message - use that instead of downloading and transcribing the file.\n\nAccess is checked the same way it is for a person: you only resolve attachments in threads you can already read.",
1924
1991
  inputSchema: z33.object({
1925
1992
  attachmentId: z33.string().min(1).describe("Attachment id, from `attachments[].id` on a message returned by naumu_read_thread. A full or relative download URL is also accepted - the id is extracted from its last path segment.")
1926
1993
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naumu/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
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.15.0",
6
+ "version": "0.17.0",
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.15.0",
30
+ "version": "0.17.0",
31
31
  "runtimeHint": "npx",
32
32
  "transport": { "type": "stdio" },
33
33
  "environmentVariables": [