@naumu/mcp 0.3.1 → 0.4.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.
Files changed (115) hide show
  1. package/README.md +7 -0
  2. package/dist/client.d.ts +4 -0
  3. package/dist/client.js +25 -5
  4. package/dist/client.js.map +1 -1
  5. package/dist/errors.d.ts +32 -1
  6. package/dist/errors.js +44 -1
  7. package/dist/errors.js.map +1 -1
  8. package/dist/index.js +23 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/tools/add-attribute.d.ts +3 -0
  11. package/dist/tools/add-attribute.js +43 -0
  12. package/dist/tools/add-attribute.js.map +1 -0
  13. package/dist/tools/add-connection.d.ts +3 -0
  14. package/dist/tools/add-connection.js +46 -0
  15. package/dist/tools/add-connection.js.map +1 -0
  16. package/dist/tools/add-edge.js +21 -13
  17. package/dist/tools/add-edge.js.map +1 -1
  18. package/dist/tools/add-node-type.d.ts +3 -0
  19. package/dist/tools/add-node-type.js +54 -0
  20. package/dist/tools/add-node-type.js.map +1 -0
  21. package/dist/tools/add-node.js +24 -13
  22. package/dist/tools/add-node.js.map +1 -1
  23. package/dist/tools/add-reaction.d.ts +3 -0
  24. package/dist/tools/add-reaction.js +34 -0
  25. package/dist/tools/add-reaction.js.map +1 -0
  26. package/dist/tools/ask-naumu.d.ts +3 -0
  27. package/dist/tools/ask-naumu.js +44 -0
  28. package/dist/tools/ask-naumu.js.map +1 -0
  29. package/dist/tools/canvas-add-element.d.ts +3 -0
  30. package/dist/tools/canvas-add-element.js +46 -0
  31. package/dist/tools/canvas-add-element.js.map +1 -0
  32. package/dist/tools/canvas-remove-element.d.ts +3 -0
  33. package/dist/tools/canvas-remove-element.js +17 -0
  34. package/dist/tools/canvas-remove-element.js.map +1 -0
  35. package/dist/tools/canvas-update-element.d.ts +3 -0
  36. package/dist/tools/canvas-update-element.js +20 -0
  37. package/dist/tools/canvas-update-element.js.map +1 -0
  38. package/dist/tools/create-canvas.d.ts +3 -0
  39. package/dist/tools/create-canvas.js +17 -0
  40. package/dist/tools/create-canvas.js.map +1 -0
  41. package/dist/tools/create-note.d.ts +3 -0
  42. package/dist/tools/create-note.js +17 -0
  43. package/dist/tools/create-note.js.map +1 -0
  44. package/dist/tools/create-thread.d.ts +3 -0
  45. package/dist/tools/create-thread.js +63 -0
  46. package/dist/tools/create-thread.js.map +1 -0
  47. package/dist/tools/get-canvas-elements.d.ts +3 -0
  48. package/dist/tools/get-canvas-elements.js +16 -0
  49. package/dist/tools/get-canvas-elements.js.map +1 -0
  50. package/dist/tools/get-canvas-image.d.ts +3 -0
  51. package/dist/tools/get-canvas-image.js +77 -0
  52. package/dist/tools/get-canvas-image.js.map +1 -0
  53. package/dist/tools/get-thread.d.ts +3 -0
  54. package/dist/tools/get-thread.js +25 -0
  55. package/dist/tools/get-thread.js.map +1 -0
  56. package/dist/tools/index.d.ts +10 -0
  57. package/dist/tools/index.js +118 -16
  58. package/dist/tools/index.js.map +1 -1
  59. package/dist/tools/list-canvases.d.ts +3 -0
  60. package/dist/tools/list-canvases.js +16 -0
  61. package/dist/tools/list-canvases.js.map +1 -0
  62. package/dist/tools/list-schema-violations.d.ts +3 -0
  63. package/dist/tools/list-schema-violations.js +16 -0
  64. package/dist/tools/list-schema-violations.js.map +1 -0
  65. package/dist/tools/list-threads.d.ts +3 -0
  66. package/dist/tools/list-threads.js +43 -0
  67. package/dist/tools/list-threads.js.map +1 -0
  68. package/dist/tools/naumu-typing.d.ts +3 -0
  69. package/dist/tools/naumu-typing.js +30 -0
  70. package/dist/tools/naumu-typing.js.map +1 -0
  71. package/dist/tools/note-append.d.ts +3 -0
  72. package/dist/tools/note-append.js +17 -0
  73. package/dist/tools/note-append.js.map +1 -0
  74. package/dist/tools/note-delete-section.d.ts +3 -0
  75. package/dist/tools/note-delete-section.js +22 -0
  76. package/dist/tools/note-delete-section.js.map +1 -0
  77. package/dist/tools/note-find-replace.d.ts +3 -0
  78. package/dist/tools/note-find-replace.js +26 -0
  79. package/dist/tools/note-find-replace.js.map +1 -0
  80. package/dist/tools/note-insert.d.ts +3 -0
  81. package/dist/tools/note-insert.js +24 -0
  82. package/dist/tools/note-insert.js.map +1 -0
  83. package/dist/tools/note-read.d.ts +3 -0
  84. package/dist/tools/note-read.js +16 -0
  85. package/dist/tools/note-read.js.map +1 -0
  86. package/dist/tools/note-replace-section.d.ts +3 -0
  87. package/dist/tools/note-replace-section.js +26 -0
  88. package/dist/tools/note-replace-section.js.map +1 -0
  89. package/dist/tools/note-replace.d.ts +3 -0
  90. package/dist/tools/note-replace.js +17 -0
  91. package/dist/tools/note-replace.js.map +1 -0
  92. package/dist/tools/post-message.d.ts +3 -0
  93. package/dist/tools/post-message.js +45 -0
  94. package/dist/tools/post-message.js.map +1 -0
  95. package/dist/tools/react-to-message.d.ts +3 -0
  96. package/dist/tools/react-to-message.js +34 -0
  97. package/dist/tools/react-to-message.js.map +1 -0
  98. package/dist/tools/read-thread.d.ts +3 -0
  99. package/dist/tools/read-thread.js +43 -0
  100. package/dist/tools/read-thread.js.map +1 -0
  101. package/dist/tools/remove-edge.js +14 -11
  102. package/dist/tools/remove-edge.js.map +1 -1
  103. package/dist/tools/remove-reaction.d.ts +3 -0
  104. package/dist/tools/remove-reaction.js +34 -0
  105. package/dist/tools/remove-reaction.js.map +1 -0
  106. package/dist/tools/request-attachment-upload.d.ts +3 -0
  107. package/dist/tools/request-attachment-upload.js +59 -0
  108. package/dist/tools/request-attachment-upload.js.map +1 -0
  109. package/dist/tools/update-schema.d.ts +3 -0
  110. package/dist/tools/update-schema.js +48 -0
  111. package/dist/tools/update-schema.js.map +1 -0
  112. package/dist/tools/whoami.d.ts +3 -0
  113. package/dist/tools/whoami.js +23 -0
  114. package/dist/tools/whoami.js.map +1 -0
  115. package/package.json +1 -1
