@naumu/mcp 0.14.2 → 0.16.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.14.2";
44
+ var NAUMU_MCP_VERSION = "0.16.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.")
@@ -416,7 +469,7 @@ var NodeTypeSchema = z5.object({
416
469
  description: z5.string().optional().describe('Short one-sentence description of what this type represents AND how it differs from semantically similar types (e.g. "Type A - distinct from Type B, which is broader"). Strongly encouraged on every type. Future agents rely on this when classifying a new node into one of several similar-sounding types.')
417
470
  });
418
471
  var SchemaDefinitionSchema = z5.object({
419
- description: z5.string().optional().describe("Schema-level description / domain summary."),
472
+ description: z5.string().optional().describe("Schema-level description / domain summary. Omit to keep the current space-wide description; send an empty string to clear it. Max 4,000 chars."),
420
473
  nodes: z5.array(NodeTypeSchema).describe("All node types in the schema.")
421
474
  });
422
475
  function registerUpdateSchema(server2, client2) {
@@ -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);
@@ -631,6 +690,8 @@ function registerAddAttribute(server2, client2) {
631
690
 
632
691
  // ../mcp-core/src/tools/update-schema-description.ts
633
692
  import { z as z9 } from "zod";
693
+ var SCHEMA_DESCRIPTION_MAX_LENGTH = 4e3;
694
+ var TARGET_DESCRIPTION_MAX_LENGTH = 300;
634
695
  function errorResult(error) {
635
696
  return {
636
697
  content: [{ type: "text", text: JSON.stringify({ error }, null, 2) }]
@@ -648,7 +709,10 @@ function registerUpdateSchemaDescription(server2, client2) {
648
709
  nodeType: z9.string().optional().describe("Existing node type whose description (or whose attribute/value description) is being rewritten. Omit to rewrite the space-wide schema description instead."),
649
710
  attribute: z9.string().optional().describe("Attribute name on that type. Omit to target the node type itself. Required when `value` is set."),
650
711
  value: z9.string().optional().describe("Label of one value of `attribute`. When set, the description is written on that value instead of the attribute."),
651
- description: z9.string().trim().min(1).max(300).describe("The new description (1-300 chars). One or two sentences, contrastive against sibling types/attributes/values so an agent can decide between them.")
712
+ description: z9.string().trim().min(1).max(SCHEMA_DESCRIPTION_MAX_LENGTH).describe("The new description. For a type, attribute, or value: 1-300 chars, one or two sentences, contrastive against sibling types/attributes/values so an agent can decide between them. For the space-wide schema description (nodeType omitted): up to 4,000 chars of guidance that holds across every type.")
713
+ }).refine((data) => data.nodeType === void 0 || data.description.length <= TARGET_DESCRIPTION_MAX_LENGTH, {
714
+ path: ["description"],
715
+ message: "per-type, attribute and value descriptions are limited to 300 characters; the space-wide description to 4,000"
652
716
  })
653
717
  },
654
718
  async ({ graphId, nodeType, attribute, value, description }) => {
@@ -950,7 +1014,7 @@ Use naumu_get_schema to check valid attributes and values for this node type.`
950
1014
  }
951
1015
 
952
1016
  // ../mcp-core/src/tools/add-node.ts
953
- var RESERVED_KEYS = /* @__PURE__ */ new Set(["id", "label", "type", "content"]);
1017
+ var RESERVED_KEYS = /* @__PURE__ */ new Set(["id", "label", "type", "content", "source"]);
954
1018
  function normalizeNodeAttributes(schema, nodeType, attributes) {
955
1019
  const attrMap = /* @__PURE__ */ new Map();
956
1020
  const nodeDef = schema.nodes.find((n) => n.type === nodeType);
@@ -1232,23 +1296,27 @@ function registerDelegate(server2, client2) {
1232
1296
  idempotentHint: false,
1233
1297
  openWorldHint: true
1234
1298
  },
1235
- 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).',
1236
1300
  inputSchema: z20.object({
1237
1301
  graphId: z20.string().describe("The space (graph) id to act in."),
1238
1302
  task: z20.string().describe("What you want @Naumu to do, add, or record."),
1239
1303
  threadId: z20.string().optional().describe("Continue an existing conversation; omit to start a new one."),
1240
1304
  topicIds: z20.array(z20.string()).max(8).optional().describe(
1241
- "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."
1242
1309
  )
1243
1310
  })
1244
1311
  },
1245
- async ({ graphId, task, threadId, topicIds }) => {
1312
+ async ({ graphId, task, threadId, topicIds, private: isPrivate }) => {
1246
1313
  try {
1247
1314
  let resolvedThreadId = threadId;
1248
1315
  if (!resolvedThreadId) {
1316
+ const hasTopics = Boolean(topicIds && topicIds.length > 0);
1249
1317
  const thread = await client2.post("/api/threads", {
1250
1318
  graphId,
1251
- ...topicIds && topicIds.length > 0 ? { topicIds } : {}
1319
+ ...hasTopics ? { topicIds } : { sharedWithSpace: !isPrivate }
1252
1320
  });
1253
1321
  resolvedThreadId = thread.id;
1254
1322
  }
@@ -1380,7 +1448,7 @@ function registerReadThread(server2, client2) {
1380
1448
  {
1381
1449
  title: "Read Thread",
1382
1450
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
1383
- 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.',
1384
1452
  inputSchema: z23.object({
1385
1453
  threadId: z23.string().describe("The thread ID to read from."),
1386
1454
  before: z23.number().optional().describe("Unix timestamp in milliseconds. Returns messages strictly older than this. Omit for the newest page."),
@@ -1915,7 +1983,7 @@ function registerGetAttachment(server2, client2) {
1915
1983
  {
1916
1984
  title: "Get Attachment",
1917
1985
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
1918
- 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.",
1986
+ 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.",
1919
1987
  inputSchema: z33.object({
1920
1988
  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.")
1921
1989
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naumu/mcp",
3
- "version": "0.14.2",
3
+ "version": "0.16.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.14.2",
6
+ "version": "0.16.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.14.2",
30
+ "version": "0.16.0",
31
31
  "runtimeHint": "npx",
32
32
  "transport": { "type": "stdio" },
33
33
  "environmentVariables": [