@naumu/mcp 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.js +313 -149
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ Add to your Cursor MCP settings:
|
|
|
51
51
|
| `naumu_add_edge` | Create a relationship between two nodes |
|
|
52
52
|
| `naumu_remove_node` | Delete a node and its connections |
|
|
53
53
|
| `naumu_remove_edge` | Delete a relationship |
|
|
54
|
+
| `naumu_create_topic` | Create a topic (filing destination) in a space; admin-only, name must be a lowercase slug, returns an `id` for `topicIds` params |
|
|
54
55
|
| `naumu_ask` | Ask @Naumu a question and get a synthesised answer back (answer + sources + confidence + threadId + status); leaves a visible thread in the space |
|
|
55
56
|
| `naumu_delegate` | Hand @Naumu a task to carry out asynchronously (add knowledge, make changes); returns a `threadId` immediately, then poll with `naumu_read_thread` |
|
|
56
57
|
| `naumu_read_thread` | Read a thread's messages; each message carries a `status` of `processing` or `complete` |
|
package/dist/index.js
CHANGED
|
@@ -133,6 +133,9 @@ URL \u2192 tool mapping (the value after /spaces/ is the slug \u2014 resolve it
|
|
|
133
133
|
|
|
134
134
|
Localhost URLs (http://localhost:3000/spaces/{slug}/...) follow the same shape \u2014 resolve the slug the same way. The MCP backend host is configured separately; the URL the user pastes is just for parsing structure.`;
|
|
135
135
|
|
|
136
|
+
// ../mcp-core/src/version.ts
|
|
137
|
+
var NAUMU_MCP_VERSION = "0.11.0";
|
|
138
|
+
|
|
136
139
|
// ../mcp-core/src/tools/list-graphs.ts
|
|
137
140
|
import { z } from "zod";
|
|
138
141
|
function registerListGraphs(server2, client2) {
|
|
@@ -523,11 +526,11 @@ function registerSearch(server2, client2) {
|
|
|
523
526
|
{
|
|
524
527
|
title: "Search Graph",
|
|
525
528
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
526
|
-
description: 'Hybrid search over graph nodes; use for meaning-based lookup when you don\'t know the exact label. Combines
|
|
529
|
+
description: 'Hybrid search over graph nodes; use for meaning-based lookup when you don\'t know the exact label. Combines text matching, which looks for the whole query as one contiguous substring (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").',
|
|
527
530
|
inputSchema: z9.object({
|
|
528
531
|
graphId: z9.string().describe("The graph ID"),
|
|
529
532
|
query: z9.string().describe(
|
|
530
|
-
|
|
533
|
+
"A short contiguous phrase \u2014 an entity name, label, or ID. The text half matches it verbatim as a case-insensitive substring; the semantic half matches meaning."
|
|
531
534
|
),
|
|
532
535
|
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
536
|
nodeTypes: z9.array(z9.string()).optional().describe('Filter to specific node types (e.g. ["Type A", "Type B"])')
|
|
@@ -805,7 +808,7 @@ function registerAddNode(server2, client2) {
|
|
|
805
808
|
{
|
|
806
809
|
title: "Add Nodes (bulk)",
|
|
807
810
|
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
|
|
811
|
+
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 treat a result as the same entity when its `matchedVia` is "semantic" or "both" AND its type matches AND the labels plausibly name the same thing - then reuse/update that node instead of creating one. (The `score` field is a rank-fusion value, not a similarity - do not compare it against a threshold.) 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
812
|
inputSchema: z13.object({
|
|
810
813
|
graphId: z13.string().describe("The graph ID"),
|
|
811
814
|
nodes: z13.array(NodeInput).min(1).max(25).describe("Batch of 1\u201325 nodes to create. Keep batches small for atomicity.")
|
|
@@ -1091,18 +1094,20 @@ function registerPostMessage(server2, client2) {
|
|
|
1091
1094
|
idempotentHint: false,
|
|
1092
1095
|
openWorldHint: false
|
|
1093
1096
|
},
|
|
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.
|
|
1097
|
+
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. Write `content` in markdown (the default format): **bold**, *italic*, `inline code`, fenced code blocks, `- ` bullets, `1. ` ordered lists, and `> ` quotes all render natively; headings and tables are not supported and render as plain text. Mentions are inline pills: `@[Name](id)` mentions a person or bot and `#[label](topic-id)` tags a topic. 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; `@[Naumu](naumu-ai)` addresses the @Naumu agent (a bare `@naumu` in prose also summons it, so only type it when you mean to). @mentioning people loops them in (mention notifications, prompts) without invoking @Naumu. Set contentFormat to "tiptap" only when you need rich content beyond the markdown subset, passing a Tiptap JSON document with mention nodes (`{ type: "mention", attrs: { id, label } }`). 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
1098
|
inputSchema: z20.object({
|
|
1096
1099
|
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.
|
|
1098
|
-
contentFormat: z20.enum(["tiptap", "
|
|
1100
|
+
content: z20.string().optional().describe('Message body. Markdown by default (see the tool description for the supported subset and the `@[Name](id)` mention pill syntax); a Tiptap JSON document when contentFormat is "tiptap". Optional when `attachmentIds` is provided.'),
|
|
1101
|
+
contentFormat: z20.enum(["tiptap", "markdown"]).optional().describe('Format of `content`. Defaults to "markdown" (rendered subset plus `@[Name](id)` / `#[label](topic-id)` mention pills). Use "tiptap" for full rich content, e.g. a doc containing `{ type: "mention", attrs: { id: userIdOrEmailOrIdentityId, label: displayName } }`.'),
|
|
1099
1102
|
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.")
|
|
1100
1103
|
})
|
|
1101
1104
|
},
|
|
1102
1105
|
async ({ threadId, content, contentFormat, attachmentIds }) => {
|
|
1103
1106
|
try {
|
|
1104
1107
|
const body = {
|
|
1105
|
-
|
|
1108
|
+
// 'markdown' is the announced name for the API's 'text' mode;
|
|
1109
|
+
// deployed backends may predate the alias, so translate on the wire.
|
|
1110
|
+
contentFormat: contentFormat === "tiptap" ? "tiptap" : "text"
|
|
1106
1111
|
};
|
|
1107
1112
|
if (content !== void 0) body.content = content;
|
|
1108
1113
|
if (attachmentIds && attachmentIds.length > 0) body.attachmentIds = attachmentIds;
|
|
@@ -1285,8 +1290,106 @@ function registerListTopics(server2, client2) {
|
|
|
1285
1290
|
);
|
|
1286
1291
|
}
|
|
1287
1292
|
|
|
1288
|
-
// ../mcp-core/src/tools/
|
|
1293
|
+
// ../mcp-core/src/tools/create-topic.ts
|
|
1289
1294
|
import { z as z25 } from "zod";
|
|
1295
|
+
var TOPIC_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
1296
|
+
var TOPIC_NAME_MAX_LENGTH = 50;
|
|
1297
|
+
var RESERVED_TOPIC_NAMES = [
|
|
1298
|
+
"all",
|
|
1299
|
+
"everyone",
|
|
1300
|
+
"naumu",
|
|
1301
|
+
"here",
|
|
1302
|
+
"misc",
|
|
1303
|
+
"hidden",
|
|
1304
|
+
"space",
|
|
1305
|
+
"shared-with-you"
|
|
1306
|
+
];
|
|
1307
|
+
function validateTopicName(name) {
|
|
1308
|
+
const trimmed = name.trim();
|
|
1309
|
+
if (trimmed.length === 0) return "Topic name is required.";
|
|
1310
|
+
if (trimmed.length > TOPIC_NAME_MAX_LENGTH) {
|
|
1311
|
+
return `Topic name must be at most ${TOPIC_NAME_MAX_LENGTH} characters (got ${trimmed.length}).`;
|
|
1312
|
+
}
|
|
1313
|
+
if (!TOPIC_NAME_PATTERN.test(trimmed)) {
|
|
1314
|
+
return `Invalid topic name "${trimmed}". Only lowercase letters, numbers, and "-" are allowed (e.g. "core-team"). Replace spaces with hyphens and drop any other characters.`;
|
|
1315
|
+
}
|
|
1316
|
+
if (RESERVED_TOPIC_NAMES.includes(trimmed.toLowerCase())) {
|
|
1317
|
+
return `Topic name "${trimmed}" is reserved. Reserved names: ${RESERVED_TOPIC_NAMES.join(", ")}.`;
|
|
1318
|
+
}
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
function registerCreateTopic(server2, client2) {
|
|
1322
|
+
server2.registerTool(
|
|
1323
|
+
"naumu_create_topic",
|
|
1324
|
+
{
|
|
1325
|
+
title: "Create Topic",
|
|
1326
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1327
|
+
description: 'Create a new topic (filing destination) in a space; use when the topic a thread or note should be filed into does not exist yet. Check `naumu_list_topics` first so you reuse an existing topic instead of creating a near-duplicate. Returns the created topic `{id, name, color, visibilityMode, archived, openToWeb, webParticipation, createdAt, createdBy, memberIds, isMember}` - pass the returned `id` to the `topicIds` param of naumu_delegate, naumu_ask or naumu_create_note to file work into it. Name rules (validated before the call): lowercase letters, numbers and "-" only (channel-slug style, e.g. "core-team"), at most 50 characters, and never one of the reserved names all, everyone, naumu, here, misc, hidden, space, shared-with-you. Names are unique per space, case-insensitively. Requires admin rights on the space: editors and bot identities always get a 403, so do not attempt this on behalf of a bot. `visibilityMode` defaults to "open"; the caller is always added as a member, and `openToWeb` cannot be combined with a "closed" topic. Resolve `graphId` via naumu_list_graphs first.',
|
|
1328
|
+
inputSchema: z25.object({
|
|
1329
|
+
graphId: z25.string().describe("The space (graph) id to create the topic in."),
|
|
1330
|
+
name: z25.string().min(1).describe(
|
|
1331
|
+
'Topic name in channel-slug form: lowercase letters, numbers and "-" only, max 50 chars, not a reserved name. Unique per space (case-insensitive).'
|
|
1332
|
+
),
|
|
1333
|
+
color: z25.string().optional().describe("Optional named color token for the topic badge. Omit unless the user asked for a specific color."),
|
|
1334
|
+
visibilityMode: z25.enum(["default", "open", "closed"]).optional().describe(
|
|
1335
|
+
'Who can see and join the topic. "open" (the default) lets any space member join, "default" is the space default, "closed" is invite-only.'
|
|
1336
|
+
),
|
|
1337
|
+
openToWeb: z25.boolean().optional().describe('Expose the topic publicly on the web. Invalid together with visibilityMode "closed".'),
|
|
1338
|
+
webParticipation: z25.enum(["participate", "view-only"]).optional().describe('What public web visitors may do when openToWeb is true. Defaults to "view-only".'),
|
|
1339
|
+
memberIds: z25.array(z25.string()).optional().describe(
|
|
1340
|
+
"User ids to add as topic members (get them from naumu_list_members). Ids that are not current space members are silently dropped. The caller is always added regardless."
|
|
1341
|
+
)
|
|
1342
|
+
})
|
|
1343
|
+
},
|
|
1344
|
+
async ({ graphId, name, color, visibilityMode, openToWeb, webParticipation, memberIds }) => {
|
|
1345
|
+
const nameError = validateTopicName(name);
|
|
1346
|
+
if (nameError) {
|
|
1347
|
+
return {
|
|
1348
|
+
content: [
|
|
1349
|
+
{
|
|
1350
|
+
type: "text",
|
|
1351
|
+
text: `Topic name validation failed - no topic was created: ${nameError}`
|
|
1352
|
+
}
|
|
1353
|
+
],
|
|
1354
|
+
isError: true
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
if (openToWeb === true && visibilityMode === "closed") {
|
|
1358
|
+
return {
|
|
1359
|
+
content: [
|
|
1360
|
+
{
|
|
1361
|
+
type: "text",
|
|
1362
|
+
text: 'Topic validation failed - no topic was created: public web access is only available on open or default topics, so openToWeb cannot be true when visibilityMode is "closed".'
|
|
1363
|
+
}
|
|
1364
|
+
],
|
|
1365
|
+
isError: true
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
try {
|
|
1369
|
+
const data = await client2.post(`/api/graphs/${graphId}/topics`, {
|
|
1370
|
+
name: name.trim(),
|
|
1371
|
+
color,
|
|
1372
|
+
visibilityMode,
|
|
1373
|
+
openToWeb,
|
|
1374
|
+
webParticipation,
|
|
1375
|
+
memberIds
|
|
1376
|
+
});
|
|
1377
|
+
return {
|
|
1378
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1379
|
+
};
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1382
|
+
return {
|
|
1383
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1384
|
+
isError: true
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// ../mcp-core/src/tools/get-thread.ts
|
|
1392
|
+
import { z as z26 } from "zod";
|
|
1290
1393
|
function sanitizeThreadParticipants2(thread) {
|
|
1291
1394
|
if (!thread || typeof thread !== "object" || !("participantEmails" in thread)) {
|
|
1292
1395
|
return thread;
|
|
@@ -1301,8 +1404,8 @@ function registerGetThread(server2, client2) {
|
|
|
1301
1404
|
title: "Get Thread",
|
|
1302
1405
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1303
1406
|
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.",
|
|
1304
|
-
inputSchema:
|
|
1305
|
-
threadId:
|
|
1407
|
+
inputSchema: z26.object({
|
|
1408
|
+
threadId: z26.string().describe("The thread ID to fetch.")
|
|
1306
1409
|
})
|
|
1307
1410
|
},
|
|
1308
1411
|
async ({ threadId }) => {
|
|
@@ -1324,7 +1427,7 @@ function registerGetThread(server2, client2) {
|
|
|
1324
1427
|
}
|
|
1325
1428
|
|
|
1326
1429
|
// ../mcp-core/src/tools/create-thread.ts
|
|
1327
|
-
import { z as
|
|
1430
|
+
import { z as z27 } from "zod";
|
|
1328
1431
|
function registerCreateThread(server2, client2) {
|
|
1329
1432
|
server2.registerTool(
|
|
1330
1433
|
"naumu_create_thread",
|
|
@@ -1339,23 +1442,23 @@ function registerCreateThread(server2, client2) {
|
|
|
1339
1442
|
openWorldHint: false
|
|
1340
1443
|
},
|
|
1341
1444
|
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.",
|
|
1342
|
-
inputSchema:
|
|
1343
|
-
title:
|
|
1344
|
-
participants:
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
type:
|
|
1348
|
-
userId:
|
|
1445
|
+
inputSchema: z27.object({
|
|
1446
|
+
title: z27.string().min(1).max(200).optional().describe('Thread title shown in the sidebar. If omitted, Naumu generates a default like "Conversation YYYY-MM-DD".'),
|
|
1447
|
+
participants: z27.array(
|
|
1448
|
+
z27.discriminatedUnion("type", [
|
|
1449
|
+
z27.object({
|
|
1450
|
+
type: z27.literal("user"),
|
|
1451
|
+
userId: z27.string().min(1).describe("User UUID \u2014 get these from `naumu_get_thread`/`naumu_read_thread` participant rosters or webhook payloads.")
|
|
1349
1452
|
}),
|
|
1350
|
-
|
|
1351
|
-
type:
|
|
1352
|
-
identityId:
|
|
1453
|
+
z27.object({
|
|
1454
|
+
type: z27.literal("identity"),
|
|
1455
|
+
identityId: z27.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.")
|
|
1353
1456
|
})
|
|
1354
1457
|
])
|
|
1355
1458
|
).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."),
|
|
1356
|
-
initialMessage:
|
|
1357
|
-
visibility:
|
|
1358
|
-
topicIds:
|
|
1459
|
+
initialMessage: z27.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."),
|
|
1460
|
+
visibility: z27.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."),
|
|
1461
|
+
topicIds: z27.array(z27.string()).max(8).optional().describe(
|
|
1359
1462
|
"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."
|
|
1360
1463
|
)
|
|
1361
1464
|
})
|
|
@@ -1384,32 +1487,53 @@ function registerCreateThread(server2, client2) {
|
|
|
1384
1487
|
}
|
|
1385
1488
|
|
|
1386
1489
|
// ../mcp-core/src/tools/request-attachment-upload.ts
|
|
1387
|
-
import { z as
|
|
1490
|
+
import { z as z28 } from "zod";
|
|
1388
1491
|
function registerRequestAttachmentUpload(server2, client2) {
|
|
1389
1492
|
server2.registerTool(
|
|
1390
1493
|
"naumu_request_attachment_upload",
|
|
1391
1494
|
{
|
|
1392
1495
|
title: "Request Attachment Upload",
|
|
1393
1496
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1394
|
-
description:
|
|
1395
|
-
inputSchema:
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1497
|
+
description: "Request a presigned S3 upload URL to attach a file - pass exactly one of `threadId`, `noteId`, or `canvasId` for where the upload will land, then PUT the bytes directly to the returned URL. Same flow Naumu users use for file uploads. User API keys and OAuth sessions must also pass `graphId` (a user spans many spaces; resolve it via `naumu_list_graphs`); bot keys resolve it automatically.\n\nThree destinations, three follow-up calls:\n\u2022 Thread: presign with `threadId` \u2192 PUT the bytes \u2192 call `naumu_post_message` with the returned `attachmentId` in `attachmentIds`.\n\u2022 Note: presign with `noteId` \u2192 PUT the bytes \u2192 call `naumu_note_append` (or any note write tool) with `` in the markdown. That single call binds the upload, embeds it inline as the note's canonical media node, and files it into the space's Files & Media library - the same syntax works for images, video, audio, and other files, dispatched by the upload's MIME type.\n\u2022 Canvas: presign with `canvasId` \u2192 PUT the bytes \u2192 call `naumu_persist_canvas_attachment` to extend the upload past its default 1-hour TTL. Placing the persisted attachment onto the canvas itself still happens in the app UI.\n\nTo attach media to a graph node, upload into a thread that originated or modified that node - files surface on the node via its threads; there is no separate node-attachment flow.\n\nPer-MIME size caps apply: 10MB for types the agent reads whole (image, text, PDF, office docs), otherwise the space plan's umbrella cap (50MB free, 500MB team, 1GB max). Audio takes the plan cap, NOT the 10MB agent-read budget - it is transcribed on upload and only the transcript reaches an agent, so a multi-hour recording is a legitimate attachment. Over-cap requests are refused with HTTP 413 before any URL is minted, and the size is re-checked at bind time against the object that actually landed - so an oversized upload is refused there too, not silently accepted.\n\nReturns `{ attachmentId, uploadUrl, method, requiredHeaders, expiresAt }`. Use these EXACTLY:\n\u2022 `method` is \"PUT\".\n\u2022 Send every header in `requiredHeaders`. It carries BOTH `Content-Type` and `Content-Length`, and both are load-bearing: `Content-Length` is signed INTO the URL, so the body must be exactly that many bytes or S3 answers 403. PUT the whole file as one fixed-length body - do not stream it, do not use chunked transfer encoding, and do not send a different byte count than the `fileSize` you declared here.\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. Bind it (post the message, embed the note reference, or persist the canvas attachment) before then or the upload orphans.\n\nServer-side checks at bind time enforce that the attachment was uploaded by you, in this graph, for this thread/note/canvas - you cannot reuse an upload across destinations.",
|
|
1498
|
+
inputSchema: z28.object({
|
|
1499
|
+
graphId: z28.string().optional().describe("Graph (space) UUID the destination lives in. REQUIRED for user API keys and OAuth sessions - a user spans many spaces, so nothing can infer it; resolve it once via `naumu_list_graphs` and reuse it. Bot keys may omit it: a bot is pinned to one graph and the tool resolves it automatically."),
|
|
1500
|
+
threadId: z28.string().optional().describe("Destination thread - presign for a thread when the upload will be attached to a chat message via `naumu_post_message`'s `attachmentIds`. You must be a participant. Exactly one of `threadId`, `noteId`, or `canvasId` is required. The pending attachment is keyed to this thread - you cannot reuse it for a different one."),
|
|
1501
|
+
noteId: z28.string().optional().describe("Destination note (Thought) - presign for a note when the upload will be embedded via `` in a note write tool call. Exactly one of `threadId`, `noteId`, or `canvasId` is required. The pending attachment is keyed to this note - you cannot reuse it for a different one."),
|
|
1502
|
+
canvasId: z28.string().optional().describe("Destination canvas - presign for a canvas when the upload will be placed on a canvas; follow up with `naumu_persist_canvas_attachment` to extend its TTL. Exactly one of `threadId`, `noteId`, or `canvasId` is required. The pending attachment is keyed to this canvas - you cannot reuse it for a different one."),
|
|
1503
|
+
fileName: z28.string().min(1).describe("Original filename (with extension). Used as the display name and for the S3 object suffix. Special characters are sanitized server-side."),
|
|
1504
|
+
fileType: z28.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."),
|
|
1505
|
+
fileSize: z28.number().int().positive().describe("File size in bytes, exact. Validated against per-MIME caps before the URL is issued - exceeding the cap returns a 413 (not a 400; 400 means a different rejection). This number is also signed into the upload URL as `Content-Length`, so it is a commitment, not an estimate: PUT exactly this many bytes. Audio is validated against the plan umbrella cap (50MB free / 500MB team / 1GB max), not the 10MB agent-read budget."),
|
|
1506
|
+
audioDurationSec: z28.number().positive().optional().describe("For audio attachments, duration in seconds. Send it whenever you know it: audio carries an 8-hour ceiling on top of the size cap, and nothing measures the file server-side, so that ceiling is checked ONLY against a length you report. Omitting it skips the check rather than failing it - the size cap is what still binds you. Omit it rather than guessing: a wrong value is worse than none, and a deliberate under-report is a policy violation, not a workaround.")
|
|
1507
|
+
}).refine(
|
|
1508
|
+
(data) => [data.threadId, data.noteId, data.canvasId].filter((v) => v !== void 0).length === 1,
|
|
1509
|
+
{ message: "Exactly one of threadId, noteId, or canvasId is required - pick the single destination this upload is for." }
|
|
1510
|
+
)
|
|
1402
1511
|
},
|
|
1403
|
-
async ({ threadId, fileName, fileType, fileSize, audioDurationSec }) => {
|
|
1512
|
+
async ({ graphId, threadId, noteId, canvasId, fileName, fileType, fileSize, audioDurationSec }) => {
|
|
1404
1513
|
try {
|
|
1405
|
-
|
|
1514
|
+
let resolvedGraphId = graphId;
|
|
1515
|
+
if (resolvedGraphId === void 0) {
|
|
1516
|
+
const me = await client2.get("/api/identities/me/whoami");
|
|
1517
|
+
resolvedGraphId = me.graphId;
|
|
1518
|
+
}
|
|
1519
|
+
if (resolvedGraphId === void 0) {
|
|
1520
|
+
return {
|
|
1521
|
+
content: [{
|
|
1522
|
+
type: "text",
|
|
1523
|
+
text: "Error: graphId is required for this session. Your key is a user key, which spans multiple spaces - call naumu_list_graphs, pick the target space, and pass its id as graphId."
|
|
1524
|
+
}],
|
|
1525
|
+
isError: true
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1406
1528
|
const body = {
|
|
1407
|
-
graphId:
|
|
1408
|
-
threadId,
|
|
1529
|
+
graphId: resolvedGraphId,
|
|
1409
1530
|
fileName,
|
|
1410
1531
|
fileType,
|
|
1411
1532
|
fileSize
|
|
1412
1533
|
};
|
|
1534
|
+
if (threadId !== void 0) body.threadId = threadId;
|
|
1535
|
+
if (noteId !== void 0) body.noteId = noteId;
|
|
1536
|
+
if (canvasId !== void 0) body.canvasId = canvasId;
|
|
1413
1537
|
if (audioDurationSec !== void 0) body.audioDurationSec = audioDurationSec;
|
|
1414
1538
|
const data = await client2.post("/api/attachments/presign", body);
|
|
1415
1539
|
return {
|
|
@@ -1426,8 +1550,38 @@ function registerRequestAttachmentUpload(server2, client2) {
|
|
|
1426
1550
|
);
|
|
1427
1551
|
}
|
|
1428
1552
|
|
|
1553
|
+
// ../mcp-core/src/tools/persist-canvas-attachment.ts
|
|
1554
|
+
import { z as z29 } from "zod";
|
|
1555
|
+
function registerPersistCanvasAttachment(server2, client2) {
|
|
1556
|
+
server2.registerTool(
|
|
1557
|
+
"naumu_persist_canvas_attachment",
|
|
1558
|
+
{
|
|
1559
|
+
title: "Persist Canvas Attachment",
|
|
1560
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1561
|
+
description: "Extend a canvas-bound pending attachment's TTL from its default 1 hour to 30 days; call right after `naumu_request_attachment_upload` (with `canvasId` set) and the S3 PUT, so the upload survives long enough to be used. This only persists the upload - actually placing it onto the canvas still happens in the app UI, there is no MCP canvas-editing tool yet. Safe to call more than once for the same attachmentId.",
|
|
1562
|
+
inputSchema: z29.object({
|
|
1563
|
+
attachmentId: z29.string().min(1).describe("The `attachmentId` returned by `naumu_request_attachment_upload` for this canvas.")
|
|
1564
|
+
})
|
|
1565
|
+
},
|
|
1566
|
+
async ({ attachmentId }) => {
|
|
1567
|
+
try {
|
|
1568
|
+
const data = await client2.post(`/api/attachments/${attachmentId}/persist-canvas`);
|
|
1569
|
+
return {
|
|
1570
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1571
|
+
};
|
|
1572
|
+
} catch (err) {
|
|
1573
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1574
|
+
return {
|
|
1575
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1576
|
+
isError: true
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1429
1583
|
// ../mcp-core/src/tools/add-reaction.ts
|
|
1430
|
-
import { z as
|
|
1584
|
+
import { z as z30 } from "zod";
|
|
1431
1585
|
function registerAddReaction(server2, client2) {
|
|
1432
1586
|
server2.registerTool(
|
|
1433
1587
|
"naumu_add_reaction",
|
|
@@ -1435,10 +1589,10 @@ function registerAddReaction(server2, client2) {
|
|
|
1435
1589
|
title: "Add Reaction",
|
|
1436
1590
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1437
1591
|
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.',
|
|
1438
|
-
inputSchema:
|
|
1439
|
-
threadId:
|
|
1440
|
-
messageId:
|
|
1441
|
-
emoji:
|
|
1592
|
+
inputSchema: z30.object({
|
|
1593
|
+
threadId: z30.string().describe("Thread containing the message. You must be a participant."),
|
|
1594
|
+
messageId: z30.string().describe("The message to react to."),
|
|
1595
|
+
emoji: z30.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.')
|
|
1442
1596
|
})
|
|
1443
1597
|
},
|
|
1444
1598
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1462,7 +1616,7 @@ function registerAddReaction(server2, client2) {
|
|
|
1462
1616
|
}
|
|
1463
1617
|
|
|
1464
1618
|
// ../mcp-core/src/tools/remove-reaction.ts
|
|
1465
|
-
import { z as
|
|
1619
|
+
import { z as z31 } from "zod";
|
|
1466
1620
|
function registerRemoveReaction(server2, client2) {
|
|
1467
1621
|
server2.registerTool(
|
|
1468
1622
|
"naumu_remove_reaction",
|
|
@@ -1470,10 +1624,10 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1470
1624
|
title: "Remove Reaction",
|
|
1471
1625
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1472
1626
|
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.",
|
|
1473
|
-
inputSchema:
|
|
1474
|
-
threadId:
|
|
1475
|
-
messageId:
|
|
1476
|
-
emoji:
|
|
1627
|
+
inputSchema: z31.object({
|
|
1628
|
+
threadId: z31.string().describe("Thread containing the message. You must be a participant."),
|
|
1629
|
+
messageId: z31.string().describe("The message to remove your reaction from."),
|
|
1630
|
+
emoji: z31.string().min(1).describe("Emoji character to remove (must match what you originally reacted with).")
|
|
1477
1631
|
})
|
|
1478
1632
|
},
|
|
1479
1633
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1497,7 +1651,7 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1497
1651
|
}
|
|
1498
1652
|
|
|
1499
1653
|
// ../mcp-core/src/tools/naumu-typing.ts
|
|
1500
|
-
import { z as
|
|
1654
|
+
import { z as z32 } from "zod";
|
|
1501
1655
|
function registerNaumuTyping(server2, client2) {
|
|
1502
1656
|
server2.registerTool(
|
|
1503
1657
|
"naumu_typing",
|
|
@@ -1508,9 +1662,9 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1508
1662
|
// repeating the same state is a no-op renew, so idempotent.
|
|
1509
1663
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1510
1664
|
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.',
|
|
1511
|
-
inputSchema:
|
|
1512
|
-
threadId:
|
|
1513
|
-
state:
|
|
1665
|
+
inputSchema: z32.object({
|
|
1666
|
+
threadId: z32.string().describe("The thread ID to set typing in. You must be a participant."),
|
|
1667
|
+
state: z32.enum(["start", "stop"]).describe('"start" acquires/renews the lease; "stop" ends it and clears the pill immediately.')
|
|
1514
1668
|
})
|
|
1515
1669
|
},
|
|
1516
1670
|
async ({ threadId, state }) => {
|
|
@@ -1531,7 +1685,7 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1531
1685
|
}
|
|
1532
1686
|
|
|
1533
1687
|
// ../mcp-core/src/tools/note-read.ts
|
|
1534
|
-
import { z as
|
|
1688
|
+
import { z as z33 } from "zod";
|
|
1535
1689
|
function registerNoteRead(server2, client2) {
|
|
1536
1690
|
server2.registerTool(
|
|
1537
1691
|
"naumu_note_read",
|
|
@@ -1539,8 +1693,8 @@ function registerNoteRead(server2, client2) {
|
|
|
1539
1693
|
title: "Read Note",
|
|
1540
1694
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1541
1695
|
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.",
|
|
1542
|
-
inputSchema:
|
|
1543
|
-
noteId:
|
|
1696
|
+
inputSchema: z33.object({
|
|
1697
|
+
noteId: z33.string().describe("The note (Thought) ID")
|
|
1544
1698
|
})
|
|
1545
1699
|
},
|
|
1546
1700
|
async ({ noteId }) => {
|
|
@@ -1553,17 +1707,17 @@ function registerNoteRead(server2, client2) {
|
|
|
1553
1707
|
}
|
|
1554
1708
|
|
|
1555
1709
|
// ../mcp-core/src/tools/note-append.ts
|
|
1556
|
-
import { z as
|
|
1710
|
+
import { z as z34 } from "zod";
|
|
1557
1711
|
function registerNoteAppend(server2, client2) {
|
|
1558
1712
|
server2.registerTool(
|
|
1559
1713
|
"naumu_note_append",
|
|
1560
1714
|
{
|
|
1561
1715
|
title: "Append to Note",
|
|
1562
1716
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1563
|
-
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.",
|
|
1564
|
-
inputSchema:
|
|
1565
|
-
noteId:
|
|
1566
|
-
markdown:
|
|
1717
|
+
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. To embed media, write `` as a sole-line image - the id must come from `naumu_request_attachment_upload` presigned with this same `noteId`. The one syntax covers images, video, audio, and other files, dispatched by the upload's MIME type into the note's canonical media node; an unresolvable or wrong-note id fails the whole write with a 400 naming the bad id. An id already embedded in the note can be repeated safely without re-uploading.",
|
|
1718
|
+
inputSchema: z34.object({
|
|
1719
|
+
noteId: z34.string().describe("The note (Thought) ID to append to"),
|
|
1720
|
+
markdown: z34.string().min(1).describe("Markdown content to append at the end of the note")
|
|
1567
1721
|
})
|
|
1568
1722
|
},
|
|
1569
1723
|
async ({ noteId, markdown }) => {
|
|
@@ -1576,18 +1730,18 @@ function registerNoteAppend(server2, client2) {
|
|
|
1576
1730
|
}
|
|
1577
1731
|
|
|
1578
1732
|
// ../mcp-core/src/tools/note-insert.ts
|
|
1579
|
-
import { z as
|
|
1733
|
+
import { z as z35 } from "zod";
|
|
1580
1734
|
function registerNoteInsert(server2, client2) {
|
|
1581
1735
|
server2.registerTool(
|
|
1582
1736
|
"naumu_note_insert",
|
|
1583
1737
|
{
|
|
1584
1738
|
title: "Insert After Heading",
|
|
1585
1739
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1586
|
-
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.",
|
|
1587
|
-
inputSchema:
|
|
1588
|
-
noteId:
|
|
1589
|
-
headingText:
|
|
1590
|
-
markdown:
|
|
1740
|
+
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. To embed media, write `` as a sole-line image - the id must come from `naumu_request_attachment_upload` presigned with this same `noteId`. The one syntax covers images, video, audio, and other files, dispatched by the upload's MIME type; an unresolvable or wrong-note id fails the whole write with a 400 naming the bad id.",
|
|
1741
|
+
inputSchema: z35.object({
|
|
1742
|
+
noteId: z35.string().describe("The note (Thought) ID"),
|
|
1743
|
+
headingText: z35.string().min(1).describe("Exact text of the heading whose section the new content follows"),
|
|
1744
|
+
markdown: z35.string().min(1).describe("Markdown content to insert at the end of that section")
|
|
1591
1745
|
})
|
|
1592
1746
|
},
|
|
1593
1747
|
async ({ noteId, headingText, markdown }) => {
|
|
@@ -1603,19 +1757,19 @@ function registerNoteInsert(server2, client2) {
|
|
|
1603
1757
|
}
|
|
1604
1758
|
|
|
1605
1759
|
// ../mcp-core/src/tools/note-replace-section.ts
|
|
1606
|
-
import { z as
|
|
1760
|
+
import { z as z36 } from "zod";
|
|
1607
1761
|
function registerNoteReplaceSection(server2, client2) {
|
|
1608
1762
|
server2.registerTool(
|
|
1609
1763
|
"naumu_note_replace_section",
|
|
1610
1764
|
{
|
|
1611
1765
|
title: "Replace Section",
|
|
1612
1766
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1613
|
-
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.",
|
|
1614
|
-
inputSchema:
|
|
1615
|
-
noteId:
|
|
1616
|
-
headingText:
|
|
1617
|
-
markdown:
|
|
1618
|
-
keepHeading:
|
|
1767
|
+
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. To embed media, write `` as a sole-line image - the id must come from `naumu_request_attachment_upload` presigned with this same `noteId`. The one syntax covers images, video, audio, and other files, dispatched by the upload's MIME type; an unresolvable or wrong-note id fails the whole write with a 400 naming the bad id. An id already embedded elsewhere in the note can be repeated safely without re-uploading.",
|
|
1768
|
+
inputSchema: z36.object({
|
|
1769
|
+
noteId: z36.string().describe("The note (Thought) ID"),
|
|
1770
|
+
headingText: z36.string().min(1).describe("Exact text of the heading anchoring the section"),
|
|
1771
|
+
markdown: z36.string().describe("Replacement markdown for the section body"),
|
|
1772
|
+
keepHeading: z36.boolean().optional().describe("Whether to keep the heading row itself. Default true.")
|
|
1619
1773
|
})
|
|
1620
1774
|
},
|
|
1621
1775
|
async ({ noteId, headingText, markdown, keepHeading }) => {
|
|
@@ -1632,7 +1786,7 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1632
1786
|
}
|
|
1633
1787
|
|
|
1634
1788
|
// ../mcp-core/src/tools/note-delete-section.ts
|
|
1635
|
-
import { z as
|
|
1789
|
+
import { z as z37 } from "zod";
|
|
1636
1790
|
function registerNoteDeleteSection(server2, client2) {
|
|
1637
1791
|
server2.registerTool(
|
|
1638
1792
|
"naumu_note_delete_section",
|
|
@@ -1640,9 +1794,9 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1640
1794
|
title: "Delete Section",
|
|
1641
1795
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1642
1796
|
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.",
|
|
1643
|
-
inputSchema:
|
|
1644
|
-
noteId:
|
|
1645
|
-
headingText:
|
|
1797
|
+
inputSchema: z37.object({
|
|
1798
|
+
noteId: z37.string().describe("The note (Thought) ID"),
|
|
1799
|
+
headingText: z37.string().min(1).describe("Exact text of the heading whose section will be deleted")
|
|
1646
1800
|
})
|
|
1647
1801
|
},
|
|
1648
1802
|
async ({ noteId, headingText }) => {
|
|
@@ -1657,17 +1811,17 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1657
1811
|
}
|
|
1658
1812
|
|
|
1659
1813
|
// ../mcp-core/src/tools/note-replace.ts
|
|
1660
|
-
import { z as
|
|
1814
|
+
import { z as z38 } from "zod";
|
|
1661
1815
|
function registerNoteReplace(server2, client2) {
|
|
1662
1816
|
server2.registerTool(
|
|
1663
1817
|
"naumu_note_replace",
|
|
1664
1818
|
{
|
|
1665
1819
|
title: "Replace Note",
|
|
1666
1820
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1667
|
-
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.",
|
|
1668
|
-
inputSchema:
|
|
1669
|
-
noteId:
|
|
1670
|
-
markdown:
|
|
1821
|
+
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. To embed media, write `` as a sole-line image - the id must come from `naumu_request_attachment_upload` presigned with this same `noteId`. The one syntax covers images, video, audio, and other files, dispatched by the upload's MIME type; an unresolvable or wrong-note id fails the whole write with a 400 naming the bad id. Ids already embedded in the note (read them back via `naumu_note_read`) can be repeated safely without re-uploading.",
|
|
1822
|
+
inputSchema: z38.object({
|
|
1823
|
+
noteId: z38.string().describe("The note (Thought) ID"),
|
|
1824
|
+
markdown: z38.string().describe("New markdown content for the entire note")
|
|
1671
1825
|
})
|
|
1672
1826
|
},
|
|
1673
1827
|
async ({ noteId, markdown }) => {
|
|
@@ -1680,19 +1834,19 @@ function registerNoteReplace(server2, client2) {
|
|
|
1680
1834
|
}
|
|
1681
1835
|
|
|
1682
1836
|
// ../mcp-core/src/tools/note-find-replace.ts
|
|
1683
|
-
import { z as
|
|
1837
|
+
import { z as z39 } from "zod";
|
|
1684
1838
|
function registerNoteFindReplace(server2, client2) {
|
|
1685
1839
|
server2.registerTool(
|
|
1686
1840
|
"naumu_note_find_replace",
|
|
1687
1841
|
{
|
|
1688
1842
|
title: "Find/Replace in Note",
|
|
1689
1843
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1690
|
-
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.",
|
|
1691
|
-
inputSchema:
|
|
1692
|
-
noteId:
|
|
1693
|
-
find:
|
|
1694
|
-
replace:
|
|
1695
|
-
all:
|
|
1844
|
+
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. `replace` can embed media by containing `` as a sole-line image - the id must come from `naumu_request_attachment_upload` presigned with this same `noteId`; an unresolvable or wrong-note id fails the whole write with a 400 naming the bad id.",
|
|
1845
|
+
inputSchema: z39.object({
|
|
1846
|
+
noteId: z39.string().describe("The note (Thought) ID"),
|
|
1847
|
+
find: z39.string().min(1).describe("Substring to search for. Literal - no regex."),
|
|
1848
|
+
replace: z39.string().describe("Replacement string. May be empty to delete the match."),
|
|
1849
|
+
all: z39.boolean().optional().describe("Replace all occurrences (default true). Pass false to replace only the first.")
|
|
1696
1850
|
})
|
|
1697
1851
|
},
|
|
1698
1852
|
async ({ noteId, find, replace, all }) => {
|
|
@@ -1709,23 +1863,24 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
1709
1863
|
}
|
|
1710
1864
|
|
|
1711
1865
|
// ../mcp-core/src/tools/create-note.ts
|
|
1712
|
-
import { z as
|
|
1866
|
+
import { z as z40 } from "zod";
|
|
1713
1867
|
function registerCreateNote(server2, client2) {
|
|
1714
1868
|
server2.registerTool(
|
|
1715
1869
|
"naumu_create_note",
|
|
1716
1870
|
{
|
|
1717
1871
|
title: "Create Note",
|
|
1718
1872
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1719
|
-
description: "Create a
|
|
1720
|
-
inputSchema:
|
|
1721
|
-
graphId:
|
|
1722
|
-
title:
|
|
1723
|
-
|
|
1724
|
-
|
|
1873
|
+
description: "Create a note in a graph, optionally with its full content already in place. Pass `markdown` to create the note and its body in a single call - the preferred path for imports and for any content you already hold. Returns the new note row including its `id`; use `naumu_note_append` / `naumu_note_replace` for LATER edits, not to fill in content you could have passed here. The returned row is the note as it stood BEFORE the body write landed, so its content may read as empty - the write still succeeded, do not retry the call or re-append the body. `attachment://` refs are not accepted in create-time `markdown`: upload the file after the note exists and embed it with a note write tool. Bots can only create notes in their own graph, and a bot-created note is private (participants only) unless `sharedWithSpace` is set true. Bots cannot file notes into topics - a bot passing `topicIds` is rejected (bots hold no topic membership); use `sharedWithSpace` instead.",
|
|
1874
|
+
inputSchema: z40.object({
|
|
1875
|
+
graphId: z40.string().describe("The graph ID to create the note in"),
|
|
1876
|
+
title: z40.string().optional().describe("Optional title for the note"),
|
|
1877
|
+
markdown: z40.string().optional().describe("Full initial note content as markdown. Provide it here to create the note with its content in a single call - preferred for imports; do not restate large content through extra edit calls."),
|
|
1878
|
+
sharedWithSpace: z40.boolean().optional().describe("Share the note with everyone in the space. Omit (or false) to keep it private to its participants."),
|
|
1879
|
+
topicIds: z40.array(z40.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.")
|
|
1725
1880
|
})
|
|
1726
1881
|
},
|
|
1727
|
-
async ({ graphId, title, sharedWithSpace, topicIds }) => {
|
|
1728
|
-
const data = await client2.post("/api/notes", { graphId, title, sharedWithSpace, topicIds });
|
|
1882
|
+
async ({ graphId, title, markdown, sharedWithSpace, topicIds }) => {
|
|
1883
|
+
const data = await client2.post("/api/notes", { graphId, title, markdown, sharedWithSpace, topicIds });
|
|
1729
1884
|
return {
|
|
1730
1885
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1731
1886
|
};
|
|
@@ -1734,7 +1889,7 @@ function registerCreateNote(server2, client2) {
|
|
|
1734
1889
|
}
|
|
1735
1890
|
|
|
1736
1891
|
// ../mcp-core/src/tools/list-schema-violations.ts
|
|
1737
|
-
import { z as
|
|
1892
|
+
import { z as z41 } from "zod";
|
|
1738
1893
|
var DEFAULT_EXAMPLE_LIMIT = 5;
|
|
1739
1894
|
var rowsForKind = (violations, kind) => {
|
|
1740
1895
|
const rows = [];
|
|
@@ -1760,12 +1915,12 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
1760
1915
|
title: "List Schema Violations",
|
|
1761
1916
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1762
1917
|
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.",
|
|
1763
|
-
inputSchema:
|
|
1764
|
-
graphId:
|
|
1765
|
-
kind:
|
|
1918
|
+
inputSchema: z41.object({
|
|
1919
|
+
graphId: z41.string().describe("The graph ID"),
|
|
1920
|
+
kind: z41.string().optional().describe(
|
|
1766
1921
|
'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.'
|
|
1767
1922
|
),
|
|
1768
|
-
limit:
|
|
1923
|
+
limit: z41.number().int().min(1).optional().describe(
|
|
1769
1924
|
"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)."
|
|
1770
1925
|
)
|
|
1771
1926
|
})
|
|
@@ -1817,7 +1972,7 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
1817
1972
|
}
|
|
1818
1973
|
|
|
1819
1974
|
// ../mcp-core/src/tools/list-dense-nodes.ts
|
|
1820
|
-
import { z as
|
|
1975
|
+
import { z as z42 } from "zod";
|
|
1821
1976
|
function registerListDenseNodes(server2, client2) {
|
|
1822
1977
|
server2.registerTool(
|
|
1823
1978
|
"naumu_list_dense_nodes",
|
|
@@ -1825,10 +1980,10 @@ function registerListDenseNodes(server2, client2) {
|
|
|
1825
1980
|
title: "List Dense Nodes",
|
|
1826
1981
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1827
1982
|
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.',
|
|
1828
|
-
inputSchema:
|
|
1829
|
-
graphId:
|
|
1830
|
-
minConnections:
|
|
1831
|
-
nodeTypes:
|
|
1983
|
+
inputSchema: z42.object({
|
|
1984
|
+
graphId: z42.string().describe("The graph ID"),
|
|
1985
|
+
minConnections: z42.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."),
|
|
1986
|
+
nodeTypes: z42.array(z42.string()).optional().describe("Optional list of node types to restrict the scan to.")
|
|
1832
1987
|
})
|
|
1833
1988
|
},
|
|
1834
1989
|
async ({ graphId, minConnections, nodeTypes }) => {
|
|
@@ -1846,7 +2001,7 @@ function registerListDenseNodes(server2, client2) {
|
|
|
1846
2001
|
}
|
|
1847
2002
|
|
|
1848
2003
|
// ../mcp-core/src/tools/list-node-connections.ts
|
|
1849
|
-
import { z as
|
|
2004
|
+
import { z as z43 } from "zod";
|
|
1850
2005
|
function registerListNodeConnections(server2, client2) {
|
|
1851
2006
|
server2.registerTool(
|
|
1852
2007
|
"naumu_list_node_connections",
|
|
@@ -1854,11 +2009,11 @@ function registerListNodeConnections(server2, client2) {
|
|
|
1854
2009
|
title: "List Node Connections",
|
|
1855
2010
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1856
2011
|
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}}] }`.',
|
|
1857
|
-
inputSchema:
|
|
1858
|
-
graphId:
|
|
1859
|
-
nodeId:
|
|
1860
|
-
edgeType:
|
|
1861
|
-
direction:
|
|
2012
|
+
inputSchema: z43.object({
|
|
2013
|
+
graphId: z43.string().describe("The graph ID"),
|
|
2014
|
+
nodeId: z43.string().describe("The node ID to inspect"),
|
|
2015
|
+
edgeType: z43.string().optional().describe('Restrict to a specific relation label (e.g. "ASSOCIATED_WITH"). Case-insensitive; non-alphanum chars are normalized.'),
|
|
2016
|
+
direction: z43.enum(["in", "out", "both"]).optional().describe('Edge direction filter - "in" (incoming), "out" (outgoing), "both" (default).')
|
|
1862
2017
|
})
|
|
1863
2018
|
},
|
|
1864
2019
|
async ({ graphId, nodeId, edgeType, direction }) => {
|
|
@@ -1876,7 +2031,7 @@ function registerListNodeConnections(server2, client2) {
|
|
|
1876
2031
|
}
|
|
1877
2032
|
|
|
1878
2033
|
// ../mcp-core/src/tools/reparent.ts
|
|
1879
|
-
import { z as
|
|
2034
|
+
import { z as z44 } from "zod";
|
|
1880
2035
|
function registerReparent(server2, client2) {
|
|
1881
2036
|
server2.registerTool(
|
|
1882
2037
|
"naumu_reparent",
|
|
@@ -1884,11 +2039,11 @@ function registerReparent(server2, client2) {
|
|
|
1884
2039
|
title: "Reparent Node",
|
|
1885
2040
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1886
2041
|
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"}`.',
|
|
1887
|
-
inputSchema:
|
|
1888
|
-
graphId:
|
|
1889
|
-
nodeId:
|
|
1890
|
-
newParentId:
|
|
1891
|
-
newRelation:
|
|
2042
|
+
inputSchema: z44.object({
|
|
2043
|
+
graphId: z44.string().describe("The graph ID"),
|
|
2044
|
+
nodeId: z44.string().describe("The child node to reparent"),
|
|
2045
|
+
newParentId: z44.string().describe("The new parent node id"),
|
|
2046
|
+
newRelation: z44.string().describe('The new parent edge relation label (e.g. "PART_OF"). Must be valid per the schema for (child.type, relation, parent.type).')
|
|
1892
2047
|
})
|
|
1893
2048
|
},
|
|
1894
2049
|
async ({ graphId, nodeId, newParentId, newRelation }) => {
|
|
@@ -1904,7 +2059,7 @@ function registerReparent(server2, client2) {
|
|
|
1904
2059
|
}
|
|
1905
2060
|
|
|
1906
2061
|
// ../mcp-core/src/tools/batch-reparent.ts
|
|
1907
|
-
import { z as
|
|
2062
|
+
import { z as z45 } from "zod";
|
|
1908
2063
|
function registerBatchReparent(server2, client2) {
|
|
1909
2064
|
server2.registerTool(
|
|
1910
2065
|
"naumu_batch_reparent",
|
|
@@ -1912,11 +2067,11 @@ function registerBatchReparent(server2, client2) {
|
|
|
1912
2067
|
title: "Batch Reparent Nodes",
|
|
1913
2068
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1914
2069
|
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?}]`.',
|
|
1915
|
-
inputSchema:
|
|
1916
|
-
graphId:
|
|
1917
|
-
newParentId:
|
|
1918
|
-
newRelation:
|
|
1919
|
-
nodeIds:
|
|
2070
|
+
inputSchema: z45.object({
|
|
2071
|
+
graphId: z45.string().describe("The graph ID"),
|
|
2072
|
+
newParentId: z45.string().describe("Parent node id every nodeId in the batch will be parented to"),
|
|
2073
|
+
newRelation: z45.string().describe("Parent edge relation label (must be valid per schema for child.type \u2192 parent.type)"),
|
|
2074
|
+
nodeIds: z45.array(z45.string()).min(1).max(25).describe("1\u201325 child node ids to reparent under `newParentId`")
|
|
1920
2075
|
})
|
|
1921
2076
|
},
|
|
1922
2077
|
async ({ graphId, newParentId, newRelation, nodeIds }) => {
|
|
@@ -1933,7 +2088,7 @@ function registerBatchReparent(server2, client2) {
|
|
|
1933
2088
|
}
|
|
1934
2089
|
|
|
1935
2090
|
// ../mcp-core/src/tools/chatgpt-search.ts
|
|
1936
|
-
import { z as
|
|
2091
|
+
import { z as z46 } from "zod";
|
|
1937
2092
|
|
|
1938
2093
|
// ../mcp-core/src/public-origin.ts
|
|
1939
2094
|
function publicOrigin() {
|
|
@@ -1987,8 +2142,10 @@ function registerChatgptSearch(server2, client2) {
|
|
|
1987
2142
|
title: "Search",
|
|
1988
2143
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1989
2144
|
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.)",
|
|
1990
|
-
inputSchema:
|
|
1991
|
-
query:
|
|
2145
|
+
inputSchema: z46.object({
|
|
2146
|
+
query: z46.string().describe(
|
|
2147
|
+
"A short contiguous phrase \u2014 an entity name, label, or ID. The text half matches it verbatim as a case-insensitive substring; the semantic half matches meaning."
|
|
2148
|
+
)
|
|
1992
2149
|
})
|
|
1993
2150
|
},
|
|
1994
2151
|
async ({ query }) => {
|
|
@@ -2020,7 +2177,7 @@ function registerChatgptSearch(server2, client2) {
|
|
|
2020
2177
|
}
|
|
2021
2178
|
|
|
2022
2179
|
// ../mcp-core/src/tools/chatgpt-fetch.ts
|
|
2023
|
-
import { z as
|
|
2180
|
+
import { z as z47 } from "zod";
|
|
2024
2181
|
var NON_ATTRIBUTE_PROPS = /* @__PURE__ */ new Set([
|
|
2025
2182
|
"id",
|
|
2026
2183
|
"label",
|
|
@@ -2085,8 +2242,8 @@ function registerChatgptFetch(server2, client2) {
|
|
|
2085
2242
|
title: "Fetch",
|
|
2086
2243
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2087
2244
|
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.",
|
|
2088
|
-
inputSchema:
|
|
2089
|
-
id:
|
|
2245
|
+
inputSchema: z47.object({
|
|
2246
|
+
id: z47.string().describe("A resource id from a previous `search` result, in the form `<graphId>:<nodeId>`.")
|
|
2090
2247
|
})
|
|
2091
2248
|
},
|
|
2092
2249
|
async ({ id }) => {
|
|
@@ -2129,7 +2286,7 @@ function registerChatgptFetch(server2, client2) {
|
|
|
2129
2286
|
}
|
|
2130
2287
|
|
|
2131
2288
|
// ../mcp-core/src/tools/admission-status.ts
|
|
2132
|
-
import { z as
|
|
2289
|
+
import { z as z48 } from "zod";
|
|
2133
2290
|
function registerAdmissionStatus(server2, client2) {
|
|
2134
2291
|
server2.registerTool(
|
|
2135
2292
|
"naumu_admission_status",
|
|
@@ -2137,8 +2294,8 @@ function registerAdmissionStatus(server2, client2) {
|
|
|
2137
2294
|
title: "Admission Status",
|
|
2138
2295
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2139
2296
|
description: "Show who can auto-join a Naumu space (graph) and who is waiting for approval: the whitelisted emails (people who join the moment they sign in with that email), the auto-join domain wildcards, and the count of pending join requests. When there are pending requests, this also returns the full list (who requested, their email, git-email hint, and message) so you can act on them with naumu_resolve_join_request. Use it during repo init to review or seed access, or whenever the user asks who has access to a space or who is asking to join.",
|
|
2140
|
-
inputSchema:
|
|
2141
|
-
graphId:
|
|
2297
|
+
inputSchema: z48.object({
|
|
2298
|
+
graphId: z48.string().describe("The space (graph) ID to inspect admission for. You must be a member of this space.")
|
|
2142
2299
|
})
|
|
2143
2300
|
},
|
|
2144
2301
|
async ({ graphId }) => {
|
|
@@ -2168,7 +2325,7 @@ function registerAdmissionStatus(server2, client2) {
|
|
|
2168
2325
|
}
|
|
2169
2326
|
|
|
2170
2327
|
// ../mcp-core/src/tools/whitelist-members.ts
|
|
2171
|
-
import { z as
|
|
2328
|
+
import { z as z49 } from "zod";
|
|
2172
2329
|
function registerWhitelistMembers(server2, client2) {
|
|
2173
2330
|
server2.registerTool(
|
|
2174
2331
|
"naumu_whitelist_members",
|
|
@@ -2181,10 +2338,10 @@ function registerWhitelistMembers(server2, client2) {
|
|
|
2181
2338
|
openWorldHint: false
|
|
2182
2339
|
},
|
|
2183
2340
|
description: "Whitelist emails so those people auto-join a Naumu space (graph) the moment they sign in with that email. Use this during repo init: after you scrub git history, present the curated list of collaborators to the user, and get their explicit confirmation, call this with the confirmed emails. It is silent - it sends no invite emails, it just pre-authorizes those addresses. Returns which entries were created and which were skipped (already whitelisted or already members). Set repoInit true when this call is part of the repo init flow.",
|
|
2184
|
-
inputSchema:
|
|
2185
|
-
graphId:
|
|
2186
|
-
emails:
|
|
2187
|
-
repoInit:
|
|
2341
|
+
inputSchema: z49.object({
|
|
2342
|
+
graphId: z49.string().describe("The space (graph) ID to whitelist emails for. You must be a member of this space."),
|
|
2343
|
+
emails: z49.array(z49.string()).min(1).describe("The emails to whitelist. Each becomes an exact-match auto-join entry. Present these to the user and get confirmation before calling."),
|
|
2344
|
+
repoInit: z49.boolean().optional().describe("Set true when this whitelist is being seeded as part of the repo init flow, so onboarding is tracked correctly.")
|
|
2188
2345
|
})
|
|
2189
2346
|
},
|
|
2190
2347
|
async ({ graphId, emails, repoInit }) => {
|
|
@@ -2208,7 +2365,7 @@ function registerWhitelistMembers(server2, client2) {
|
|
|
2208
2365
|
}
|
|
2209
2366
|
|
|
2210
2367
|
// ../mcp-core/src/tools/resolve-admission.ts
|
|
2211
|
-
import { z as
|
|
2368
|
+
import { z as z50 } from "zod";
|
|
2212
2369
|
function registerResolveAdmission(server2, client2) {
|
|
2213
2370
|
server2.registerTool(
|
|
2214
2371
|
"naumu_resolve_admission",
|
|
@@ -2221,9 +2378,9 @@ function registerResolveAdmission(server2, client2) {
|
|
|
2221
2378
|
openWorldHint: false
|
|
2222
2379
|
},
|
|
2223
2380
|
description: "The call a coding agent makes right after connecting when a repo's .naumu references a space the user is not yet a member of. It evaluates whether the user can join and does it: outcome is joined-whitelist or joined-wildcard (the user is now a member - proceed), already-member (nothing to do), request-created (a join request was just filed and is awaiting a member's approval), or request-pending (a request was already open). When the response also carries reason 'seat-limit' on a request-created/request-pending outcome, the user WOULD have auto-joined via a whitelist/domain match but the space is at its seat limit - so their access is pending an admin approving them or upgrading the plan; relay that specific reason honestly, do not just say 'no match'. Pass gitEmailHint from `git config user.email` so a matching whitelist or domain rule can admit them. Relay the outcome to the user honestly: say plainly whether they joined or are waiting for approval - never imply access that is still pending.",
|
|
2224
|
-
inputSchema:
|
|
2225
|
-
graphId:
|
|
2226
|
-
gitEmailHint:
|
|
2381
|
+
inputSchema: z50.object({
|
|
2382
|
+
graphId: z50.string().describe("The space (graph) ID referenced by the repo .naumu file that the user wants to join."),
|
|
2383
|
+
gitEmailHint: z50.string().optional().describe("The email from `git config user.email`, used to match whitelist entries and auto-join domains.")
|
|
2227
2384
|
})
|
|
2228
2385
|
},
|
|
2229
2386
|
async ({ graphId, gitEmailHint }) => {
|
|
@@ -2247,7 +2404,7 @@ function registerResolveAdmission(server2, client2) {
|
|
|
2247
2404
|
}
|
|
2248
2405
|
|
|
2249
2406
|
// ../mcp-core/src/tools/resolve-join-request.ts
|
|
2250
|
-
import { z as
|
|
2407
|
+
import { z as z51 } from "zod";
|
|
2251
2408
|
function registerResolveJoinRequest(server2, client2) {
|
|
2252
2409
|
server2.registerTool(
|
|
2253
2410
|
"naumu_resolve_join_request",
|
|
@@ -2260,10 +2417,10 @@ function registerResolveJoinRequest(server2, client2) {
|
|
|
2260
2417
|
openWorldHint: false
|
|
2261
2418
|
},
|
|
2262
2419
|
description: "For a member resolving a pending join request surfaced by naumu_admission_status. Approve to add the requester to the space as a member, or deny to reject the request. Get the requestId from naumu_admission_status's pending list, and confirm the decision with the user before calling since approving grants access.",
|
|
2263
|
-
inputSchema:
|
|
2264
|
-
graphId:
|
|
2265
|
-
requestId:
|
|
2266
|
-
action:
|
|
2420
|
+
inputSchema: z51.object({
|
|
2421
|
+
graphId: z51.string().describe("The space (graph) ID the request is for. You must be a member of this space."),
|
|
2422
|
+
requestId: z51.string().describe("The pending join request ID, taken from naumu_admission_status."),
|
|
2423
|
+
action: z51.enum(["approve", "deny"]).describe("approve adds the requester as a member; deny rejects the request.")
|
|
2267
2424
|
})
|
|
2268
2425
|
},
|
|
2269
2426
|
async ({ graphId, requestId, action }) => {
|
|
@@ -2313,9 +2470,16 @@ var TOOL_REGISTRARS = {
|
|
|
2313
2470
|
naumu_whoami: registerWhoami,
|
|
2314
2471
|
naumu_list_threads: registerListThreads,
|
|
2315
2472
|
naumu_list_topics: registerListTopics,
|
|
2473
|
+
// User-surface only. `POST /api/graphs/:id/topics` requires the ADMIN-only
|
|
2474
|
+
// `space:manage-topics` permission, and bot identities always resolve to the
|
|
2475
|
+
// editor role, so a bot calling this could only ever 403. It is therefore
|
|
2476
|
+
// omitted from BOT_ONLY_TOOL_NAMES and has no entry in the backend
|
|
2477
|
+
// PERMISSION_TO_MCP_TOOLS map (same treatment as the admission tools below).
|
|
2478
|
+
naumu_create_topic: registerCreateTopic,
|
|
2316
2479
|
naumu_get_thread: registerGetThread,
|
|
2317
2480
|
naumu_create_thread: registerCreateThread,
|
|
2318
2481
|
naumu_request_attachment_upload: registerRequestAttachmentUpload,
|
|
2482
|
+
naumu_persist_canvas_attachment: registerPersistCanvasAttachment,
|
|
2319
2483
|
naumu_add_reaction: registerAddReaction,
|
|
2320
2484
|
naumu_remove_reaction: registerRemoveReaction,
|
|
2321
2485
|
naumu_typing: registerNaumuTyping,
|
|
@@ -2389,7 +2553,7 @@ var client = new NaumuClient(apiUrl, apiKey);
|
|
|
2389
2553
|
var server = new McpServer(
|
|
2390
2554
|
{
|
|
2391
2555
|
name: "naumu",
|
|
2392
|
-
version:
|
|
2556
|
+
version: NAUMU_MCP_VERSION
|
|
2393
2557
|
},
|
|
2394
2558
|
{
|
|
2395
2559
|
instructions: NAUMU_INSTRUCTIONS
|
package/package.json
CHANGED