package/README.md CHANGED
@@ -53,6 +53,13 @@ Add to your Cursor MCP settings:
53
53
  | `naumu_remove_edge` | Delete a relationship |
54
54
  | `naumu_ask` | Ask the Naumu AI agent a question about your graph |
55
55
  | `naumu_get_ai_thread` | Retrieve messages from a previous AI conversation |
56
+ | `naumu_whoami` | (Bot keys) Self-introspection — id, graphId, name, instructions, allowed tools |
57
+ | `naumu_list_threads` | (Bot keys) List threads in your graph with `isParticipant` flag |
58
+ | `naumu_get_thread` | (Bot keys) Fetch a thread plus its human + bot participant rosters |
59
+ | `naumu_create_thread` | (Bot keys) Start a new conversation, attach participants, post initial message |
60
+ | `naumu_request_attachment_upload` | (Bot keys) Get a presigned S3 URL to upload a file; pair with `naumu_post_message`'s `attachmentIds` |
61
+ | `naumu_add_reaction` | (Bot keys) Add an emoji reaction to a message in a thread you participate in (idempotent) |
62
+ | `naumu_remove_reaction` | (Bot keys) Remove your own reaction from a message |
56
63
 
57
64
  ## Environment variables
58
65
 
package/dist/client.d.ts CHANGED
@@ -5,6 +5,10 @@ export declare class NaumuClient {
5
5
  private headers;
6
6
  private handleResponse;
7
7
  get(path: string): Promise<unknown>;
8
+ getBinary(path: string): Promise<{
9
+ buffer: Uint8Array;
10
+ contentType: string;
11
+ }>;
8
12
  post(path: string, body?: unknown): Promise<unknown>;
9
13
  patch(path: string, body: unknown): Promise<unknown>;
10
14
  del(path: string, body?: unknown): Promise<unknown>;
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NaumuApiError } from './errors.js';
1
+ import { NaumuApiError, safeErrorMessage } from './errors.js';
2
2
  export class NaumuClient {
3
3
  baseUrl;
4
4
  apiKey;
@@ -15,15 +15,19 @@ export class NaumuClient {
15
15
  async handleResponse(res) {
16
16
  if (!res.ok) {
17
17
  const text = await res.text().catch(() => 'Unknown error');
18
- let message;
18
+ let upstream;
19
19
  try {
20
20
  const json = JSON.parse(text);
21
- message = json.message || text;
21
+ upstream = json.message || text;
22
22
  }
23
23
  catch {
24
- message = text;
24
+ upstream = text;
25
25
  }
26
- throw new NaumuApiError(res.status, message);
26
+ // Log the raw upstream message server-side for debugging, but throw
27
+ // a safe-mapped one so 403/404 differentials don't leak through to
28
+ // the model.
29
+ console.error(`[mcp] upstream ${res.status}: ${upstream.slice(0, 500)}`);
30
+ throw new NaumuApiError(res.status, safeErrorMessage(res.status, upstream), upstream);
27
31
  }
28
32
  return res.json();
29
33
  }
@@ -34,6 +38,22 @@ export class NaumuClient {
34
38
  });
35
39
  return this.handleResponse(res);
36
40
  }
