@naumu/mcp 0.7.0 → 0.7.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 +1 -1
- package/dist/index.js +483 -301
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -153,8 +153,44 @@ function registerListGraphs(server2, client2) {
|
|
|
153
153
|
);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
// ../mcp-core/src/tools/
|
|
156
|
+
// ../mcp-core/src/tools/list-members.ts
|
|
157
157
|
import { z as z2 } from "zod";
|
|
158
|
+
function registerListMembers(server2, client2) {
|
|
159
|
+
server2.registerTool(
|
|
160
|
+
"naumu_list_members",
|
|
161
|
+
{
|
|
162
|
+
title: "List Members",
|
|
163
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
164
|
+
description: "List the members of a Naumu space (graph): each member's userId, name, email, and role. Use it to discover teammates and to get the id you pass when @mentioning a human via naumu_post_message (their userId or email both work).",
|
|
165
|
+
inputSchema: z2.object({
|
|
166
|
+
graphId: z2.string().describe("The space (graph) ID to list members of. You must be a member of this space.")
|
|
167
|
+
})
|
|
168
|
+
},
|
|
169
|
+
async ({ graphId }) => {
|
|
170
|
+
try {
|
|
171
|
+
const data = await client2.get(`/api/graphs/${graphId}/members`);
|
|
172
|
+
const members = Array.isArray(data) ? data.map((m) => ({
|
|
173
|
+
userId: m.userId,
|
|
174
|
+
name: m.name,
|
|
175
|
+
email: m.email,
|
|
176
|
+
role: m.role
|
|
177
|
+
})) : data;
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: JSON.stringify(members, null, 2) }]
|
|
180
|
+
};
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
185
|
+
isError: true
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ../mcp-core/src/tools/create-graph.ts
|
|
193
|
+
import { z as z3 } from "zod";
|
|
158
194
|
function registerCreateGraph(server2, client2) {
|
|
159
195
|
server2.registerTool(
|
|
160
196
|
"naumu_create_graph",
|
|
@@ -162,8 +198,8 @@ function registerCreateGraph(server2, client2) {
|
|
|
162
198
|
title: "Create Graph",
|
|
163
199
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
164
200
|
description: "Create a new, empty knowledge graph (space) owned by the authenticated user; use when you need a fresh space to populate. Returns `{id, name, slug, role, memberRole, createdAt, onboardingThreadId}` - use the returned `id` as `graphId` for subsequent tool calls. The space starts empty (no schema, no nodes); follow up with `naumu_update_schema` to register types before any `naumu_add_node` calls.",
|
|
165
|
-
inputSchema:
|
|
166
|
-
name:
|
|
201
|
+
inputSchema: z3.object({
|
|
202
|
+
name: z3.string().min(1).describe("Display name for the new space. A URL slug is generated from this name.")
|
|
167
203
|
})
|
|
168
204
|
},
|
|
169
205
|
async ({ name }) => {
|
|
@@ -176,7 +212,7 @@ function registerCreateGraph(server2, client2) {
|
|
|
176
212
|
}
|
|
177
213
|
|
|
178
214
|
// ../mcp-core/src/tools/get-schema.ts
|
|
179
|
-
import { z as
|
|
215
|
+
import { z as z4 } from "zod";
|
|
180
216
|
function formatConnection(c) {
|
|
181
217
|
const out = { relation: c.relation };
|
|
182
218
|
if (c.polymorphic) {
|
|
@@ -224,8 +260,8 @@ function registerGetSchema(server2, client2) {
|
|
|
224
260
|
title: "Get Graph Schema",
|
|
225
261
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
226
262
|
description: "Get the schema definition for a knowledge graph; call this before classifying a new node into a type, before picking an attribute value, or before extending the schema. Returns a ROUND-TRIPPABLE structural schema: each `types[]` entry has the SAME shape `naumu_update_schema` ingests (type, description, structured `connections: { parent?, required, suggested }` with parent NESTED - not a string, and structured attributes), so you can copy a type straight back into naumu_update_schema without losing data (notably the parent relation). Descriptions are short, contrastive notes from the schema author explaining what each type/attribute/value is for and how it differs from similar-sounding ones.",
|
|
227
|
-
inputSchema:
|
|
228
|
-
graphId:
|
|
263
|
+
inputSchema: z4.object({
|
|
264
|
+
graphId: z4.string().describe("The graph ID")
|
|
229
265
|
})
|
|
230
266
|
},
|
|
231
267
|
async ({ graphId }) => {
|
|
@@ -247,40 +283,40 @@ function registerGetSchema(server2, client2) {
|
|
|
247
283
|
}
|
|
248
284
|
|
|
249
285
|
// ../mcp-core/src/tools/update-schema.ts
|
|
250
|
-
import { z as
|
|
251
|
-
var ConnectionSchema =
|
|
252
|
-
relation:
|
|
253
|
-
target_node:
|
|
254
|
-
polymorphic:
|
|
286
|
+
import { z as z5 } from "zod";
|
|
287
|
+
var ConnectionSchema = z5.object({
|
|
288
|
+
relation: z5.string().describe("UPPER_SNAKE_CASE relation name (e.g. WORKS_AT, BUILT_BY, BELONGS_TO)"),
|
|
289
|
+
target_node: z5.string().optional().describe("Target type name. Omit when polymorphic=true."),
|
|
290
|
+
polymorphic: z5.boolean().optional().describe("Set true when this relation can target multiple types.")
|
|
255
291
|
});
|
|
256
|
-
var AttributeValueSchema =
|
|
257
|
-
label:
|
|
258
|
-
color:
|
|
259
|
-
description:
|
|
292
|
+
var AttributeValueSchema = z5.object({
|
|
293
|
+
label: z5.string().describe("Display label for this enum value"),
|
|
294
|
+
color: z5.string().optional().describe('Optional hex color (e.g. "#ff5722")'),
|
|
295
|
+
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.')
|
|
260
296
|
});
|
|
261
|
-
var AttributeSchema =
|
|
262
|
-
name:
|
|
263
|
-
type:
|
|
264
|
-
values:
|
|
265
|
-
description:
|
|
297
|
+
var AttributeSchema = z5.object({
|
|
298
|
+
name: z5.string().describe('Attribute key (e.g. "stage", "status", "category")'),
|
|
299
|
+
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.'),
|
|
300
|
+
values: z5.array(AttributeValueSchema).describe("Allowed enum values for select/multiselect; pass [] for string/number/date."),
|
|
301
|
+
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.")
|
|
266
302
|
});
|
|
267
|
-
var NodeTypeSchema =
|
|
268
|
-
type:
|
|
269
|
-
connections:
|
|
303
|
+
var NodeTypeSchema = z5.object({
|
|
304
|
+
type: z5.string().describe("Type name in PascalCase (e.g. TypeA, TypeB)"),
|
|
305
|
+
connections: z5.object({
|
|
270
306
|
parent: ConnectionSchema.optional().describe("Optional parent relation (this type nests under another via this connection)."),
|
|
271
|
-
required:
|
|
272
|
-
suggested:
|
|
307
|
+
required: z5.array(ConnectionSchema).default([]).describe("Required outgoing connections to other types."),
|
|
308
|
+
suggested: z5.array(ConnectionSchema).default([]).describe("Suggested-but-optional outgoing connections.")
|
|
273
309
|
}),
|
|
274
|
-
attributes:
|
|
275
|
-
defaultVisibility:
|
|
310
|
+
attributes: z5.array(AttributeSchema).optional().describe("Type-level attributes for instances of this type."),
|
|
311
|
+
defaultVisibility: z5.enum(["restricted", "internal", "open"]).optional().describe(
|
|
276
312
|
'Default visibility for NEW nodes of this type when no explicit visibility is passed on creation. Does NOT retroactively change visibility on existing nodes. "open" = visible to anyone with the space link (including non-members), "internal" = visible to all space members, "restricted" = only members explicitly granted access. Omit to leave the type unset - it then falls back to the space-level defaultVisibility.'
|
|
277
313
|
),
|
|
278
|
-
color:
|
|
279
|
-
description:
|
|
314
|
+
color: z5.string().optional().describe("Optional hex color for instances of this type."),
|
|
315
|
+
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.')
|
|
280
316
|
});
|
|
281
|
-
var SchemaDefinitionSchema =
|
|
282
|
-
description:
|
|
283
|
-
nodes:
|
|
317
|
+
var SchemaDefinitionSchema = z5.object({
|
|
318
|
+
description: z5.string().optional().describe("Schema-level description / domain summary."),
|
|
319
|
+
nodes: z5.array(NodeTypeSchema).describe("All node types in the schema.")
|
|
284
320
|
});
|
|
285
321
|
function registerUpdateSchema(server2, client2) {
|
|
286
322
|
server2.registerTool(
|
|
@@ -289,8 +325,8 @@ function registerUpdateSchema(server2, client2) {
|
|
|
289
325
|
title: "Update Graph Schema",
|
|
290
326
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
291
327
|
description: "Replace the graph schema with a new full definition; use to bootstrap an empty graph (cold-start) or fully replace mid-build. STRUCTURAL RULES (load-bearing): (1) HIERARCHICAL with a single root - exactly ONE type has no parent; every other type MUST declare a parent. (2) Each parent / required / suggested connection is XOR: ONE concrete target_node OR polymorphic=true - never both, never a list of multiple targets. (3) DEFAULT TO CONCRETE target_node. Polymorphic is the ESCAPE HATCH - reserve for genuinely cross-cutting concepts like Comment or Tag (entities that validly attach to many distinct types). If you find yourself making most parents polymorphic, you are avoiding the design work - pick concrete relationships instead. If you can't pick a concrete parent, you may be missing a type - add the missing type first. (4) Same-type nesting is implicit: a node can be placed under another of its same type using the existing parent relation - never add a self-relation just to enable nesting. (5) Always provide `description` on every node type, attribute, and select value - one short, contrastive sentence (what it IS and what it is NOT vs sibling types/attrs/values). This is the single strongest disambiguation signal for future agents classifying nodes. When replacing mid-build, call naumu_get_schema first (it returns a round-trippable structural schema you can copy back) and send the FULL new schema; anything you omit is removed. PARENT PRESERVATION: omitting a type's `connections.parent` PRESERVES its existing parent (so a round-trip never silently orphans a type). To intentionally remove a parent and make a type a root, send `parent: null` explicitly. (A parent whose target type you delete is dropped automatically.) Conventions: type names PascalCase, relation names UPPER_SNAKE_CASE. Bias toward general types - refine via attributes or nested children, not type proliferation.",
|
|
292
|
-
inputSchema:
|
|
293
|
-
graphId:
|
|
328
|
+
inputSchema: z5.object({
|
|
329
|
+
graphId: z5.string().describe("The graph ID"),
|
|
294
330
|
schema: SchemaDefinitionSchema
|
|
295
331
|
})
|
|
296
332
|
},
|
|
@@ -306,22 +342,22 @@ function registerUpdateSchema(server2, client2) {
|
|
|
306
342
|
}
|
|
307
343
|
|
|
308
344
|
// ../mcp-core/src/tools/add-node-type.ts
|
|
309
|
-
import { z as
|
|
310
|
-
var ConnectionSchema2 =
|
|
311
|
-
relation:
|
|
312
|
-
target_node:
|
|
313
|
-
polymorphic:
|
|
345
|
+
import { z as z6 } from "zod";
|
|
346
|
+
var ConnectionSchema2 = z6.object({
|
|
347
|
+
relation: z6.string().describe("UPPER_SNAKE_CASE relation name (WORKS_AT, BELONGS_TO)."),
|
|
348
|
+
target_node: z6.string().optional().describe("Target type name. Omit when polymorphic=true."),
|
|
349
|
+
polymorphic: z6.boolean().optional().describe("True when this relation can target many types.")
|
|
314
350
|
});
|
|
315
|
-
var AttributeValueSchema2 =
|
|
316
|
-
label:
|
|
317
|
-
color:
|
|
318
|
-
description:
|
|
351
|
+
var AttributeValueSchema2 = z6.object({
|
|
352
|
+
label: z6.string(),
|
|
353
|
+
color: z6.string().optional(),
|
|
354
|
+
description: z6.string().optional().describe('Short note distinguishing this value from sibling values (e.g. for status="closed-won", "signed and revenue committed"). Encouraged when the label alone is ambiguous.')
|
|
319
355
|
});
|
|
320
|
-
var AttributeSchema2 =
|
|
321
|
-
name:
|
|
322
|
-
type:
|
|
323
|
-
values:
|
|
324
|
-
description:
|
|
356
|
+
var AttributeSchema2 = z6.object({
|
|
357
|
+
name: z6.string(),
|
|
358
|
+
type: z6.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.'),
|
|
359
|
+
values: z6.array(AttributeValueSchema2).default([]),
|
|
360
|
+
description: z6.string().optional().describe('Short note explaining what this attribute captures and how it differs from similarly-named attributes on other types (e.g. "stage on Type A" vs "stage on Type B"). Strongly encouraged.')
|
|
325
361
|
});
|
|
326
362
|
function registerAddNodeType(server2, client2) {
|
|
327
363
|
server2.registerTool(
|
|
@@ -330,18 +366,18 @@ function registerAddNodeType(server2, client2) {
|
|
|
330
366
|
title: "Add Node Type to Schema",
|
|
331
367
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
332
368
|
description: 'Add one new node type to the schema; use to extend an existing schema without resending the whole thing (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. Relation names UPPER_SNAKE_CASE. (5) ALWAYS provide `description` - one short sentence that says what this type is AND what it is NOT relative to semantically close types (e.g. "Type A \u2014 distinct from Type B, which is broader"). This is the single strongest signal future agents have when classifying a node into one of several similar-sounding types. Returns an error if the type already exists - use naumu_add_attribute / naumu_add_connection to extend it.',
|
|
333
|
-
inputSchema:
|
|
334
|
-
graphId:
|
|
335
|
-
type:
|
|
336
|
-
description:
|
|
369
|
+
inputSchema: z6.object({
|
|
370
|
+
graphId: z6.string(),
|
|
371
|
+
type: z6.string().describe("PascalCase type name"),
|
|
372
|
+
description: z6.string().optional().describe(
|
|
337
373
|
"Short one-sentence description of what this type represents and how it differs from sibling types. Contrastive (say what it IS and what it is NOT) is most useful. Strongly encouraged on every new type - agents reading the schema later rely on this to classify nodes."
|
|
338
374
|
),
|
|
339
375
|
parent: ConnectionSchema2.optional().describe("Optional parent connection - sets this type as a child of another type."),
|
|
340
|
-
required:
|
|
341
|
-
suggested:
|
|
342
|
-
attributes:
|
|
343
|
-
color:
|
|
344
|
-
defaultVisibility:
|
|
376
|
+
required: z6.array(ConnectionSchema2).optional(),
|
|
377
|
+
suggested: z6.array(ConnectionSchema2).optional(),
|
|
378
|
+
attributes: z6.array(AttributeSchema2).optional(),
|
|
379
|
+
color: z6.string().optional(),
|
|
380
|
+
defaultVisibility: z6.enum(["restricted", "internal", "open"]).optional().describe(
|
|
345
381
|
'Default visibility for NEW nodes of this type when no explicit visibility is passed on creation. Does NOT retroactively change existing nodes. "open" = visible to anyone with the space link, "internal" = visible to all space members, "restricted" = only members explicitly granted access. Omit to fall back to the space-level defaultVisibility.'
|
|
346
382
|
)
|
|
347
383
|
})
|
|
@@ -376,7 +412,7 @@ function registerAddNodeType(server2, client2) {
|
|
|
376
412
|
}
|
|
377
413
|
|
|
378
414
|
// ../mcp-core/src/tools/add-connection.ts
|
|
379
|
-
import { z as
|
|
415
|
+
import { z as z7 } from "zod";
|
|
380
416
|
function registerAddConnection(server2, client2) {
|
|
381
417
|
server2.registerTool(
|
|
382
418
|
"naumu_add_connection",
|
|
@@ -384,13 +420,13 @@ function registerAddConnection(server2, client2) {
|
|
|
384
420
|
title: "Add Connection to Node Type",
|
|
385
421
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
386
422
|
description: 'Add one connection from an existing node type to another; use to extend a type\'s relations without resending the whole schema. `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. Type A under Type A) does NOT need a new connection - use the existing parent relation. Relation name UPPER_SNAKE_CASE.',
|
|
387
|
-
inputSchema:
|
|
388
|
-
graphId:
|
|
389
|
-
source_type:
|
|
390
|
-
relation:
|
|
391
|
-
target_node:
|
|
392
|
-
polymorphic:
|
|
393
|
-
kind:
|
|
423
|
+
inputSchema: z7.object({
|
|
424
|
+
graphId: z7.string(),
|
|
425
|
+
source_type: z7.string().describe("Existing node type to add the connection to."),
|
|
426
|
+
relation: z7.string().describe("UPPER_SNAKE_CASE relation name."),
|
|
427
|
+
target_node: z7.string().optional().describe("Target type. Omit when polymorphic=true."),
|
|
428
|
+
polymorphic: z7.boolean().optional(),
|
|
429
|
+
kind: z7.enum(["required", "suggested", "parent"]).default("suggested")
|
|
394
430
|
})
|
|
395
431
|
},
|
|
396
432
|
async ({ graphId, source_type, relation, target_node, polymorphic, kind }) => {
|
|
@@ -426,7 +462,7 @@ function registerAddConnection(server2, client2) {
|
|
|
426
462
|
}
|
|
427
463
|
|
|
428
464
|
// ../mcp-core/src/tools/add-attribute.ts
|
|
429
|
-
import { z as
|
|
465
|
+
import { z as z8 } from "zod";
|
|
430
466
|
function registerAddAttribute(server2, client2) {
|
|
431
467
|
server2.registerTool(
|
|
432
468
|
"naumu_add_attribute",
|
|
@@ -434,19 +470,19 @@ function registerAddAttribute(server2, client2) {
|
|
|
434
470
|
title: "Add Attribute to Node Type",
|
|
435
471
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
436
472
|
description: 'Add or extend an attribute on an existing node type; use to add a field or merge new enum values into an existing one. 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). Date values on nodes are written via naumu_update_node as either an ISO date string "YYYY-MM-DD" (single day) or an object { start, end? } with ISO date strings (inclusive range). Strongly encouraged to provide `description` on the attribute and on each value - short, contrastive notes (e.g. attribute "stage - sales funnel position; distinct from status which captures health/blockers", value "closed-won - deal signed and revenue committed"). Descriptions are the single strongest signal future agents use when picking which attribute/value to set.',
|
|
437
|
-
inputSchema:
|
|
438
|
-
graphId:
|
|
439
|
-
node_type:
|
|
440
|
-
name:
|
|
441
|
-
type:
|
|
442
|
-
values:
|
|
443
|
-
|
|
444
|
-
label:
|
|
445
|
-
color:
|
|
446
|
-
description:
|
|
473
|
+
inputSchema: z8.object({
|
|
474
|
+
graphId: z8.string(),
|
|
475
|
+
node_type: z8.string().describe("Existing node type to add the attribute to."),
|
|
476
|
+
name: z8.string().describe('Attribute key (e.g. "stage", "status").'),
|
|
477
|
+
type: z8.enum(["select", "multiselect", "string", "number", "date"]).describe('Attribute type (required). select/multiselect define a closed set of categories - provide their values. string/number/date hold free-form per-node data - pass values: []. A specific calendar date is per-node data, so use type "date" and write the date on the node, rather than adding the date as a select value.'),
|
|
478
|
+
values: z8.array(
|
|
479
|
+
z8.object({
|
|
480
|
+
label: z8.string(),
|
|
481
|
+
color: z8.string().optional(),
|
|
482
|
+
description: z8.string().optional().describe('Short note distinguishing this value from sibling values (e.g. "closed-won - deal signed and revenue committed"). Encouraged when the label alone is ambiguous.')
|
|
447
483
|
})
|
|
448
484
|
).default([]),
|
|
449
|
-
description:
|
|
485
|
+
description: z8.string().optional().describe('Short note explaining what this attribute captures and how it differs from similarly-named attributes elsewhere in the schema (e.g. "Sales funnel position; differs from `status` which captures health/blockers"). Strongly encouraged.')
|
|
450
486
|
})
|
|
451
487
|
},
|
|
452
488
|
async ({ graphId, node_type, name, type, values, description }) => {
|
|
@@ -480,7 +516,7 @@ function registerAddAttribute(server2, client2) {
|
|
|
480
516
|
}
|
|
481
517
|
|
|
482
518
|
// ../mcp-core/src/tools/search.ts
|
|
483
|
-
import { z as
|
|
519
|
+
import { z as z9 } from "zod";
|
|
484
520
|
function registerSearch(server2, client2) {
|
|
485
521
|
server2.registerTool(
|
|
486
522
|
"naumu_search",
|
|
@@ -488,13 +524,13 @@ function registerSearch(server2, client2) {
|
|
|
488
524
|
title: "Search Graph",
|
|
489
525
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
490
526
|
description: 'Hybrid search over graph nodes; use for meaning-based lookup when you don\'t know the exact label. Combines exact-token text matching (good for UUIDs, proper nouns, specific labels) with semantic similarity (good for paraphrase and meaning), then fuses both rankings with Reciprocal Rank Fusion. Returns the top matches with a `matchedVia` tag - `both` is the highest-confidence signal, then `semantic`, then `text`. Use `naumu_filter` for structured queries by type and attributes (e.g. "all in-progress Tasks"). Tip: include both synonyms ("authentication login SSO") and exact tokens you remember in the same query - the fusion handles both.',
|
|
491
|
-
inputSchema:
|
|
492
|
-
graphId:
|
|
493
|
-
query:
|
|
527
|
+
inputSchema: z9.object({
|
|
528
|
+
graphId: z9.string().describe("The graph ID"),
|
|
529
|
+
query: z9.string().describe(
|
|
494
530
|
'Search query text. Mix synonyms and exact tokens freely (e.g. "auth login SSO 2fa Twitter handle").'
|
|
495
531
|
),
|
|
496
|
-
limit:
|
|
497
|
-
nodeTypes:
|
|
532
|
+
limit: z9.number().optional().default(20).describe("Max results to return (default 20, max 200). Adaptive cutoff may return fewer when the top match is weak."),
|
|
533
|
+
nodeTypes: z9.array(z9.string()).optional().describe('Filter to specific node types (e.g. ["Type A", "Type B"])')
|
|
498
534
|
})
|
|
499
535
|
},
|
|
500
536
|
async ({ graphId, query, limit, nodeTypes }) => {
|
|
@@ -511,7 +547,7 @@ function registerSearch(server2, client2) {
|
|
|
511
547
|
}
|
|
512
548
|
|
|
513
549
|
// ../mcp-core/src/tools/filter.ts
|
|
514
|
-
import { z as
|
|
550
|
+
import { z as z10 } from "zod";
|
|
515
551
|
function registerFilter(server2, client2) {
|
|
516
552
|
server2.registerTool(
|
|
517
553
|
"naumu_filter",
|
|
@@ -519,17 +555,17 @@ function registerFilter(server2, client2) {
|
|
|
519
555
|
title: "Filter Graph Nodes",
|
|
520
556
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
521
557
|
description: 'Filter nodes by type and attributes with deterministic, complete results; use for structured queries like "all in-progress Tasks" or "Bugs not yet resolved." Unlike search tools, this returns every matching node (up to the limit) - no semantic ranking, no missed results. Results are sorted by sortKey or recency.',
|
|
522
|
-
inputSchema:
|
|
523
|
-
graphId:
|
|
524
|
-
nodeTypes:
|
|
525
|
-
includeAttributes:
|
|
558
|
+
inputSchema: z10.object({
|
|
559
|
+
graphId: z10.string().describe("The graph ID"),
|
|
560
|
+
nodeTypes: z10.array(z10.string()).optional().describe('Filter to specific node types (e.g. ["Type A", "Type B"])'),
|
|
561
|
+
includeAttributes: z10.record(z10.string(), z10.array(z10.string())).optional().describe(
|
|
526
562
|
'Only include nodes where attribute matches one of the values. Example: {"Status": ["Todo", "In Progress"]}'
|
|
527
563
|
),
|
|
528
|
-
excludeAttributes:
|
|
564
|
+
excludeAttributes: z10.record(z10.string(), z10.array(z10.string())).optional().describe(
|
|
529
565
|
'Exclude nodes where attribute matches any of the values. Example: {"Status": ["Done", "Wont do"]}'
|
|
530
566
|
),
|
|
531
|
-
sortBy:
|
|
532
|
-
limit:
|
|
567
|
+
sortBy: z10.enum(["sortKey", "updatedAt", "label"]).optional().default("sortKey").describe('Sort order: "sortKey" (default), "updatedAt" (most recent first), or "label" (alphabetical)'),
|
|
568
|
+
limit: z10.number().optional().default(50).describe("Max results to return (default 50, max 200)")
|
|
533
569
|
})
|
|
534
570
|
},
|
|
535
571
|
async ({ graphId, nodeTypes, includeAttributes, excludeAttributes, sortBy, limit }) => {
|
|
@@ -554,7 +590,7 @@ function registerFilter(server2, client2) {
|
|
|
554
590
|
}
|
|
555
591
|
|
|
556
592
|
// ../mcp-core/src/tools/get-node.ts
|
|
557
|
-
import { z as
|
|
593
|
+
import { z as z11 } from "zod";
|
|
558
594
|
function registerGetNode(server2, client2) {
|
|
559
595
|
server2.registerTool(
|
|
560
596
|
"naumu_get_node",
|
|
@@ -562,9 +598,9 @@ function registerGetNode(server2, client2) {
|
|
|
562
598
|
title: "Get Node",
|
|
563
599
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
564
600
|
description: "Get a single node with all its properties and connections (incoming and outgoing edges).",
|
|
565
|
-
inputSchema:
|
|
566
|
-
graphId:
|
|
567
|
-
nodeId:
|
|
601
|
+
inputSchema: z11.object({
|
|
602
|
+
graphId: z11.string().describe("The graph ID"),
|
|
603
|
+
nodeId: z11.string().describe("The node ID")
|
|
568
604
|
})
|
|
569
605
|
},
|
|
570
606
|
async ({ graphId, nodeId }) => {
|
|
@@ -577,38 +613,7 @@ function registerGetNode(server2, client2) {
|
|
|
577
613
|
}
|
|
578
614
|
|
|
579
615
|
// ../mcp-core/src/tools/add-node.ts
|
|
580
|
-
import { z as
|
|
581
|
-
var NodeInput = z11.object({
|
|
582
|
-
label: z11.string().describe("Display name of the node"),
|
|
583
|
-
type: z11.string().describe("Node type from the graph schema"),
|
|
584
|
-
content: z11.string().min(1).describe("Rich text content / description. REQUIRED - every node must explain what it is."),
|
|
585
|
-
attributes: z11.record(z11.string(), z11.unknown()).optional().describe("Additional key-value attributes")
|
|
586
|
-
});
|
|
587
|
-
function registerAddNode(server2, client2) {
|
|
588
|
-
server2.registerTool(
|
|
589
|
-
"naumu_add_node",
|
|
590
|
-
{
|
|
591
|
-
title: "Add Nodes (bulk)",
|
|
592
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
593
|
-
description: 'Create 1-25 nodes in the knowledge graph in a single call; use when you have a vetted, dedup-checked batch ready to insert. 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_search` on each candidate label and skip/route to update if a result has high similarity (\u22650.78) and matching type. Warning: nodes are isolated until you connect them with `naumu_add_edge`. Prefer `naumu_delegate` for general knowledge intake - it discovers and creates connections for you.',
|
|
594
|
-
inputSchema: z11.object({
|
|
595
|
-
graphId: z11.string().describe("The graph ID"),
|
|
596
|
-
nodes: z11.array(NodeInput).min(1).max(25).describe("Batch of 1\u201325 nodes to create. Keep batches small for atomicity.")
|
|
597
|
-
})
|
|
598
|
-
},
|
|
599
|
-
async ({ graphId, nodes }) => {
|
|
600
|
-
const payload = nodes.map(({ label, type, content, attributes }) => {
|
|
601
|
-
const node = { label, type, content };
|
|
602
|
-
if (attributes) Object.assign(node, attributes);
|
|
603
|
-
return node;
|
|
604
|
-
});
|
|
605
|
-
const data = await client2.post(`/api/graphs/${graphId}/nodes`, { nodes: payload });
|
|
606
|
-
return {
|
|
607
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
);
|
|
611
|
-
}
|
|
616
|
+
import { z as z13 } from "zod";
|
|
612
617
|
|
|
613
618
|
// ../mcp-core/src/tools/update-node.ts
|
|
614
619
|
import { z as z12 } from "zod";
|
|
@@ -740,13 +745,120 @@ Use naumu_get_schema to check valid attributes and values for this node type.`
|
|
|
740
745
|
);
|
|
741
746
|
}
|
|
742
747
|
|
|
748
|
+
// ../mcp-core/src/tools/add-node.ts
|
|
749
|
+
var RESERVED_KEYS = /* @__PURE__ */ new Set(["id", "label", "type", "content"]);
|
|
750
|
+
function normalizeNodeAttributes(schema, nodeType, attributes) {
|
|
751
|
+
const attrMap = /* @__PURE__ */ new Map();
|
|
752
|
+
const nodeDef = schema.nodes.find((n) => n.type === nodeType);
|
|
753
|
+
for (const attr of nodeDef?.attributes ?? []) {
|
|
754
|
+
attrMap.set(attr.name.toLowerCase(), attr);
|
|
755
|
+
}
|
|
756
|
+
const normalized = {};
|
|
757
|
+
const errors = [];
|
|
758
|
+
for (const [key, val] of Object.entries(attributes)) {
|
|
759
|
+
const lower = key.toLowerCase();
|
|
760
|
+
if (RESERVED_KEYS.has(lower)) {
|
|
761
|
+
errors.push(`Attribute "${key}" conflicts with a reserved node field.`);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const schemaDef = attrMap.get(lower);
|
|
765
|
+
if (!schemaDef) {
|
|
766
|
+
const validNames = [...attrMap.values()].map((a) => a.name);
|
|
767
|
+
errors.push(
|
|
768
|
+
`Unknown attribute "${key}" for type "${nodeType}". Valid attributes: ${validNames.length > 0 ? validNames.join(", ") : "none"}.`
|
|
769
|
+
);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (schemaDef.type === "date") {
|
|
773
|
+
const result = validateDateAttribute(schemaDef.name, val);
|
|
774
|
+
if (!result.ok) {
|
|
775
|
+
errors.push(result.error);
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (result.value === null) continue;
|
|
779
|
+
const range = typeof result.value === "string" ? { start: result.value } : result.value;
|
|
780
|
+
normalized[lower] = JSON.stringify(range);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
const validValues = schemaDef.values.map((v) => v.label);
|
|
784
|
+
if (typeof val === "string" && validValues.length > 0 && !validValues.includes(val)) {
|
|
785
|
+
errors.push(
|
|
786
|
+
`Invalid value "${val}" for attribute "${schemaDef.name}" on type "${nodeType}". Valid values: ${validValues.join(", ")}.`
|
|
787
|
+
);
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
normalized[lower] = val;
|
|
791
|
+
}
|
|
792
|
+
return errors.length > 0 ? { ok: false, errors } : { ok: true, attributes: normalized };
|
|
793
|
+
}
|
|
794
|
+
var NodeInput = z13.object({
|
|
795
|
+
label: z13.string().describe("Display name of the node"),
|
|
796
|
+
type: z13.string().describe("Node type from the graph schema"),
|
|
797
|
+
content: z13.string().min(1).describe("Rich text content / description. REQUIRED - every node must explain what it is."),
|
|
798
|
+
attributes: z13.record(z13.string(), z13.unknown()).optional().describe(
|
|
799
|
+
`Schema-defined attributes. Keys are matched case-insensitively against the node type's schema attributes and stored in canonical form; unknown attributes or invalid select values reject the whole batch. Date attributes accept "YYYY-MM-DD" or { start, end? }.`
|
|
800
|
+
)
|
|
801
|
+
});
|
|
802
|
+
function registerAddNode(server2, client2) {
|
|
803
|
+
server2.registerTool(
|
|
804
|
+
"naumu_add_node",
|
|
805
|
+
{
|
|
806
|
+
title: "Add Nodes (bulk)",
|
|
807
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
808
|
+
description: 'Create 1-25 nodes in the knowledge graph in a single call; use when you have a vetted, dedup-checked batch ready to insert. 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_search` on each candidate label and skip/route to update if a result has high similarity (\u22650.78) and matching type. Warning: nodes are isolated until you connect them with `naumu_add_edge`. Prefer `naumu_delegate` for general knowledge intake - it discovers and creates connections for you.',
|
|
809
|
+
inputSchema: z13.object({
|
|
810
|
+
graphId: z13.string().describe("The graph ID"),
|
|
811
|
+
nodes: z13.array(NodeInput).min(1).max(25).describe("Batch of 1\u201325 nodes to create. Keep batches small for atomicity.")
|
|
812
|
+
})
|
|
813
|
+
},
|
|
814
|
+
async ({ graphId, nodes }) => {
|
|
815
|
+
let schema = null;
|
|
816
|
+
if (nodes.some((n) => n.attributes && Object.keys(n.attributes).length > 0)) {
|
|
817
|
+
const schemaRes = await client2.get(`/api/graphs/${graphId}/schema`);
|
|
818
|
+
schema = JSON.parse(schemaRes.definition);
|
|
819
|
+
}
|
|
820
|
+
const errors = [];
|
|
821
|
+
const payload = nodes.map(({ label, type, content, attributes }, i) => {
|
|
822
|
+
const node = { label, type, content };
|
|
823
|
+
if (schema && attributes && Object.keys(attributes).length > 0) {
|
|
824
|
+
const result = normalizeNodeAttributes(schema, type, attributes);
|
|
825
|
+
if (result.ok) {
|
|
826
|
+
Object.assign(node, result.attributes);
|
|
827
|
+
} else {
|
|
828
|
+
errors.push(...result.errors.map((e) => `nodes[${i}] ("${label}"): ${e}`));
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return node;
|
|
832
|
+
});
|
|
833
|
+
if (errors.length > 0) {
|
|
834
|
+
return {
|
|
835
|
+
content: [
|
|
836
|
+
{
|
|
837
|
+
type: "text",
|
|
838
|
+
text: `Attribute validation failed \u2014 no nodes were created:
|
|
839
|
+
${errors.join("\n")}
|
|
840
|
+
|
|
841
|
+
Use naumu_get_schema to check valid attribute names, types, and values.`
|
|
842
|
+
}
|
|
843
|
+
],
|
|
844
|
+
isError: true
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
const data = await client2.post(`/api/graphs/${graphId}/nodes`, { nodes: payload });
|
|
848
|
+
return {
|
|
849
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
743
855
|
// ../mcp-core/src/tools/add-edge.ts
|
|
744
|
-
import { z as
|
|
745
|
-
var EdgeInput =
|
|
746
|
-
source:
|
|
747
|
-
target:
|
|
748
|
-
label:
|
|
749
|
-
isParent:
|
|
856
|
+
import { z as z14 } from "zod";
|
|
857
|
+
var EdgeInput = z14.object({
|
|
858
|
+
source: z14.string().describe("Source node ID"),
|
|
859
|
+
target: z14.string().describe("Target node ID"),
|
|
860
|
+
label: z14.string().describe("Relationship type (e.g. RELATES_TO, SOLVES, TRACKS)"),
|
|
861
|
+
isParent: z14.boolean().optional().describe("Whether this is a parent relationship")
|
|
750
862
|
});
|
|
751
863
|
function registerAddEdge(server2, client2) {
|
|
752
864
|
server2.registerTool(
|
|
@@ -755,9 +867,9 @@ function registerAddEdge(server2, client2) {
|
|
|
755
867
|
title: "Add Edges (bulk)",
|
|
756
868
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
757
869
|
description: "Create 1-25 relationships (edges) between existing nodes in a single call; use after a bulk node insert to wire nodes together. Keep batches small and atomic (5-25 edges) so failures stay contained. Typical workflow: 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. When the backend runs with `STRICT_EDGE_VALIDATION=true`, invalid tuples are rejected immediately with `error: invalid_edge` (the response includes `details.allowed_targets_for_relation` and a `hint` for routing the call); otherwise the edge persists and surfaces later as an `invalid_connection_target` / `parent_mismatch` violation. Either way, check `naumu_get_schema` and flip or drop offending edges before calling. Prefer `naumu_delegate` when you want the agent to discover the right connections itself.",
|
|
758
|
-
inputSchema:
|
|
759
|
-
graphId:
|
|
760
|
-
edges:
|
|
870
|
+
inputSchema: z14.object({
|
|
871
|
+
graphId: z14.string().describe("The graph ID"),
|
|
872
|
+
edges: z14.array(EdgeInput).min(1).max(25).describe("Batch of 1\u201325 edges to create. Keep batches small for atomicity.")
|
|
761
873
|
})
|
|
762
874
|
},
|
|
763
875
|
async ({ graphId, edges }) => {
|
|
@@ -776,7 +888,7 @@ function registerAddEdge(server2, client2) {
|
|
|
776
888
|
}
|
|
777
889
|
|
|
778
890
|
// ../mcp-core/src/tools/remove-node.ts
|
|
779
|
-
import { z as
|
|
891
|
+
import { z as z15 } from "zod";
|
|
780
892
|
function registerRemoveNode(server2, client2) {
|
|
781
893
|
server2.registerTool(
|
|
782
894
|
"naumu_remove_node",
|
|
@@ -784,9 +896,9 @@ function registerRemoveNode(server2, client2) {
|
|
|
784
896
|
title: "Remove Node",
|
|
785
897
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
786
898
|
description: "Delete a node and all its connections from the knowledge graph; use only when you need precise, surgical deletion. This is destructive. Prefer `naumu_delegate` for removing knowledge - it understands the impact on the broader graph and can handle cascading changes.",
|
|
787
|
-
inputSchema:
|
|
788
|
-
graphId:
|
|
789
|
-
nodeId:
|
|
899
|
+
inputSchema: z15.object({
|
|
900
|
+
graphId: z15.string().describe("The graph ID"),
|
|
901
|
+
nodeId: z15.string().describe("The node ID to delete")
|
|
790
902
|
})
|
|
791
903
|
},
|
|
792
904
|
async ({ graphId, nodeId }) => {
|
|
@@ -799,7 +911,7 @@ function registerRemoveNode(server2, client2) {
|
|
|
799
911
|
}
|
|
800
912
|
|
|
801
913
|
// ../mcp-core/src/tools/remove-edge.ts
|
|
802
|
-
import { z as
|
|
914
|
+
import { z as z16 } from "zod";
|
|
803
915
|
function registerRemoveEdge(server2, client2) {
|
|
804
916
|
server2.registerTool(
|
|
805
917
|
"naumu_remove_edge",
|
|
@@ -807,11 +919,11 @@ function registerRemoveEdge(server2, client2) {
|
|
|
807
919
|
title: "Remove Edge",
|
|
808
920
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
809
921
|
description: "Delete a single edge identified by `(source, target, label)` tuple; use to correct a wrong-target edge mistake (a recovery tool, not a routine one). Does NOT delete the endpoint nodes - only the edge between them. System edges (HAS_NODE, HAS_THREAD, OWNS, etc.) are rejected with `error: system_edge_not_removable`. Parent edges (`isParent: true`) are removable but the response includes a warning that the child may now be orphaned - call `naumu_reparent` first if you want connectivity preserved. Response: `{deleted: 0 | 1, warnings: string[]}`. Prefer `naumu_delegate` when the structural intent is broader than removing one specific edge - it can reason about the surrounding graph.",
|
|
810
|
-
inputSchema:
|
|
811
|
-
graphId:
|
|
812
|
-
source:
|
|
813
|
-
target:
|
|
814
|
-
label:
|
|
922
|
+
inputSchema: z16.object({
|
|
923
|
+
graphId: z16.string().describe("The graph ID"),
|
|
924
|
+
source: z16.string().describe("Source node id of the edge to delete"),
|
|
925
|
+
target: z16.string().describe("Target node id of the edge to delete"),
|
|
926
|
+
label: z16.string().describe('Relation label of the edge to delete (e.g. "AUTHORED"). Case-insensitive; non-alphanum chars are normalized.')
|
|
815
927
|
})
|
|
816
928
|
},
|
|
817
929
|
async ({ graphId, source, target, label }) => {
|
|
@@ -828,11 +940,11 @@ function registerRemoveEdge(server2, client2) {
|
|
|
828
940
|
}
|
|
829
941
|
|
|
830
942
|
// ../mcp-core/src/tools/remove-edges-bulk.ts
|
|
831
|
-
import { z as
|
|
832
|
-
var EdgeRef =
|
|
833
|
-
source:
|
|
834
|
-
target:
|
|
835
|
-
label:
|
|
943
|
+
import { z as z17 } from "zod";
|
|
944
|
+
var EdgeRef = z17.object({
|
|
945
|
+
source: z17.string().describe("Source node ID"),
|
|
946
|
+
target: z17.string().describe("Target node ID"),
|
|
947
|
+
label: z17.string().describe("Relation label of the edge to delete")
|
|
836
948
|
});
|
|
837
949
|
function registerRemoveEdgesBulk(server2, client2) {
|
|
838
950
|
server2.registerTool(
|
|
@@ -841,9 +953,9 @@ function registerRemoveEdgesBulk(server2, client2) {
|
|
|
841
953
|
title: "Remove Edges (bulk)",
|
|
842
954
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
843
955
|
description: "Delete 1-100 edges in a single atomic call (all-or-none); use to fix multiple wrong-target edge mistakes in one shot. Does NOT delete endpoint nodes. System edges (HAS_NODE, HAS_THREAD, OWNS, etc.) are rejected - if any edge in the batch targets a system relation, the WHOLE batch is rejected with `error: system_edge_not_removable`. Parent edges (`isParent: true`) are removable but each parent removal contributes a warning to the response (`{deleted, warnings: string[]}`) - call `naumu_reparent` first if you want connectivity preserved. Prefer `naumu_delegate` for broader structural cleanup that needs graph-wide reasoning.",
|
|
844
|
-
inputSchema:
|
|
845
|
-
graphId:
|
|
846
|
-
edges:
|
|
956
|
+
inputSchema: z17.object({
|
|
957
|
+
graphId: z17.string().describe("The graph ID"),
|
|
958
|
+
edges: z17.array(EdgeRef).min(1).max(100).describe("1-100 edges to delete. Atomic per call - all succeed or none do.")
|
|
847
959
|
})
|
|
848
960
|
},
|
|
849
961
|
async ({ graphId, edges }) => {
|
|
@@ -856,7 +968,7 @@ function registerRemoveEdgesBulk(server2, client2) {
|
|
|
856
968
|
}
|
|
857
969
|
|
|
858
970
|
// ../mcp-core/src/tools/ask.ts
|
|
859
|
-
import { z as
|
|
971
|
+
import { z as z18 } from "zod";
|
|
860
972
|
function registerAsk(server2, client2) {
|
|
861
973
|
server2.registerTool(
|
|
862
974
|
"naumu_ask",
|
|
@@ -872,14 +984,20 @@ function registerAsk(server2, client2) {
|
|
|
872
984
|
openWorldHint: true
|
|
873
985
|
},
|
|
874
986
|
description: 'Ask @Naumu a question about a space and get back a synthesised, node-grounded answer with the exact source node ids it used and a confidence hint. This is THE tool for any question about a space - what is in it, what is new or recently changed, how something works, or a summary of any topic. @Naumu has full read access and has already inspected the graph for you, so its answer is authoritative: present it and its cited sources directly. Do NOT independently re-read the graph (naumu_get_schema, naumu_filter, naumu_get_node, fetch) to verify or flesh out the answer - that repeats work @Naumu already did and is far slower. Reach for a granular read ONLY to pull a specific node the answer pointed to but did not fully include. Returns { answer, sources, confidence, threadId, status }; the answer is saved as a visible conversation in the space, which is expected and useful, so do not avoid the tool to prevent creating a thread. On a very long synthesis it may return status "processing" with a threadId - in that case wait a few seconds and call naumu_read_thread with that threadId for the final answer, and still do not fall back to manual digging. To hand @Naumu work to carry out (add knowledge, make changes, record status) without waiting, use naumu_delegate instead.',
|
|
875
|
-
inputSchema:
|
|
876
|
-
graphId:
|
|
877
|
-
question:
|
|
987
|
+
inputSchema: z18.object({
|
|
988
|
+
graphId: z18.string().describe("The space (graph) id to ask about."),
|
|
989
|
+
question: z18.string().max(4e3).describe("The question for @Naumu. Up to 4000 characters."),
|
|
990
|
+
topicIds: z18.array(z18.string()).max(8).optional().describe(
|
|
991
|
+
"Topic ids (from naumu_list_topics) to file the resulting conversation into, making it visible to those topics' members from birth. Omit to keep the default behavior (the ask lands in the virtual #misc bucket)."
|
|
992
|
+
)
|
|
878
993
|
})
|
|
879
994
|
},
|
|
880
|
-
async ({ graphId, question }) => {
|
|
995
|
+
async ({ graphId, question, topicIds }) => {
|
|
881
996
|
try {
|
|
882
|
-
const data = await client2.post(`/api/graphs/${graphId}/ask`, {
|
|
997
|
+
const data = await client2.post(`/api/graphs/${graphId}/ask`, {
|
|
998
|
+
question,
|
|
999
|
+
...topicIds && topicIds.length > 0 ? { topicIds } : {}
|
|
1000
|
+
});
|
|
883
1001
|
return {
|
|
884
1002
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
885
1003
|
};
|
|
@@ -895,7 +1013,7 @@ function registerAsk(server2, client2) {
|
|
|
895
1013
|
}
|
|
896
1014
|
|
|
897
1015
|
// ../mcp-core/src/tools/delegate.ts
|
|
898
|
-
import { z as
|
|
1016
|
+
import { z as z19 } from "zod";
|
|
899
1017
|
function registerDelegate(server2, client2) {
|
|
900
1018
|
server2.registerTool(
|
|
901
1019
|
"naumu_delegate",
|
|
@@ -910,23 +1028,30 @@ function registerDelegate(server2, client2) {
|
|
|
910
1028
|
idempotentHint: false,
|
|
911
1029
|
openWorldHint: true
|
|
912
1030
|
},
|
|
913
|
-
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.',
|
|
914
|
-
inputSchema:
|
|
915
|
-
graphId:
|
|
916
|
-
task:
|
|
917
|
-
threadId:
|
|
1031
|
+
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).',
|
|
1032
|
+
inputSchema: z19.object({
|
|
1033
|
+
graphId: z19.string().describe("The space (graph) id to act in."),
|
|
1034
|
+
task: z19.string().describe("What you want @Naumu to do, add, or record."),
|
|
1035
|
+
threadId: z19.string().optional().describe("Continue an existing conversation; omit to start a new one."),
|
|
1036
|
+
topicIds: z19.array(z19.string()).max(8).optional().describe(
|
|
1037
|
+
"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."
|
|
1038
|
+
)
|
|
918
1039
|
})
|
|
919
1040
|
},
|
|
920
|
-
async ({ graphId, task, threadId }) => {
|
|
1041
|
+
async ({ graphId, task, threadId, topicIds }) => {
|
|
921
1042
|
try {
|
|
922
1043
|
let resolvedThreadId = threadId;
|
|
923
1044
|
if (!resolvedThreadId) {
|
|
924
|
-
const thread = await client2.post("/api/threads", {
|
|
1045
|
+
const thread = await client2.post("/api/threads", {
|
|
1046
|
+
graphId,
|
|
1047
|
+
...topicIds && topicIds.length > 0 ? { topicIds } : {}
|
|
1048
|
+
});
|
|
925
1049
|
resolvedThreadId = thread.id;
|
|
926
1050
|
}
|
|
927
1051
|
await client2.post(`/api/threads/${resolvedThreadId}/messages`, {
|
|
928
1052
|
content: task,
|
|
929
|
-
async: true
|
|
1053
|
+
async: true,
|
|
1054
|
+
invokeAgent: true
|
|
930
1055
|
});
|
|
931
1056
|
return {
|
|
932
1057
|
content: [
|
|
@@ -952,7 +1077,7 @@ function registerDelegate(server2, client2) {
|
|
|
952
1077
|
}
|
|
953
1078
|
|
|
954
1079
|
// ../mcp-core/src/tools/post-message.ts
|
|
955
|
-
import { z as
|
|
1080
|
+
import { z as z20 } from "zod";
|
|
956
1081
|
function registerPostMessage(server2, client2) {
|
|
957
1082
|
server2.registerTool(
|
|
958
1083
|
"naumu_post_message",
|
|
@@ -966,12 +1091,12 @@ function registerPostMessage(server2, client2) {
|
|
|
966
1091
|
idempotentHint: false,
|
|
967
1092
|
openWorldHint: false
|
|
968
1093
|
},
|
|
969
|
-
description: 'Post a message in a Naumu thread you participate in. Use it to reply to humans (or other bots) in a thread that pinged you. Plain text is accepted by default; for rendered @mentions pass a Tiptap JSON document with mention nodes (`{ type: "mention", attrs: { id, label } }`) and set contentFormat to "tiptap". @mentioning people loops them in without invoking @Naumu. To attach files call naumu_request_attachment_upload first, PUT the bytes to the returned uploadUrl, then pass the resulting attachmentIds here. The message needs either `content` or `attachmentIds`. Returns the created message JSON. To get a synthesised answer from @Naumu, use naumu_ask.',
|
|
970
|
-
inputSchema:
|
|
971
|
-
threadId:
|
|
972
|
-
content:
|
|
973
|
-
contentFormat:
|
|
974
|
-
attachmentIds:
|
|
1094
|
+
description: 'Post a message in a Naumu thread you participate in. Use it to reply to humans (or other bots) in a thread that pinged you. Plain text is accepted by default; for rendered @mentions pass a Tiptap JSON document with mention nodes (`{ type: "mention", attrs: { id, label } }`) and set contentFormat to "tiptap". @mentioning people loops them in (mention notifications, prompts) without invoking @Naumu. For a human, the mention `id` is their User id (from naumu_list_members or naumu_get_thread participantDetails) or their email - both work; for a bot/agent, use its identity id. To attach files call naumu_request_attachment_upload first, PUT the bytes to the returned uploadUrl, then pass the resulting attachmentIds here. The message needs either `content` or `attachmentIds`. Returns the created message JSON. To get a synthesised answer from @Naumu, use naumu_ask.',
|
|
1095
|
+
inputSchema: z20.object({
|
|
1096
|
+
threadId: z20.string().describe("The thread ID to post into. You must be a participant in this thread."),
|
|
1097
|
+
content: z20.string().optional().describe('Message body. Plain text by default; pass a Tiptap JSON document only if contentFormat is set to "tiptap". Optional when `attachmentIds` is provided.'),
|
|
1098
|
+
contentFormat: z20.enum(["tiptap", "text"]).optional().describe('Format of `content`. Defaults to "text". Use "tiptap" for rendered mentions/embeds, e.g. a doc containing `{ type: "mention", attrs: { id: userIdOrEmailOrIdentityId, label: displayName } }` (a human mention id is their User id or email; a bot mention id is its identity id).'),
|
|
1099
|
+
attachmentIds: z20.array(z20.string().min(1)).max(25).optional().describe("Attachment IDs from prior `naumu_request_attachment_upload` calls. Each must be a successfully-uploaded pending attachment in this graph (1-hour TTL). Up to 25 per message.")
|
|
975
1100
|
})
|
|
976
1101
|
},
|
|
977
1102
|
async ({ threadId, content, contentFormat, attachmentIds }) => {
|
|
@@ -997,7 +1122,7 @@ function registerPostMessage(server2, client2) {
|
|
|
997
1122
|
}
|
|
998
1123
|
|
|
999
1124
|
// ../mcp-core/src/tools/read-thread.ts
|
|
1000
|
-
import { z as
|
|
1125
|
+
import { z as z21 } from "zod";
|
|
1001
1126
|
function registerReadThread(server2, client2) {
|
|
1002
1127
|
server2.registerTool(
|
|
1003
1128
|
"naumu_read_thread",
|
|
@@ -1005,10 +1130,10 @@ function registerReadThread(server2, client2) {
|
|
|
1005
1130
|
title: "Read Thread",
|
|
1006
1131
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1007
1132
|
description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first; 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. Default page size 50, max 200.',
|
|
1008
|
-
inputSchema:
|
|
1009
|
-
threadId:
|
|
1010
|
-
before:
|
|
1011
|
-
limit:
|
|
1133
|
+
inputSchema: z21.object({
|
|
1134
|
+
threadId: z21.string().describe("The thread ID to read from."),
|
|
1135
|
+
before: z21.number().optional().describe("Unix timestamp in milliseconds. Returns messages strictly older than this. Omit for the newest page."),
|
|
1136
|
+
limit: z21.number().int().min(1).max(200).optional().describe("Page size, default 50, max 200.")
|
|
1012
1137
|
})
|
|
1013
1138
|
},
|
|
1014
1139
|
async ({ threadId, before, limit }) => {
|
|
@@ -1034,7 +1159,7 @@ function registerReadThread(server2, client2) {
|
|
|
1034
1159
|
}
|
|
1035
1160
|
|
|
1036
1161
|
// ../mcp-core/src/tools/whoami.ts
|
|
1037
|
-
import { z as
|
|
1162
|
+
import { z as z22 } from "zod";
|
|
1038
1163
|
function registerWhoami(server2, client2, allToolNames) {
|
|
1039
1164
|
server2.registerTool(
|
|
1040
1165
|
"naumu_whoami",
|
|
@@ -1042,7 +1167,7 @@ function registerWhoami(server2, client2, allToolNames) {
|
|
|
1042
1167
|
title: "Who Am I",
|
|
1043
1168
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1044
1169
|
description: 'Return who the calling key is plus the live MCP tool manifest, so you can bootstrap before the first real operation. A bot identity key returns its Identity row (id, graphId, name, instructions, allowedTools). A user API key returns `kind: "user"` with userId, name, and email - a person spans many graphs, so resolve a specific graph via naumu_list_graphs. No arguments. Always available regardless of the permission grid.',
|
|
1045
|
-
inputSchema:
|
|
1170
|
+
inputSchema: z22.object({})
|
|
1046
1171
|
},
|
|
1047
1172
|
async () => {
|
|
1048
1173
|
try {
|
|
@@ -1065,7 +1190,7 @@ function registerWhoami(server2, client2, allToolNames) {
|
|
|
1065
1190
|
}
|
|
1066
1191
|
|
|
1067
1192
|
// ../mcp-core/src/tools/list-threads.ts
|
|
1068
|
-
import { z as
|
|
1193
|
+
import { z as z23 } from "zod";
|
|
1069
1194
|
function sanitizeThreadParticipants(thread) {
|
|
1070
1195
|
if (!thread || typeof thread !== "object" || !("participantEmails" in thread)) {
|
|
1071
1196
|
return thread;
|
|
@@ -1080,10 +1205,10 @@ function registerListThreads(server2, client2) {
|
|
|
1080
1205
|
title: "List Threads",
|
|
1081
1206
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1082
1207
|
description: "List threads sorted by last activity (newest first), for self-discovery before deciding which to engage. With a user API key, pass `graphId` to list threads you can see in that space (resolve it via naumu_list_graphs). With a bot identity key, omit `graphId` to list threads in your own graph \u2014 each row carries an `isParticipant` flag (TRUE means you were explicitly invited and your replies fan out via webhook). Page back with `cursor` set to the oldest `lastActivityAt` from the previous page.",
|
|
1083
|
-
inputSchema:
|
|
1084
|
-
graphId:
|
|
1085
|
-
cursor:
|
|
1086
|
-
limit:
|
|
1208
|
+
inputSchema: z23.object({
|
|
1209
|
+
graphId: z23.string().optional().describe("Graph (space) ID. Required for user API keys; omit for bot identity keys (defaults to your own graph)."),
|
|
1210
|
+
cursor: z23.number().int().optional().describe("Unix timestamp ms \u2014 returns threads with `lastActivityAt` strictly older than this. Omit for the newest page."),
|
|
1211
|
+
limit: z23.number().int().min(1).max(200).optional().describe("Page size, default 50, max 200.")
|
|
1087
1212
|
})
|
|
1088
1213
|
},
|
|
1089
1214
|
async ({ graphId, cursor, limit }) => {
|
|
@@ -1118,8 +1243,53 @@ function registerListThreads(server2, client2) {
|
|
|
1118
1243
|
);
|
|
1119
1244
|
}
|
|
1120
1245
|
|
|
1246
|
+
// ../mcp-core/src/tools/list-topics.ts
|
|
1247
|
+
import { z as z24 } from "zod";
|
|
1248
|
+
function toFilingDestination(topic) {
|
|
1249
|
+
if (!topic || typeof topic !== "object") return null;
|
|
1250
|
+
const t = topic;
|
|
1251
|
+
if (typeof t.id !== "string" || typeof t.name !== "string") return null;
|
|
1252
|
+
return {
|
|
1253
|
+
id: t.id,
|
|
1254
|
+
name: t.name,
|
|
1255
|
+
...typeof t.visibilityMode === "string" ? { visibilityMode: t.visibilityMode } : {},
|
|
1256
|
+
...typeof t.memberCount === "number" ? { memberCount: t.memberCount } : {},
|
|
1257
|
+
...typeof t.openToWeb === "boolean" ? { openToWeb: t.openToWeb } : {},
|
|
1258
|
+
...typeof t.isMember === "boolean" ? { isMember: t.isMember } : {}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function registerListTopics(server2, client2) {
|
|
1262
|
+
server2.registerTool(
|
|
1263
|
+
"naumu_list_topics",
|
|
1264
|
+
{
|
|
1265
|
+
title: "List Topics",
|
|
1266
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1267
|
+
description: "List a space's real topics \u2014 the filing destinations you can hand to the `topicIds` param of naumu_delegate or naumu_ask when creating a thread. Filing a new thread into one or more topics makes it visible to those topics' members from birth (instead of the default private thread). Each row carries { id, name, visibilityMode, memberCount, openToWeb }; use the id values in `topicIds`. Topics have no separate description field. The virtual #misc bucket is not a real topic and is omitted; archived topics are omitted because they can't be tagged. Resolve `graphId` via naumu_list_graphs first.",
|
|
1268
|
+
inputSchema: z24.object({
|
|
1269
|
+
graphId: z24.string().describe("The space (graph) id to list topics for.")
|
|
1270
|
+
})
|
|
1271
|
+
},
|
|
1272
|
+
async ({ graphId }) => {
|
|
1273
|
+
try {
|
|
1274
|
+
const data = await client2.get(`/api/graphs/${graphId}/topics`);
|
|
1275
|
+
const rows = Array.isArray(data) ? data : [];
|
|
1276
|
+
const topics = rows.filter((t) => !(t && typeof t === "object" && t.archived === true)).map(toFilingDestination).filter((t) => t !== null);
|
|
1277
|
+
return {
|
|
1278
|
+
content: [{ type: "text", text: JSON.stringify(topics, null, 2) }]
|
|
1279
|
+
};
|
|
1280
|
+
} catch (err) {
|
|
1281
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1284
|
+
isError: true
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1121
1291
|
// ../mcp-core/src/tools/get-thread.ts
|
|
1122
|
-
import { z as
|
|
1292
|
+
import { z as z25 } from "zod";
|
|
1123
1293
|
function sanitizeThreadParticipants2(thread) {
|
|
1124
1294
|
if (!thread || typeof thread !== "object" || !("participantEmails" in thread)) {
|
|
1125
1295
|
return thread;
|
|
@@ -1134,8 +1304,8 @@ function registerGetThread(server2, client2) {
|
|
|
1134
1304
|
title: "Get Thread",
|
|
1135
1305
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1136
1306
|
description: "Fetch a single thread, including the human participant roster (`participantDetails` \u2014 userId, name, image) and bot roster (`identityParticipants` \u2014 id, name, isSystem). Use this when `naumu_list_threads` surfaced a candidate and you want to know exactly who is in it before posting. Pair with `naumu_read_thread` for message history.",
|
|
1137
|
-
inputSchema:
|
|
1138
|
-
threadId:
|
|
1307
|
+
inputSchema: z25.object({
|
|
1308
|
+
threadId: z25.string().describe("The thread ID to fetch.")
|
|
1139
1309
|
})
|
|
1140
1310
|
},
|
|
1141
1311
|
async ({ threadId }) => {
|
|
@@ -1157,7 +1327,7 @@ function registerGetThread(server2, client2) {
|
|
|
1157
1327
|
}
|
|
1158
1328
|
|
|
1159
1329
|
// ../mcp-core/src/tools/create-thread.ts
|
|
1160
|
-
import { z as
|
|
1330
|
+
import { z as z26 } from "zod";
|
|
1161
1331
|
function registerCreateThread(server2, client2) {
|
|
1162
1332
|
server2.registerTool(
|
|
1163
1333
|
"naumu_create_thread",
|
|
@@ -1171,32 +1341,36 @@ function registerCreateThread(server2, client2) {
|
|
|
1171
1341
|
idempotentHint: false,
|
|
1172
1342
|
openWorldHint: false
|
|
1173
1343
|
},
|
|
1174
|
-
description: "Start a new conversation in a space. You are auto-attached as a participant, and the thread's formal creator is your primary owner (the user who registered you), so it shows in their sidebar. Optional `participants` adds humans (by userId) and other bots (by identityId) at creation. Optional `initialMessage` opens the conversation as your first message. Tagging people loops them in without invoking @Naumu; only an explicit @Naumu mention, or naumu_ask, brings the agent in. Returns the created thread (including its id) so you can follow up with naumu_post_message.",
|
|
1175
|
-
inputSchema:
|
|
1176
|
-
title:
|
|
1177
|
-
participants:
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
type:
|
|
1181
|
-
userId:
|
|
1344
|
+
description: "Start a new conversation in a space. You are auto-attached as a participant, and the thread's formal creator is your primary owner (the user who registered you), so it shows in their sidebar. Optional `participants` adds humans (by userId) and other bots (by identityId) at creation. Optional `initialMessage` opens the conversation as your first message. Optional `topicIds` files the new thread into one or more topics (see naumu_list_topics for ids): it becomes visible to those topics' members from birth instead of staying a private thread between you and your owner. Filing is creation-only and only ever widens - it never removes the thread from a topic later. Tagging people loops them in without invoking @Naumu; only an explicit @Naumu mention, or naumu_ask, brings the agent in. Returns the created thread (including its id) so you can follow up with naumu_post_message.",
|
|
1345
|
+
inputSchema: z26.object({
|
|
1346
|
+
title: z26.string().min(1).max(200).optional().describe('Thread title shown in the sidebar. If omitted, Naumu generates a default like "Conversation YYYY-MM-DD".'),
|
|
1347
|
+
participants: z26.array(
|
|
1348
|
+
z26.discriminatedUnion("type", [
|
|
1349
|
+
z26.object({
|
|
1350
|
+
type: z26.literal("user"),
|
|
1351
|
+
userId: z26.string().min(1).describe("User UUID \u2014 get these from `naumu_get_thread`/`naumu_read_thread` participant rosters or webhook payloads.")
|
|
1182
1352
|
}),
|
|
1183
|
-
|
|
1184
|
-
type:
|
|
1185
|
-
identityId:
|
|
1353
|
+
z26.object({
|
|
1354
|
+
type: z26.literal("identity"),
|
|
1355
|
+
identityId: z26.string().min(1).describe("Identity id (`identity-\u2026` or `id-\u2026`). Other bots in the same graph can be co-attached to multi-bot threads.")
|
|
1186
1356
|
})
|
|
1187
1357
|
])
|
|
1188
1358
|
).max(32).optional().describe("Up to 32 humans and/or other bots to attach at creation. Your primary owner is added automatically \u2014 you do NOT need to list them here."),
|
|
1189
|
-
initialMessage:
|
|
1190
|
-
visibility:
|
|
1359
|
+
initialMessage: z26.string().min(1).max(32e3).optional().describe("Markdown body for the first message. Authored by you (the bot), so it appears in the thread under your name."),
|
|
1360
|
+
visibility: z26.enum(["restricted", "internal", "open"]).optional().describe("`restricted` (invite-only, default) hides from non-participants. `internal` is visible to space members. `open` is visible to anyone who can see the space."),
|
|
1361
|
+
topicIds: z26.array(z26.string()).max(8).optional().describe(
|
|
1362
|
+
"Topic ids (from naumu_list_topics) to file the NEW thread into, making it visible to those topics' members from birth. Creation-only and only ever widens - it never removes the thread from a topic later. Omit to keep the default private thread between you and your owner."
|
|
1363
|
+
)
|
|
1191
1364
|
})
|
|
1192
1365
|
},
|
|
1193
|
-
async ({ title, participants, initialMessage, visibility }) => {
|
|
1366
|
+
async ({ title, participants, initialMessage, visibility, topicIds }) => {
|
|
1194
1367
|
try {
|
|
1195
1368
|
const body = {};
|
|
1196
1369
|
if (title !== void 0) body.title = title;
|
|
1197
1370
|
if (participants !== void 0) body.participants = participants;
|
|
1198
1371
|
if (initialMessage !== void 0) body.initialMessage = initialMessage;
|
|
1199
1372
|
if (visibility !== void 0) body.visibility = visibility;
|
|
1373
|
+
if (topicIds !== void 0) body.topicIds = topicIds;
|
|
1200
1374
|
const data = await client2.post("/api/identities/me/threads", body);
|
|
1201
1375
|
return {
|
|
1202
1376
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
@@ -1213,7 +1387,7 @@ function registerCreateThread(server2, client2) {
|
|
|
1213
1387
|
}
|
|
1214
1388
|
|
|
1215
1389
|
// ../mcp-core/src/tools/request-attachment-upload.ts
|
|
1216
|
-
import { z as
|
|
1390
|
+
import { z as z27 } from "zod";
|
|
1217
1391
|
function registerRequestAttachmentUpload(server2, client2) {
|
|
1218
1392
|
server2.registerTool(
|
|
1219
1393
|
"naumu_request_attachment_upload",
|
|
@@ -1221,12 +1395,12 @@ function registerRequestAttachmentUpload(server2, client2) {
|
|
|
1221
1395
|
title: "Request Attachment Upload",
|
|
1222
1396
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1223
1397
|
description: 'Request a presigned S3 upload URL to attach a file to a message; use when you want to deliver generated content as a file (a markdown report, a PDF, an image, an audio recording, a video). Same flow Naumu users use for file uploads: get a signed URL, PUT the bytes to it directly, then call `naumu_post_message` with the returned `attachmentId` in `attachmentIds`. Per-MIME size caps apply (typically 50MB umbrella, 10MB for agent-readable types).\n\nReturns `{ attachmentId, uploadUrl, method, requiredHeaders, expiresAt }`. Use these EXACTLY:\n\u2022 `method` is "PUT".\n\u2022 Send every header in `requiredHeaders` (Content-Type matters for S3 signature validation).\n\u2022 Do NOT add an Authorization header - the URL itself is the auth.\n\u2022 Do NOT log `uploadUrl` - it is a bearer capability for the duration of the TTL.\n\u2022 `expiresAt` is a Unix-ms timestamp; the pending attachment vanishes at that moment whether or not you uploaded. Call `naumu_post_message` with the attachmentId before then or the upload orphans.\n\nServer-side checks at post time enforce that the attachment was uploaded by you, in this graph, for this thread - you cannot reuse an upload across threads.',
|
|
1224
|
-
inputSchema:
|
|
1225
|
-
threadId:
|
|
1226
|
-
fileName:
|
|
1227
|
-
fileType:
|
|
1228
|
-
fileSize:
|
|
1229
|
-
audioDurationSec:
|
|
1398
|
+
inputSchema: z27.object({
|
|
1399
|
+
threadId: z27.string().describe("Thread the attachment will land in. You must be a participant. The pending attachment is keyed to this thread - you cannot reuse it for a different one."),
|
|
1400
|
+
fileName: z27.string().min(1).describe("Original filename (with extension). Used as the display name in the message and for the S3 object suffix. Special characters are sanitized server-side."),
|
|
1401
|
+
fileType: z27.string().min(1).describe("MIME type, e.g. `application/pdf`, `image/png`, `text/markdown`, `audio/mpeg`, `video/mp4`. The S3 PUT will enforce this Content-Type."),
|
|
1402
|
+
fileSize: z27.number().int().positive().describe("File size in bytes. Validated against per-MIME caps before the URL is issued - exceeding the cap returns a 400."),
|
|
1403
|
+
audioDurationSec: z27.number().positive().optional().describe("For audio attachments, duration in seconds. Validated against the audio recording cap (currently 8 hours).")
|
|
1230
1404
|
})
|
|
1231
1405
|
},
|
|
1232
1406
|
async ({ threadId, fileName, fileType, fileSize, audioDurationSec }) => {
|
|
@@ -1256,7 +1430,7 @@ function registerRequestAttachmentUpload(server2, client2) {
|
|
|
1256
1430
|
}
|
|
1257
1431
|
|
|
1258
1432
|
// ../mcp-core/src/tools/add-reaction.ts
|
|
1259
|
-
import { z as
|
|
1433
|
+
import { z as z28 } from "zod";
|
|
1260
1434
|
function registerAddReaction(server2, client2) {
|
|
1261
1435
|
server2.registerTool(
|
|
1262
1436
|
"naumu_add_reaction",
|
|
@@ -1264,10 +1438,10 @@ function registerAddReaction(server2, client2) {
|
|
|
1264
1438
|
title: "Add Reaction",
|
|
1265
1439
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1266
1440
|
description: 'Add an emoji reaction to a message in a thread you are participating in; use for lightweight acknowledgement instead of posting a message. 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\u2022 React (no message) for lightweight acknowledgement (\u{1F440}, \u2705, \u{1F44D}), appreciation (\u2764\uFE0F, \u{1F64C}), laughter (\u{1F602}), or "I saw this".\n\u2022 Post a message for direct questions, clarification, important corrections, or final results - situations where words are required.\n\u2022 For long tasks: react \u{1F440} 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\u2022 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.',
|
|
1267
|
-
inputSchema:
|
|
1268
|
-
threadId:
|
|
1269
|
-
messageId:
|
|
1270
|
-
emoji:
|
|
1441
|
+
inputSchema: z28.object({
|
|
1442
|
+
threadId: z28.string().describe("Thread containing the message. You must be a participant."),
|
|
1443
|
+
messageId: z28.string().describe("The message to react to."),
|
|
1444
|
+
emoji: z28.string().min(1).describe('Emoji character (e.g. "\u{1F440}", "\u2705", "\u2764\uFE0F"). Custom-emoji shortcodes are NOT supported here - pass a real Unicode emoji.')
|
|
1271
1445
|
})
|
|
1272
1446
|
},
|
|
1273
1447
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1291,7 +1465,7 @@ function registerAddReaction(server2, client2) {
|
|
|
1291
1465
|
}
|
|
1292
1466
|
|
|
1293
1467
|
// ../mcp-core/src/tools/remove-reaction.ts
|
|
1294
|
-
import { z as
|
|
1468
|
+
import { z as z29 } from "zod";
|
|
1295
1469
|
function registerRemoveReaction(server2, client2) {
|
|
1296
1470
|
server2.registerTool(
|
|
1297
1471
|
"naumu_remove_reaction",
|
|
@@ -1299,10 +1473,10 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1299
1473
|
title: "Remove Reaction",
|
|
1300
1474
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1301
1475
|
description: "Remove your own emoji reaction from a message; use to walk back an acknowledgement you previously added. Idempotent - calling on a reaction you never added is a no-op. Pair with `naumu_add_reaction` (e.g. you reacted \u{1F440} to start a task and want to clear it after a final result message lands). Returns `{ ok, messageId, emoji, alreadyExisted, reactionCount, reactions }` - `alreadyExisted: false` means there was nothing to remove and the call was a no-op.",
|
|
1302
|
-
inputSchema:
|
|
1303
|
-
threadId:
|
|
1304
|
-
messageId:
|
|
1305
|
-
emoji:
|
|
1476
|
+
inputSchema: z29.object({
|
|
1477
|
+
threadId: z29.string().describe("Thread containing the message. You must be a participant."),
|
|
1478
|
+
messageId: z29.string().describe("The message to remove your reaction from."),
|
|
1479
|
+
emoji: z29.string().min(1).describe("Emoji character to remove (must match what you originally reacted with).")
|
|
1306
1480
|
})
|
|
1307
1481
|
},
|
|
1308
1482
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1326,7 +1500,7 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1326
1500
|
}
|
|
1327
1501
|
|
|
1328
1502
|
// ../mcp-core/src/tools/naumu-typing.ts
|
|
1329
|
-
import { z as
|
|
1503
|
+
import { z as z30 } from "zod";
|
|
1330
1504
|
function registerNaumuTyping(server2, client2) {
|
|
1331
1505
|
server2.registerTool(
|
|
1332
1506
|
"naumu_typing",
|
|
@@ -1337,9 +1511,9 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1337
1511
|
// repeating the same state is a no-op renew, so idempotent.
|
|
1338
1512
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1339
1513
|
description: 'Show or hide your "is typing\u2026" pill in a thread; use to signal that you are composing a reply. Call with `state: "start"` the moment you decide to compose a reply (before any LLM call), and the server holds the pill alive - re-broadcasting on a short interval - until you stop, post a message, or the lease cap (~5 min) fires. You do NOT need to refresh on a timer; that\'s the lease\'s job.\n\nThe pill clears automatically when:\n\u2022 you call this tool with `state: "stop"`\n\u2022 you call `naumu_post_message` for the same thread (cleared on commit)\n\u2022 the lease cap expires\n\nUse `start` whenever you start work, even if you might end up not replying - call `stop` if you decide NOT to post. Calling `start` while a lease is already active renews it (resets the cap), so a long-running run can call `start` again as a heartbeat without breaking the indicator. You must be a participant of the thread.',
|
|
1340
|
-
inputSchema:
|
|
1341
|
-
threadId:
|
|
1342
|
-
state:
|
|
1514
|
+
inputSchema: z30.object({
|
|
1515
|
+
threadId: z30.string().describe("The thread ID to set typing in. You must be a participant."),
|
|
1516
|
+
state: z30.enum(["start", "stop"]).describe('"start" acquires/renews the lease; "stop" ends it and clears the pill immediately.')
|
|
1343
1517
|
})
|
|
1344
1518
|
},
|
|
1345
1519
|
async ({ threadId, state }) => {
|
|
@@ -1360,7 +1534,7 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1360
1534
|
}
|
|
1361
1535
|
|
|
1362
1536
|
// ../mcp-core/src/tools/note-read.ts
|
|
1363
|
-
import { z as
|
|
1537
|
+
import { z as z31 } from "zod";
|
|
1364
1538
|
function registerNoteRead(server2, client2) {
|
|
1365
1539
|
server2.registerTool(
|
|
1366
1540
|
"naumu_note_read",
|
|
@@ -1368,8 +1542,8 @@ function registerNoteRead(server2, client2) {
|
|
|
1368
1542
|
title: "Read Note",
|
|
1369
1543
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1370
1544
|
description: "Read the current contents of a note as markdown; use before editing so you know what you're working with. `naumu_note_find_replace` and the section-based tools (`naumu_note_insert`, `naumu_note_replace_section`, `naumu_note_delete_section`) anchor on text/headings present in the live doc.",
|
|
1371
|
-
inputSchema:
|
|
1372
|
-
noteId:
|
|
1545
|
+
inputSchema: z31.object({
|
|
1546
|
+
noteId: z31.string().describe("The note (Thought) ID")
|
|
1373
1547
|
})
|
|
1374
1548
|
},
|
|
1375
1549
|
async ({ noteId }) => {
|
|
@@ -1382,7 +1556,7 @@ function registerNoteRead(server2, client2) {
|
|
|
1382
1556
|
}
|
|
1383
1557
|
|
|
1384
1558
|
// ../mcp-core/src/tools/note-append.ts
|
|
1385
|
-
import { z as
|
|
1559
|
+
import { z as z32 } from "zod";
|
|
1386
1560
|
function registerNoteAppend(server2, client2) {
|
|
1387
1561
|
server2.registerTool(
|
|
1388
1562
|
"naumu_note_append",
|
|
@@ -1390,9 +1564,9 @@ function registerNoteAppend(server2, client2) {
|
|
|
1390
1564
|
title: "Append to Note",
|
|
1391
1565
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1392
1566
|
description: "Append markdown blocks to the end of a note; use for additive note writing that never touches existing content. Other participants see your colored cursor while the write lands. Markdown supports headings (1-3), bold/italic/code, lists, blockquotes, code blocks, links, and tables.",
|
|
1393
|
-
inputSchema:
|
|
1394
|
-
noteId:
|
|
1395
|
-
markdown:
|
|
1567
|
+
inputSchema: z32.object({
|
|
1568
|
+
noteId: z32.string().describe("The note (Thought) ID to append to"),
|
|
1569
|
+
markdown: z32.string().min(1).describe("Markdown content to append at the end of the note")
|
|
1396
1570
|
})
|
|
1397
1571
|
},
|
|
1398
1572
|
async ({ noteId, markdown }) => {
|
|
@@ -1405,7 +1579,7 @@ function registerNoteAppend(server2, client2) {
|
|
|
1405
1579
|
}
|
|
1406
1580
|
|
|
1407
1581
|
// ../mcp-core/src/tools/note-insert.ts
|
|
1408
|
-
import { z as
|
|
1582
|
+
import { z as z33 } from "zod";
|
|
1409
1583
|
function registerNoteInsert(server2, client2) {
|
|
1410
1584
|
server2.registerTool(
|
|
1411
1585
|
"naumu_note_insert",
|
|
@@ -1413,10 +1587,10 @@ function registerNoteInsert(server2, client2) {
|
|
|
1413
1587
|
title: "Insert After Heading",
|
|
1414
1588
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1415
1589
|
description: "Insert markdown content into a note immediately after a named section; use to add content under a specific heading without rewriting it. The section ends at the next heading of equal-or-higher level (or end of doc). 404 if no heading matches `headingText` exactly - call `naumu_note_read` first to see the live structure.",
|
|
1416
|
-
inputSchema:
|
|
1417
|
-
noteId:
|
|
1418
|
-
headingText:
|
|
1419
|
-
markdown:
|
|
1590
|
+
inputSchema: z33.object({
|
|
1591
|
+
noteId: z33.string().describe("The note (Thought) ID"),
|
|
1592
|
+
headingText: z33.string().min(1).describe("Exact text of the heading whose section the new content follows"),
|
|
1593
|
+
markdown: z33.string().min(1).describe("Markdown content to insert at the end of that section")
|
|
1420
1594
|
})
|
|
1421
1595
|
},
|
|
1422
1596
|
async ({ noteId, headingText, markdown }) => {
|
|
@@ -1432,7 +1606,7 @@ function registerNoteInsert(server2, client2) {
|
|
|
1432
1606
|
}
|
|
1433
1607
|
|
|
1434
1608
|
// ../mcp-core/src/tools/note-replace-section.ts
|
|
1435
|
-
import { z as
|
|
1609
|
+
import { z as z34 } from "zod";
|
|
1436
1610
|
function registerNoteReplaceSection(server2, client2) {
|
|
1437
1611
|
server2.registerTool(
|
|
1438
1612
|
"naumu_note_replace_section",
|
|
@@ -1440,11 +1614,11 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1440
1614
|
title: "Replace Section",
|
|
1441
1615
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1442
1616
|
description: "Replace the body under a named heading with new markdown; use to rewrite one section of a note while leaving the rest intact. By default the heading row itself is preserved (set `keepHeading: false` to drop it too). 404 if no heading matches.",
|
|
1443
|
-
inputSchema:
|
|
1444
|
-
noteId:
|
|
1445
|
-
headingText:
|
|
1446
|
-
markdown:
|
|
1447
|
-
keepHeading:
|
|
1617
|
+
inputSchema: z34.object({
|
|
1618
|
+
noteId: z34.string().describe("The note (Thought) ID"),
|
|
1619
|
+
headingText: z34.string().min(1).describe("Exact text of the heading anchoring the section"),
|
|
1620
|
+
markdown: z34.string().describe("Replacement markdown for the section body"),
|
|
1621
|
+
keepHeading: z34.boolean().optional().describe("Whether to keep the heading row itself. Default true.")
|
|
1448
1622
|
})
|
|
1449
1623
|
},
|
|
1450
1624
|
async ({ noteId, headingText, markdown, keepHeading }) => {
|
|
@@ -1461,7 +1635,7 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1461
1635
|
}
|
|
1462
1636
|
|
|
1463
1637
|
// ../mcp-core/src/tools/note-delete-section.ts
|
|
1464
|
-
import { z as
|
|
1638
|
+
import { z as z35 } from "zod";
|
|
1465
1639
|
function registerNoteDeleteSection(server2, client2) {
|
|
1466
1640
|
server2.registerTool(
|
|
1467
1641
|
"naumu_note_delete_section",
|
|
@@ -1469,9 +1643,9 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1469
1643
|
title: "Delete Section",
|
|
1470
1644
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1471
1645
|
description: "\u26A0 DESTRUCTIVE: remove a heading row plus its body (down to the next heading of equal-or-higher level); ONLY use when the user explicitly asks to drop a section. Anything inside that section is gone - there is no per-call undo. If you're unsure which heading they meant, call `naumu_note_read` first to see the current structure. Returns 404 if `headingText` does not exactly match any live heading.",
|
|
1472
|
-
inputSchema:
|
|
1473
|
-
noteId:
|
|
1474
|
-
headingText:
|
|
1646
|
+
inputSchema: z35.object({
|
|
1647
|
+
noteId: z35.string().describe("The note (Thought) ID"),
|
|
1648
|
+
headingText: z35.string().min(1).describe("Exact text of the heading whose section will be deleted")
|
|
1475
1649
|
})
|
|
1476
1650
|
},
|
|
1477
1651
|
async ({ noteId, headingText }) => {
|
|
@@ -1486,7 +1660,7 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1486
1660
|
}
|
|
1487
1661
|
|
|
1488
1662
|
// ../mcp-core/src/tools/note-replace.ts
|
|
1489
|
-
import { z as
|
|
1663
|
+
import { z as z36 } from "zod";
|
|
1490
1664
|
function registerNoteReplace(server2, client2) {
|
|
1491
1665
|
server2.registerTool(
|
|
1492
1666
|
"naumu_note_replace",
|
|
@@ -1494,9 +1668,9 @@ function registerNoteReplace(server2, client2) {
|
|
|
1494
1668
|
title: "Replace Note",
|
|
1495
1669
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1496
1670
|
description: "\u26A0 DESTRUCTIVE: replace the entire note content with new markdown; ONLY use when the user explicitly asks to rewrite/replace the whole note. Any concurrent human edits made during the call are silently overwritten. For additive work prefer `naumu_note_append`. For section-level edits use `naumu_note_replace_section`. For inline tweaks use `naumu_note_find_replace`. Read with `naumu_note_read` first if you weren't the last writer.",
|
|
1497
|
-
inputSchema:
|
|
1498
|
-
noteId:
|
|
1499
|
-
markdown:
|
|
1671
|
+
inputSchema: z36.object({
|
|
1672
|
+
noteId: z36.string().describe("The note (Thought) ID"),
|
|
1673
|
+
markdown: z36.string().describe("New markdown content for the entire note")
|
|
1500
1674
|
})
|
|
1501
1675
|
},
|
|
1502
1676
|
async ({ noteId, markdown }) => {
|
|
@@ -1509,7 +1683,7 @@ function registerNoteReplace(server2, client2) {
|
|
|
1509
1683
|
}
|
|
1510
1684
|
|
|
1511
1685
|
// ../mcp-core/src/tools/note-find-replace.ts
|
|
1512
|
-
import { z as
|
|
1686
|
+
import { z as z37 } from "zod";
|
|
1513
1687
|
function registerNoteFindReplace(server2, client2) {
|
|
1514
1688
|
server2.registerTool(
|
|
1515
1689
|
"naumu_note_find_replace",
|
|
@@ -1517,11 +1691,11 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
1517
1691
|
title: "Find/Replace in Note",
|
|
1518
1692
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1519
1693
|
description: "Literal find/replace within a note's text content; use for mid-paragraph tweaks the section-based tools can't target. Marks (bold, italic, code, etc.) are preserved on the surrounding text. \u26A0 The match is literal-substring across every text leaf in the doc; an overly generic `find` (e.g. \" a \") can rewrite the doc unrecognizably. Pick a phrase distinctive enough to land where you mean. By default replaces every occurrence; set `all: false` for first-only. Returns `{ replacements }` so you can sanity-check the count.",
|
|
1520
|
-
inputSchema:
|
|
1521
|
-
noteId:
|
|
1522
|
-
find:
|
|
1523
|
-
replace:
|
|
1524
|
-
all:
|
|
1694
|
+
inputSchema: z37.object({
|
|
1695
|
+
noteId: z37.string().describe("The note (Thought) ID"),
|
|
1696
|
+
find: z37.string().min(1).describe("Substring to search for. Literal - no regex."),
|
|
1697
|
+
replace: z37.string().describe("Replacement string. May be empty to delete the match."),
|
|
1698
|
+
all: z37.boolean().optional().describe("Replace all occurrences (default true). Pass false to replace only the first.")
|
|
1525
1699
|
})
|
|
1526
1700
|
},
|
|
1527
1701
|
async ({ noteId, find, replace, all }) => {
|
|
@@ -1538,21 +1712,23 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
1538
1712
|
}
|
|
1539
1713
|
|
|
1540
1714
|
// ../mcp-core/src/tools/create-note.ts
|
|
1541
|
-
import { z as
|
|
1715
|
+
import { z as z38 } from "zod";
|
|
1542
1716
|
function registerCreateNote(server2, client2) {
|
|
1543
1717
|
server2.registerTool(
|
|
1544
1718
|
"naumu_create_note",
|
|
1545
1719
|
{
|
|
1546
1720
|
title: "Create Note",
|
|
1547
1721
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1548
|
-
description: "Create a new empty note in a graph; use when you need a fresh note to write into. Returns the new note row including its `id` - pass that id to `naumu_note_append` / `naumu_note_replace` to fill in the content. Bots can only create notes in their own graph.",
|
|
1549
|
-
inputSchema:
|
|
1550
|
-
graphId:
|
|
1551
|
-
title:
|
|
1722
|
+
description: "Create a new empty note in a graph; use when you need a fresh note to write into. Returns the new note row including its `id` - pass that id to `naumu_note_append` / `naumu_note_replace` to fill in the content. 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 \u2014 a bot passing `topicIds` is rejected (bots hold no topic membership); use `sharedWithSpace` instead.",
|
|
1723
|
+
inputSchema: z38.object({
|
|
1724
|
+
graphId: z38.string().describe("The graph ID to create the note in"),
|
|
1725
|
+
title: z38.string().optional().describe("Optional title for the note"),
|
|
1726
|
+
sharedWithSpace: z38.boolean().optional().describe("Share the note with everyone in the space. Omit (or false) to keep it private to its participants."),
|
|
1727
|
+
topicIds: z38.array(z38.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.")
|
|
1552
1728
|
})
|
|
1553
1729
|
},
|
|
1554
|
-
async ({ graphId, title }) => {
|
|
1555
|
-
const data = await client2.post("/api/notes", { graphId, title });
|
|
1730
|
+
async ({ graphId, title, sharedWithSpace, topicIds }) => {
|
|
1731
|
+
const data = await client2.post("/api/notes", { graphId, title, sharedWithSpace, topicIds });
|
|
1556
1732
|
return {
|
|
1557
1733
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1558
1734
|
};
|
|
@@ -1561,7 +1737,7 @@ function registerCreateNote(server2, client2) {
|
|
|
1561
1737
|
}
|
|
1562
1738
|
|
|
1563
1739
|
// ../mcp-core/src/tools/list-schema-violations.ts
|
|
1564
|
-
import { z as
|
|
1740
|
+
import { z as z39 } from "zod";
|
|
1565
1741
|
var DEFAULT_EXAMPLE_LIMIT = 5;
|
|
1566
1742
|
var rowsForKind = (violations, kind) => {
|
|
1567
1743
|
const rows = [];
|
|
@@ -1587,12 +1763,12 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
1587
1763
|
title: "List Schema Violations",
|
|
1588
1764
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1589
1765
|
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.",
|
|
1590
|
-
inputSchema:
|
|
1591
|
-
graphId:
|
|
1592
|
-
kind:
|
|
1766
|
+
inputSchema: z39.object({
|
|
1767
|
+
graphId: z39.string().describe("The graph ID"),
|
|
1768
|
+
kind: z39.string().optional().describe(
|
|
1593
1769
|
'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.'
|
|
1594
1770
|
),
|
|
1595
|
-
limit:
|
|
1771
|
+
limit: z39.number().int().min(1).optional().describe(
|
|
1596
1772
|
"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)."
|
|
1597
1773
|
)
|
|
1598
1774
|
})
|
|
@@ -1644,7 +1820,7 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
1644
1820
|
}
|
|
1645
1821
|
|
|
1646
1822
|
// ../mcp-core/src/tools/list-dense-nodes.ts
|
|
1647
|
-
import { z as
|
|
1823
|
+
import { z as z40 } from "zod";
|
|
1648
1824
|
function registerListDenseNodes(server2, client2) {
|
|
1649
1825
|
server2.registerTool(
|
|
1650
1826
|
"naumu_list_dense_nodes",
|
|
@@ -1652,10 +1828,10 @@ function registerListDenseNodes(server2, client2) {
|
|
|
1652
1828
|
title: "List Dense Nodes",
|
|
1653
1829
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1654
1830
|
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.',
|
|
1655
|
-
inputSchema:
|
|
1656
|
-
graphId:
|
|
1657
|
-
minConnections:
|
|
1658
|
-
nodeTypes:
|
|
1831
|
+
inputSchema: z40.object({
|
|
1832
|
+
graphId: z40.string().describe("The graph ID"),
|
|
1833
|
+
minConnections: z40.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."),
|
|
1834
|
+
nodeTypes: z40.array(z40.string()).optional().describe("Optional list of node types to restrict the scan to.")
|
|
1659
1835
|
})
|
|
1660
1836
|
},
|
|
1661
1837
|
async ({ graphId, minConnections, nodeTypes }) => {
|
|
@@ -1673,7 +1849,7 @@ function registerListDenseNodes(server2, client2) {
|
|
|
1673
1849
|
}
|
|
1674
1850
|
|
|
1675
1851
|
// ../mcp-core/src/tools/list-node-connections.ts
|
|
1676
|
-
import { z as
|
|
1852
|
+
import { z as z41 } from "zod";
|
|
1677
1853
|
function registerListNodeConnections(server2, client2) {
|
|
1678
1854
|
server2.registerTool(
|
|
1679
1855
|
"naumu_list_node_connections",
|
|
@@ -1681,11 +1857,11 @@ function registerListNodeConnections(server2, client2) {
|
|
|
1681
1857
|
title: "List Node Connections",
|
|
1682
1858
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1683
1859
|
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}}] }`.',
|
|
1684
|
-
inputSchema:
|
|
1685
|
-
graphId:
|
|
1686
|
-
nodeId:
|
|
1687
|
-
edgeType:
|
|
1688
|
-
direction:
|
|
1860
|
+
inputSchema: z41.object({
|
|
1861
|
+
graphId: z41.string().describe("The graph ID"),
|
|
1862
|
+
nodeId: z41.string().describe("The node ID to inspect"),
|
|
1863
|
+
edgeType: z41.string().optional().describe('Restrict to a specific relation label (e.g. "ASSOCIATED_WITH"). Case-insensitive; non-alphanum chars are normalized.'),
|
|
1864
|
+
direction: z41.enum(["in", "out", "both"]).optional().describe('Edge direction filter - "in" (incoming), "out" (outgoing), "both" (default).')
|
|
1689
1865
|
})
|
|
1690
1866
|
},
|
|
1691
1867
|
async ({ graphId, nodeId, edgeType, direction }) => {
|
|
@@ -1703,7 +1879,7 @@ function registerListNodeConnections(server2, client2) {
|
|
|
1703
1879
|
}
|
|
1704
1880
|
|
|
1705
1881
|
// ../mcp-core/src/tools/reparent.ts
|
|
1706
|
-
import { z as
|
|
1882
|
+
import { z as z42 } from "zod";
|
|
1707
1883
|
function registerReparent(server2, client2) {
|
|
1708
1884
|
server2.registerTool(
|
|
1709
1885
|
"naumu_reparent",
|
|
@@ -1711,11 +1887,11 @@ function registerReparent(server2, client2) {
|
|
|
1711
1887
|
title: "Reparent Node",
|
|
1712
1888
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1713
1889
|
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"}`.',
|
|
1714
|
-
inputSchema:
|
|
1715
|
-
graphId:
|
|
1716
|
-
nodeId:
|
|
1717
|
-
newParentId:
|
|
1718
|
-
newRelation:
|
|
1890
|
+
inputSchema: z42.object({
|
|
1891
|
+
graphId: z42.string().describe("The graph ID"),
|
|
1892
|
+
nodeId: z42.string().describe("The child node to reparent"),
|
|
1893
|
+
newParentId: z42.string().describe("The new parent node id"),
|
|
1894
|
+
newRelation: z42.string().describe('The new parent edge relation label (e.g. "PART_OF"). Must be valid per the schema for (child.type, relation, parent.type).')
|
|
1719
1895
|
})
|
|
1720
1896
|
},
|
|
1721
1897
|
async ({ graphId, nodeId, newParentId, newRelation }) => {
|
|
@@ -1731,7 +1907,7 @@ function registerReparent(server2, client2) {
|
|
|
1731
1907
|
}
|
|
1732
1908
|
|
|
1733
1909
|
// ../mcp-core/src/tools/batch-reparent.ts
|
|
1734
|
-
import { z as
|
|
1910
|
+
import { z as z43 } from "zod";
|
|
1735
1911
|
function registerBatchReparent(server2, client2) {
|
|
1736
1912
|
server2.registerTool(
|
|
1737
1913
|
"naumu_batch_reparent",
|
|
@@ -1739,11 +1915,11 @@ function registerBatchReparent(server2, client2) {
|
|
|
1739
1915
|
title: "Batch Reparent Nodes",
|
|
1740
1916
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1741
1917
|
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?}]`.',
|
|
1742
|
-
inputSchema:
|
|
1743
|
-
graphId:
|
|
1744
|
-
newParentId:
|
|
1745
|
-
newRelation:
|
|
1746
|
-
nodeIds:
|
|
1918
|
+
inputSchema: z43.object({
|
|
1919
|
+
graphId: z43.string().describe("The graph ID"),
|
|
1920
|
+
newParentId: z43.string().describe("Parent node id every nodeId in the batch will be parented to"),
|
|
1921
|
+
newRelation: z43.string().describe("Parent edge relation label (must be valid per schema for child.type \u2192 parent.type)"),
|
|
1922
|
+
nodeIds: z43.array(z43.string()).min(1).max(25).describe("1\u201325 child node ids to reparent under `newParentId`")
|
|
1747
1923
|
})
|
|
1748
1924
|
},
|
|
1749
1925
|
async ({ graphId, newParentId, newRelation, nodeIds }) => {
|
|
@@ -1760,7 +1936,7 @@ function registerBatchReparent(server2, client2) {
|
|
|
1760
1936
|
}
|
|
1761
1937
|
|
|
1762
1938
|
// ../mcp-core/src/tools/chatgpt-search.ts
|
|
1763
|
-
import { z as
|
|
1939
|
+
import { z as z44 } from "zod";
|
|
1764
1940
|
|
|
1765
1941
|
// ../mcp-core/src/public-origin.ts
|
|
1766
1942
|
function publicOrigin() {
|
|
@@ -1814,8 +1990,8 @@ function registerChatgptSearch(server2, client2) {
|
|
|
1814
1990
|
title: "Search",
|
|
1815
1991
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1816
1992
|
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.)",
|
|
1817
|
-
inputSchema:
|
|
1818
|
-
query:
|
|
1993
|
+
inputSchema: z44.object({
|
|
1994
|
+
query: z44.string().describe('Search query text. Mix synonyms and exact tokens freely (e.g. "auth login SSO 2fa").')
|
|
1819
1995
|
})
|
|
1820
1996
|
},
|
|
1821
1997
|
async ({ query }) => {
|
|
@@ -1847,7 +2023,7 @@ function registerChatgptSearch(server2, client2) {
|
|
|
1847
2023
|
}
|
|
1848
2024
|
|
|
1849
2025
|
// ../mcp-core/src/tools/chatgpt-fetch.ts
|
|
1850
|
-
import { z as
|
|
2026
|
+
import { z as z45 } from "zod";
|
|
1851
2027
|
var NON_ATTRIBUTE_PROPS = /* @__PURE__ */ new Set([
|
|
1852
2028
|
"id",
|
|
1853
2029
|
"label",
|
|
@@ -1912,8 +2088,8 @@ function registerChatgptFetch(server2, client2) {
|
|
|
1912
2088
|
title: "Fetch",
|
|
1913
2089
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1914
2090
|
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.",
|
|
1915
|
-
inputSchema:
|
|
1916
|
-
id:
|
|
2091
|
+
inputSchema: z45.object({
|
|
2092
|
+
id: z45.string().describe("A resource id from a previous `search` result, in the form `<graphId>:<nodeId>`.")
|
|
1917
2093
|
})
|
|
1918
2094
|
},
|
|
1919
2095
|
async ({ id }) => {
|
|
@@ -1958,6 +2134,7 @@ function registerChatgptFetch(server2, client2) {
|
|
|
1958
2134
|
// ../mcp-core/src/tools/index.ts
|
|
1959
2135
|
var TOOL_REGISTRARS = {
|
|
1960
2136
|
naumu_list_graphs: registerListGraphs,
|
|
2137
|
+
naumu_list_members: registerListMembers,
|
|
1961
2138
|
naumu_create_graph: registerCreateGraph,
|
|
1962
2139
|
naumu_get_schema: registerGetSchema,
|
|
1963
2140
|
naumu_update_schema: registerUpdateSchema,
|
|
@@ -1980,6 +2157,7 @@ var TOOL_REGISTRARS = {
|
|
|
1980
2157
|
naumu_read_thread: registerReadThread,
|
|
1981
2158
|
naumu_whoami: registerWhoami,
|
|
1982
2159
|
naumu_list_threads: registerListThreads,
|
|
2160
|
+
naumu_list_topics: registerListTopics,
|
|
1983
2161
|
naumu_get_thread: registerGetThread,
|
|
1984
2162
|
naumu_create_thread: registerCreateThread,
|
|
1985
2163
|
naumu_request_attachment_upload: registerRequestAttachmentUpload,
|
|
@@ -2006,9 +2184,13 @@ var TOOL_REGISTRARS = {
|
|
|
2006
2184
|
search: registerChatgptSearch,
|
|
2007
2185
|
fetch: registerChatgptFetch
|
|
2008
2186
|
};
|
|
2009
|
-
var
|
|
2187
|
+
var BOT_ONLY_TOOL_NAMES = /* @__PURE__ */ new Set(["naumu_create_thread"]);
|
|
2188
|
+
var ALL_TOOL_NAMES = Object.keys(TOOL_REGISTRARS).filter(
|
|
2189
|
+
(name) => !BOT_ONLY_TOOL_NAMES.has(name)
|
|
2190
|
+
);
|
|
2010
2191
|
function registerAllTools(server2, client2) {
|
|
2011
2192
|
for (const [name, registrar] of Object.entries(TOOL_REGISTRARS)) {
|
|
2193
|
+
if (BOT_ONLY_TOOL_NAMES.has(name)) continue;
|
|
2012
2194
|
if (name === "naumu_whoami") {
|
|
2013
2195
|
registerWhoami(server2, client2, ALL_TOOL_NAMES);
|
|
2014
2196
|
} else {
|