41
+ async getBinary(path) {
42
+ const res = await fetch(`${this.baseUrl}${path}`, {
43
+ method: 'GET',
44
+ headers: { Authorization: `Bearer ${this.apiKey}` },
45
+ });
46
+ if (!res.ok) {
47
+ const upstream = await res.text().catch(() => 'Unknown error');
48
+ console.error(`[mcp] upstream binary ${res.status}: ${upstream.slice(0, 500)}`);
49
+ throw new NaumuApiError(res.status, safeErrorMessage(res.status, upstream), upstream);
50
+ }
51
+ const arrayBuffer = await res.arrayBuffer();
52
+ return {
53
+ buffer: new Uint8Array(arrayBuffer),
54
+ contentType: res.headers.get('content-type') ?? 'application/octet-stream',
55
+ };
56
+ }
37
57
  async post(path, body) {
38
58
  const res = await fetch(`${this.baseUrl}${path}`, {
39
59
  method: 'POST',
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,35 @@
1
1
  export declare class NaumuApiError extends Error {
2
2
  readonly status: number;
3
- constructor(status: number, message: string);
3
+ /**
4
+ * Raw upstream message — captured for server-side logging only. Never
5
+ * pipe this to model output: it can carry 403/404 differential signal
6
+ * (e.g. "Thread not found" vs "Role 'X' cannot perform 'thread:post'")
7
+ * which lets a malicious caller enumerate resources they don't have
8
+ * access to. Tools should surface `error.message` (the safe-mapped
9
+ * version) instead.
10
+ */
11
+ readonly upstreamMessage?: string | undefined;
12
+ constructor(status: number, message: string,
13
+ /**
14
+ * Raw upstream message — captured for server-side logging only. Never
15
+ * pipe this to model output: it can carry 403/404 differential signal
16
+ * (e.g. "Thread not found" vs "Role 'X' cannot perform 'thread:post'")
17
+ * which lets a malicious caller enumerate resources they don't have
18
+ * access to. Tools should surface `error.message` (the safe-mapped
19
+ * version) instead.
20
+ */
21
+ upstreamMessage?: string | undefined);
4
22
  }
23
+ /**
24
+ * Map an HTTP status + raw upstream message to a model-safe error string.
25
+ *
26
+ * The 403/404 collapse is the security mitigation: bots can otherwise
27
+ * enumerate threads / attachments / nodes they don't own by reading the
28
+ * differential between "not found" and "forbidden" responses.
29
+ *
30
+ * 400/422 messages pass through verbatim because they describe what the
31
+ * caller did wrong (malformed input, validation failure) — the bot needs
32
+ * that detail to fix its own request, and the messages are derived from
33
+ * its own input rather than server-side state.
34
+ */
35
+ export declare function safeErrorMessage(status: number, upstream: string): string;
package/dist/errors.js CHANGED
@@ -1,9 +1,52 @@
1
1
  export class NaumuApiError extends Error {
2
2
  status;
3
- constructor(status, message) {
3
+ upstreamMessage;
4
+ constructor(status, message,
5
+ /**
6
+ * Raw upstream message — captured for server-side logging only. Never
7
+ * pipe this to model output: it can carry 403/404 differential signal
8
+ * (e.g. "Thread not found" vs "Role 'X' cannot perform 'thread:post'")
9
+ * which lets a malicious caller enumerate resources they don't have
10
+ * access to. Tools should surface `error.message` (the safe-mapped
11
+ * version) instead.
12
+ */
13
+ upstreamMessage) {
4
14
  super(message);
5
15
  this.status = status;
16
+ this.upstreamMessage = upstreamMessage;
6
17
  this.name = 'NaumuApiError';
7
18
  }
8
19
  }
20
+ /**
21
+ * Map an HTTP status + raw upstream message to a model-safe error string.
22
+ *
23
+ * The 403/404 collapse is the security mitigation: bots can otherwise
24
+ * enumerate threads / attachments / nodes they don't own by reading the
25
+ * differential between "not found" and "forbidden" responses.
26
+ *
27
+ * 400/422 messages pass through verbatim because they describe what the
28
+ * caller did wrong (malformed input, validation failure) — the bot needs
29
+ * that detail to fix its own request, and the messages are derived from
30
+ * its own input rather than server-side state.
31
+ */
32
+ export function safeErrorMessage(status, upstream) {
33
+ if (status === 400 || status === 422)
34
+ return upstream;
35
+ if (status === 401)
36
+ return 'Unauthorized — check your API key.';
37
+ if (status === 403 || status === 404) {
38
+ return 'Resource not found, or you do not have access to it.';
39
+ }
40
+ if (status === 409)
41
+ return 'Conflict with current state.';
42
+ if (status === 410)
43
+ return 'Resource is no longer available.';
44
+ if (status === 413)
45
+ return 'Payload too large.';
46
+ if (status === 429)
47
+ return 'Rate limit exceeded — slow down and retry.';
48
+ if (status >= 500)
49
+ return 'Server error — try again shortly.';
50
+ return `Request failed with status ${status}.`;
51
+ }
9
52
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAEtB;IADjB,YACiB,MAAc,EAC9B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,WAAM,GAAN,MAAM,CAAQ;QAI9B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC7B,CAAC;CACD"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAEtB;IAUA;IAXjB,YACiB,MAAc,EAC9B,OAAe;IACf;;;;;;;OAOG;IACa,eAAwB;QAExC,KAAK,CAAC,OAAO,CAAC,CAAC;QAZC,WAAM,GAAN,MAAM,CAAQ;QAUd,oBAAe,GAAf,eAAe,CAAS;QAGxC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC7B,CAAC;CACD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,QAAgB;IAChE,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC;IACtD,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,oCAAoC,CAAC;IAChE,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACtC,OAAO,sDAAsD,CAAC;IAC/D,CAAC;IACD,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,8BAA8B,CAAC;IAC1D,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,kCAAkC,CAAC;IAC9D,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,oBAAoB,CAAC;IAChD,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,4CAA4C,CAAC;IACxE,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,mCAAmC,CAAC;IAC9D,OAAO,8BAA8B,MAAM,GAAG,CAAC;AAChD,CAAC"}
package/dist/index.js CHANGED
@@ -2,8 +2,9 @@
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { NaumuClient } from './client.js';
5
- import { registerAllTools } from './tools/index.js';
5
+ import { registerAllTools, registerNamedTools } from './tools/index.js';
6
6
  const DEFAULT_API_URL = 'https://naumu.ai';
7
+ const BOT_KEY_PREFIX = 'nmu_bot_';
7
8
  const apiUrl = (process.env.NAUMU_API_URL || DEFAULT_API_URL).replace(/\/api\/?$/, '');
8
9
  const apiKey = process.env.NAUMU_API_KEY;
9
10
  if (!apiKey) {
@@ -38,11 +39,30 @@ Localhost URLs (http://localhost:3000/spaces/{slug}/...) follow the same shape
38
39
  const client = new NaumuClient(apiUrl, apiKey);
39
40
  const server = new McpServer({
40
41
  name: 'naumu',
41
- version: '0.3.1',
42
+ version: '0.4.0',
42
43
  }, {
43
44
  instructions: NAUMU_INSTRUCTIONS,
44
45
  });
45
- registerAllTools(server, client);
46
+ // Bot keys (`nmu_bot_*`) get a filtered tool surface — the backend computes
47
+ // the allowed set from the Identity's `allowedTools`. User keys keep the full
48
+ // surface (no per-Identity scoping applies).
49
+ const isBotKey = apiKey.startsWith(BOT_KEY_PREFIX);
50
+ if (isBotKey) {
51
+ try {
52
+ const manifest = (await client.get('/api/identities/me/mcp-tool-manifest'));
53
+ registerNamedTools(server, client, manifest.mcpTools);
54
+ console.error(`[mcp] connected to ${apiUrl} as bot identity ${manifest.identityId} (${manifest.intelligenceKind}); ${manifest.mcpTools.length} tool(s) registered`);
55
+ }
56
+ catch (err) {
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ console.error(`[mcp] failed to fetch tool manifest: ${message}`);
59
+ process.exit(1);
60
+ }
61
+ }
62
+ else {
63
+ registerAllTools(server, client);
64
+ console.error(`[mcp] connected to ${apiUrl} as user key (${process.env.NAUMU_IDENTITY_ID ?? 'no identity id set'})`);
65
+ }
46
66
  const transport = new StdioServerTransport();
47
67
  await server.connect(transport);
48
68
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAE3C,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AACvF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAEzC,IAAI,CAAC,MAAM,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;wOAwB6M,CAAC;AAEzO,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC3B;IACC,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,OAAO;CAChB,EACD;IACC,YAAY,EAAE,kBAAkB;CAChC,CACD,CAAC;AAEF,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExE,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AACvF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAEzC,IAAI,CAAC,MAAM,EAAE,CAAC;IACb,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;wOAwB6M,CAAC;AAEzO,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC3B;IACC,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,OAAO;CAChB,EACD;IACC,YAAY,EAAE,kBAAkB;CAChC,CACD,CAAC;AAEF,4EAA4E;AAC5E,8EAA8E;AAC9E,6CAA6C;AAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAEnD,IAAI,QAAQ,EAAE,CAAC;IACd,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAKzE,CAAC;QACF,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACtD,OAAO,CAAC,KAAK,CACZ,sBAAsB,MAAM,oBAAoB,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,gBAAgB,MAAM,QAAQ,CAAC,QAAQ,CAAC,MAAM,qBAAqB,CACpJ,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;AACF,CAAC;KAAM,CAAC;IACP,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,KAAK,CACZ,sBAAsB,MAAM,iBAAiB,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,oBAAoB,GAAG,CACrG,CAAC;AACH,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NaumuClient } from '../client.js';
3
+ export declare function registerAddAttribute(server: McpServer, client: NaumuClient): void;
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ export function registerAddAttribute(server, client) {
3
+ server.registerTool('naumu_add_attribute', {
4
+ title: 'Add Attribute to Node Type',
5
+ description: 'Add or extend an attribute on an existing node type. If the attribute name doesn\'t exist, it is created. If it exists and is a select/multiselect, new values are merged in (existing values kept). Use type "select"/"multiselect" with values; "string"/"number"/"date" for free-form fields (pass values: []).',
6
+ inputSchema: z.object({
7
+ graphId: z.string(),
8
+ node_type: z.string().describe('Existing node type to add the attribute to.'),
9
+ name: z.string().describe('Attribute key (e.g. "stage", "status").'),
10
+ type: z.enum(['select', 'multiselect', 'string', 'number', 'date']).default('select'),
11
+ values: z.array(z.object({ label: z.string(), color: z.string().optional() })).default([]),
12
+ }),
13
+ }, async ({ graphId, node_type, name, type, values }) => {
14
+ const current = (await client.get(`/api/graphs/${graphId}/schema`));
15
+ const schema = current.definition ? JSON.parse(current.definition) : { nodes: [] };
16
+ const node = schema.nodes.find((n) => n.type === node_type);
17
+ if (!node) {
18
+ return {
19
+ content: [{ type: 'text', text: JSON.stringify({ error: `Node type "${node_type}" not found. Add it first via naumu_add_node_type.` }, null, 2) }],
20
+ };
21
+ }
22
+ node.attributes = node.attributes ?? [];
23
+ const existing = node.attributes.find((a) => a.name === name);
24
+ let action;
25
+ if (!existing) {
26
+ node.attributes.push({ name, type, values });
27
+ action = `Created attribute "${name}" (${type}) with ${values.length} value(s).`;
28
+ }
29
+ else {
30
+ const existingLabels = new Set((existing.values ?? []).map((v) => v.label));
31
+ const added = values.filter((v) => !existingLabels.has(v.label));
32
+ existing.values = [...(existing.values ?? []), ...added];
33
+ action = added.length > 0
34
+ ? `Extended attribute "${name}" with ${added.length} new value(s); kept ${existingLabels.size} existing.`
35
+ : `Attribute "${name}" already had all proposed values; no change.`;
36
+ }
37
+ await client.post(`/api/graphs/${graphId}/schema`, { definition: schema });
38
+ return {
39
+ content: [{ type: 'text', text: JSON.stringify({ message: action, node_type }, null, 2) }],
40
+ };
41
+ });
42
+ }
43
+ //# sourceMappingURL=add-attribute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-attribute.js","sourceRoot":"","sources":["../../src/tools/add-attribute.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NaumuClient } from '../client.js';
3
+ export declare function registerAddConnection(server: McpServer, client: NaumuClient): void;
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ export function registerAddConnection(server, client) {
3
+ server.registerTool('naumu_add_connection', {
4
+ title: 'Add Connection to Node Type',
5
+ description: 'Add one connection from an existing node type to another. `kind`: "parent" (sets/replaces — every non-root type needs exactly one parent; only ONE type in the whole schema has no parent), "required" (must exist on instances), "suggested" (optional). Each connection is XOR: ONE concrete target_node OR polymorphic=true — never both. DEFAULT TO CONCRETE target_node. Polymorphic is the ESCAPE HATCH — reserve for genuinely cross-cutting relations (e.g. a Tag-style relation that validly applies to many distinct types). If you can\'t pick a concrete target, you may be missing a type — add it first instead of falling back to polymorphic. Same-type nesting (e.g. Topic under Topic) does NOT need a new connection — use the existing parent relation. Relation name UPPER_SNAKE_CASE.',
6
+ inputSchema: z.object({
7
+ graphId: z.string(),
8
+ source_type: z.string().describe('Existing node type to add the connection to.'),
9
+ relation: z.string().describe('UPPER_SNAKE_CASE relation name.'),
10
+ target_node: z.string().optional().describe('Target type. Omit when polymorphic=true.'),
11
+ polymorphic: z.boolean().optional(),
12
+ kind: z.enum(['required', 'suggested', 'parent']).default('suggested'),
13
+ }),
14
+ }, async ({ graphId, source_type, relation, target_node, polymorphic, kind }) => {
15
+ const current = (await client.get(`/api/graphs/${graphId}/schema`));
16
+ const schema = current.definition ? JSON.parse(current.definition) : { nodes: [] };
17
+ const node = schema.nodes.find((n) => n.type === source_type);
18
+ if (!node) {
19
+ return {
20
+ content: [{ type: 'text', text: JSON.stringify({ error: `Node type "${source_type}" not found. Add it first via naumu_add_node_type.` }, null, 2) }],
21
+ };
22
+ }
23
+ const conn = { relation, ...(polymorphic ? { polymorphic: true } : {}), ...(target_node ? { target_node } : {}) };
24
+ if (!node.connections)
25
+ node.connections = { required: [], suggested: [] };
26
+ if (kind === 'parent') {
27
+ node.connections.parent = conn;
28
+ }
29
+ else {
30
+ const list = node.connections[kind] ?? [];
31
+ const exists = list.some((c) => c.relation === relation && c.target_node === target_node && Boolean(c.polymorphic) === Boolean(polymorphic));
32
+ if (exists) {
33
+ return {
34
+ content: [{ type: 'text', text: JSON.stringify({ message: `Connection already exists on "${source_type}", no change.` }, null, 2) }],
35
+ };
36
+ }
37
+ list.push(conn);
38
+ node.connections[kind] = list;
39
+ }
40
+ await client.post(`/api/graphs/${graphId}/schema`, { definition: schema });
41
+ return {
42
+ content: [{ type: 'text', text: JSON.stringify({ message: `Added ${kind} connection ${relation}${target_node ? ` → ${target_node}` : ' (polymorphic)'} on "${source_type}".` }, null, 2) }],
43
+ };
44
+ });
45
+ }
46
+ //# sourceMappingURL=add-connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-connection.js","sourceRoot":"","sources":["../../src/tools/add-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAoBxB,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,MAAmB;IAC3E,MAAM,CAAC,YAAY,CAClB,sBAAsB,EACtB;QACC,KAAK,EAAE,6BAA6B;QACpC,WAAW,EACV,6wBAA6wB;QAC9wB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;YAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YAChE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;YACvF,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;YACnC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;SACtE,CAAC;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE;QAC5E,MAAM,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,eAAe,OAAO,SAAS,CAAC,CAAkC,CAAC;QACrG,MAAM,MAAM,GAAc,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC9F,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,WAAW,oDAAoD,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aAC7J,CAAC;QACH,CAAC;QACD,MAAM,IAAI,GAAqB,EAAE,QAAQ,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACpI,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,WAAW,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QAC1E,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvB,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC;QAChC,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;YAC7I,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,iCAAiC,WAAW,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;iBAC7I,CAAC;YACH,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAC/B,CAAC;QACD,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,SAAS,IAAI,eAAe,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,gBAAgB,QAAQ,WAAW,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACpM,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
@@ -1,22 +1,30 @@
1
1
  import { z } from 'zod';
2
+ const EdgeInput = z.object({
3
+ source: z.string().describe('Source node ID'),
4
+ target: z.string().describe('Target node ID'),
5
+ label: z.string().describe('Relationship type (e.g. RELATES_TO, SOLVES, TRACKS)'),
6
+ isParent: z.boolean().optional().describe('Whether this is a parent relationship'),
7
+ });
2
8
  export function registerAddEdge(server, client) {
3
9
  server.registerTool('naumu_add_edge', {
4
- title: 'Add Edge',
5
- description: 'Low-level tool: create a single relationship (edge) between two nodes. **Prefer `naumu_ask`** for adding new knowledgeit automatically discovers and creates all relevant connections. Use this only when you need precise control over a specific relationship.',
10
+ title: 'Add Edges (bulk)',
11
+ description: 'Create 1–25 relationships (edges) between existing nodes in a single call. Keep batches small and atomic (5–25 edges) so failures stay contained. Typical workflow after a bulk import: create nodes with `naumu_add_node`, then wire them up with batched `naumu_add_edge` calls. **Direction matters and must match the schema.** Each edge\'s `(source.type, label, target.type)` tuple should appear in the schema\'s `connections` or `parent` for the source type. Wrong direction or wrong target type silently passes here but surfaces as `invalid_connection_target` / `parent_mismatch` violations later check `naumu_get_schema` and flip / drop offending edges before calling. **Prefer `naumu_ask`** when you want the agent to discover the right connections itself.',
6
12
  inputSchema: z.object({
7
13
  graphId: z.string().describe('The graph ID'),
8
- source: z.string().describe('Source node ID'),
9
- target: z.string().describe('Target node ID'),
10
- label: z.string().describe('Relationship type (e.g. RELATES_TO, SOLVES, TRACKS)'),
11
- isParent: z.boolean().optional().describe('Whether this is a parent relationship'),
14
+ edges: z
15
+ .array(EdgeInput)
16
+ .min(1)
17
+ .max(25)
18
+ .describe('Batch of 1–25 edges to create. Keep batches small for atomicity.'),
12
19
  }),
13
- }, async ({ graphId, source, target, label, isParent }) => {
14
- const data = await client.post(`/api/graphs/${graphId}/edges`, {
15
- source,
16
- target,
17
- label,
18
- isParent: isParent || false,
19
- });
20
+ }, async ({ graphId, edges }) => {
21
+ const payload = edges.map((e) => ({
22
+ source: e.source,
23
+ target: e.target,
24
+ label: e.label,
25
+ isParent: e.isParent || false,
26
+ }));
27
+ const data = await client.post(`/api/graphs/${graphId}/edges`, { edges: payload });
20
28
  return {
21
29
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
22
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"add-edge.js","sourceRoot":"","sources":["../../src/tools/add-edge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,eAAe,CAAC,MAAiB,EAAE,MAAmB;IACrE,MAAM,CAAC,YAAY,CAClB,gBAAgB,EAChB;QACC,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,qQAAqQ;QAClR,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC5C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAC7C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;YACjF,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;SAClF,CAAC;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;QACtD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,OAAO,QAAQ,EAAE;YAC9D,MAAM;YACN,MAAM;YACN,KAAK;YACL,QAAQ,EAAE,QAAQ,IAAI,KAAK;SAC3B,CAAC,CAAC;QACH,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"add-edge.js","sourceRoot":"","sources":["../../src/tools/add-edge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAC7C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;IACjF,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;CAClF,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAAC,MAAiB,EAAE,MAAmB;IACrE,MAAM,CAAC,YAAY,CAClB,gBAAgB,EAChB;QACC,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACV,yvBAAyvB;QAC1vB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC5C,KAAK,EAAE,CAAC;iBACN,KAAK,CAAC,SAAS,CAAC;iBAChB,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,EAAE,CAAC;iBACP,QAAQ,CAAC,kEAAkE,CAAC;SAC9E,CAAC;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK;SAC7B,CAAC,CAAC,CAAC;QAEJ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,OAAO,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACnF,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NaumuClient } from '../client.js';
3
+ export declare function registerAddNodeType(server: McpServer, client: NaumuClient): void;
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ const ConnectionSchema = z.object({
3
+ relation: z.string().describe('UPPER_SNAKE_CASE relation name (WORKS_AT, BELONGS_TO).'),
4
+ target_node: z.string().optional().describe('Target type name. Omit when polymorphic=true.'),
5
+ polymorphic: z.boolean().optional().describe('True when this relation can target many types.'),
6
+ });
7
+ const AttributeValueSchema = z.object({
8
+ label: z.string(),
9
+ color: z.string().optional(),
10
+ });
11
+ const AttributeSchema = z.object({
12
+ name: z.string(),
13
+ type: z.enum(['select', 'multiselect', 'string', 'number', 'date']).optional(),
14
+ values: z.array(AttributeValueSchema).default([]),
15
+ });
16
+ export function registerAddNodeType(server, client) {
17
+ server.registerTool('naumu_add_node_type', {
18
+ title: 'Add Node Type to Schema',
19
+ description: 'Add one new node type to the schema. Cheaper than naumu_update_schema. STRUCTURAL RULES: (1) Schemas are hierarchical with a single root. If schema is empty, the first type IS the root — omit `parent`. Every subsequent type MUST set `parent`. (2) DEFAULT TO CONCRETE — pass {relation, target_node: <existing type>}. Polymorphic is the ESCAPE HATCH — use {relation, polymorphic: true} ONLY for genuinely cross-cutting concepts like Comment or Tag (types that validly attach to many distinct parents). If you can\'t pick a concrete parent, you may be missing a type — add the missing type first instead of falling back to polymorphic. (3) Required/suggested connections also each take ONE concrete target_node OR polymorphic — never a list. Default concrete there too. (4) Type name PascalCase (Company, Person, Feature). Relation names UPPER_SNAKE_CASE (BELONGS_TO, WORKS_AT). Returns an error if the type already exists — use naumu_add_attribute / naumu_add_connection to extend it.',
20
+ inputSchema: z.object({
21
+ graphId: z.string(),
22
+ type: z.string().describe('PascalCase type name'),
23
+ parent: ConnectionSchema.optional().describe('Optional parent connection — sets this type as a child of another type.'),
24
+ required: z.array(ConnectionSchema).optional(),
25
+ suggested: z.array(ConnectionSchema).optional(),
26
+ attributes: z.array(AttributeSchema).optional(),
27
+ color: z.string().optional(),
28
+ }),
29
+ }, async ({ graphId, type, parent, required, suggested, attributes, color }) => {
30
+ const current = (await client.get(`/api/graphs/${graphId}/schema`));
31
+ const schema = current.definition ? JSON.parse(current.definition) : { nodes: [] };
32
+ if (schema.nodes.some((n) => n.type === type)) {
33
+ return {
34
+ content: [{ type: 'text', text: JSON.stringify({ error: `Node type "${type}" already exists. Use naumu_add_attribute / naumu_add_connection to extend it.` }, null, 2) }],
35
+ };
36
+ }
37
+ const newNode = {
38
+ type,
39
+ connections: {
40
+ required: required ?? [],
41
+ suggested: suggested ?? [],
42
+ ...(parent ? { parent } : {}),
43
+ },
44
+ ...(attributes && attributes.length > 0 ? { attributes } : {}),
45
+ ...(color ? { color } : {}),
46
+ };
47
+ schema.nodes.push(newNode);
48
+ await client.post(`/api/graphs/${graphId}/schema`, { definition: schema });
49
+ return {
50
+ content: [{ type: 'text', text: JSON.stringify({ message: `Added node type "${type}"`, total_types: schema.nodes.length }, null, 2) }],
51
+ };
52
+ });
53
+ }
54
+ //# sourceMappingURL=add-node-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-node-type.js","sourceRoot":"","sources":["../../src/tools/add-node-type.ts"],"names":[],"mappings":"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"}
@@ -1,22 +1,33 @@
1
1
  import { z } from 'zod';
2
+ const NodeInput = z.object({
3
+ label: z.string().describe('Display name of the node'),
4
+ type: z.string().describe('Node type from the graph schema (e.g. Feature, Pain, Metric)'),
5
+ content: z
6
+ .string()
7
+ .min(1)
8
+ .describe('Rich text content / description. REQUIRED — every node must explain what it is.'),
9
+ attributes: z.record(z.string(), z.unknown()).optional().describe('Additional key-value attributes'),
10
+ });
2
11
  export function registerAddNode(server, client) {
3
12
  server.registerTool('naumu_add_node', {
4
- title: 'Add Node',
5
- description: 'Low-level tool: create a single node in the knowledge graph. Warning: this creates an isolated node with no connectionsthe whole point of a knowledge graph is interconnectedness. **Prefer `naumu_ask`** for adding new knowledge, as it automatically discovers and creates relevant connections. Use this only when you need precise, surgical control over node creation.',
13
+ title: 'Add Nodes (bulk)',
14
+ description: 'Create 1–25 nodes in the knowledge graph in a single call. Keep batches small and atomic (5–25 nodes) so failures stay contained. Each node MUST include a non-empty `content` describing what it is. Returns one entry per input node with `{id, label, type, status: "created"}` there is NO server-side dedup, every input becomes a node. Dedup is the caller\'s responsibility: BEFORE calling this tool, run `naumu_semantic_search` on each candidate label and skip/route to update if a result has high similarity (≥0.78) and matching type. Warning: nodes are isolated until you connect them with `naumu_add_edge`. **Prefer `naumu_ask`** for general knowledge intake it discovers and creates connections for you. Use this when you have a vetted, dedup-checked batch ready to insert.',
6
15
  inputSchema: z.object({
7
16
  graphId: z.string().describe('The graph ID'),
8
- label: z.string().describe('Display name of the node'),
9
- type: z.string().describe('Node type from the graph schema (e.g. Feature, Pain, Metric)'),
10
- content: z.string().optional().describe('Rich text content / description'),
11
- attributes: z.record(z.string(), z.unknown()).optional().describe('Additional key-value attributes'),
17
+ nodes: z
18
+ .array(NodeInput)
19
+ .min(1)
20
+ .max(25)
21
+ .describe('Batch of 1–25 nodes to create. Keep batches small for atomicity.'),
12
22
  }),
13
- }, async ({ graphId, label, type, content, attributes }) => {
14
- const node = { label, type };
15
- if (content)
16
- node.content = content;
17
- if (attributes)
18
- Object.assign(node, attributes);
19
- const data = await client.post(`/api/graphs/${graphId}/nodes`, { nodes: [node] });
23
+ }, async ({ graphId, nodes }) => {
24
+ const payload = nodes.map(({ label, type, content, attributes }) => {
25
+ const node = { label, type, content };
26
+ if (attributes)
27
+ Object.assign(node, attributes);
28
+ return node;
29
+ });
30
+ const data = await client.post(`/api/graphs/${graphId}/nodes`, { nodes: payload });
20
31
  return {
21
32
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
22
33
  };
@@ -1 +1 @@
1
- {"version":3,"file":"add-node.js","sourceRoot":"","sources":["../../src/tools/add-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,eAAe,CAAC,MAAiB,EAAE,MAAmB;IACrE,MAAM,CAAC,YAAY,CAClB,gBAAgB,EAChB;QACC,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,kXAAkX;QAC/X,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC5C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YACtD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8DAA8D,CAAC;YACzF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YAC1E,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;SACpG,CAAC;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;QACvD,MAAM,IAAI,GAA4B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACtD,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACpC,IAAI,UAAU;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAEhD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,OAAO,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClF,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"add-node.js","sourceRoot":"","sources":["../../src/tools/add-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;IACtD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8DAA8D,CAAC;IACzF,OAAO,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,iFAAiF,CAAC;IAC7F,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;CACpG,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAAC,MAAiB,EAAE,MAAmB;IACrE,MAAM,CAAC,YAAY,CAClB,gBAAgB,EAChB;QACC,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACV,8wBAA8wB;QAC/wB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC5C,KAAK,EAAE,CAAC;iBACN,KAAK,CAAC,SAAS,CAAC;iBAChB,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,EAAE,CAAC;iBACP,QAAQ,CAAC,kEAAkE,CAAC;SAC9E,CAAC;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE;YAClE,MAAM,IAAI,GAA4B,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YAC/D,IAAI,UAAU;gBAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAChD,OAAO,IAAI,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,eAAe,OAAO,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACnF,OAAO;YACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC;IACH,CAAC,CACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NaumuClient } from '../client.js';
3
+ export declare function registerAddReaction(server: McpServer, client: NaumuClient): void;
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod';
2
+ export function registerAddReaction(server, client) {
3
+ server.registerTool('naumu_add_reaction', {
4
+ title: 'Add Reaction',
5
+ description: 'Add an emoji reaction to a message in a thread you are participating in. Idempotent — calling twice with the same emoji is a no-op (use `naumu_remove_reaction` to undo). Returns `{ ok, messageId, emoji, alreadyExisted, reactionCount, reactions }` so you can confirm the state without re-reading the thread; `alreadyExisted: true` means the reaction was already on the message and the call was a no-op.\n\nWhen to react vs. when to post a message:\n• React (no message) for lightweight acknowledgement (👀, ✅, 👍), appreciation (❤️, 🙌), laughter (😂), or "I saw this".\n• Post a message for direct questions, clarification, important corrections, or final results — situations where words are required.\n• For long tasks: react 👀 first to acknowledge, optionally post a short "On it — I\'ll report back" if the work will take >20s, do the work, then post the final result.\n• Ignore casual human banter, side-conversations someone else already answered, or anything where you would only say "ok"/"nice"/"lol".\n\nUse at most one reaction per message unless explicitly useful. Reactions are social backpressure relief, not a sparkle-confetti channel.',
6
+ inputSchema: z.object({
7
+ threadId: z
8
+ .string()
9
+ .describe('Thread containing the message. You must be a participant.'),
10
+ messageId: z
11
+ .string()
12
+ .describe('The message to react to.'),
13
+ emoji: z
14
+ .string()
15
+ .min(1)
16
+ .describe('Emoji character (e.g. "👀", "✅", "❤️"). Custom-emoji shortcodes are NOT supported here — pass a real Unicode emoji.'),
17
+ }),
18
+ }, async ({ threadId, messageId, emoji }) => {
19
+ try {
20
+ const data = await client.post(`/api/threads/${threadId}/messages/${messageId}/reactions`, { emoji, action: 'add' });
21
+ return {
22
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
23
+ };
24
+ }
25
+ catch (err) {
26
+ const message = err instanceof Error ? err.message : String(err);
27
+ return {
28
+ content: [{ type: 'text', text: `Error: ${message}` }],
29
+ isError: true,
30
+ };
31
+ }
32
+ });
33
+ }
34
+ //# sourceMappingURL=add-reaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-reaction.js","sourceRoot":"","sources":["../../src/tools/add-reaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAmB;IACzE,MAAM,CAAC,YAAY,CAClB,oBAAoB,EACpB;QACC,KAAK,EAAE,cAAc;QACrB,WAAW,EACV,goCAAgoC;QACjoC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACrB,QAAQ,EAAE,CAAC;iBACT,MAAM,EAAE;iBACR,QAAQ,CAAC,2DAA2D,CAAC;YACvE,SAAS,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,CAAC,0BAA0B,CAAC;YACtC,KAAK,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,qHAAqH,CAAC;SACjI,CAAC;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;QACxC,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAC7B,gBAAgB,QAAQ,aAAa,SAAS,YAAY,EAC1D,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CACxB,CAAC;YACF,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACzE,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;gBAC/D,OAAO,EAAE,IAAI;aACb,CAAC;QACH,CAAC;IACF,CAAC,CACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { NaumuClient } from '../client.js';
3
+ export declare function registerAskNaumu(server: McpServer, client: NaumuClient): void;