@naumu/mcp 0.12.1 → 0.14.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 +324 -173
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -162,6 +162,7 @@ Both transports expose the same tool surface. Tools marked **bot** are only regi
|
|
|
162
162
|
| `naumu_note_delete_section` | Delete a heading and its body (destructive) |
|
|
163
163
|
| `naumu_note_replace` | Replace an entire note's content (destructive) |
|
|
164
164
|
| `naumu_note_find_replace` | Literal find and replace within a note |
|
|
165
|
+
| `naumu_note_batch` | Apply several note edits in ONE write (one transaction, one update event) |
|
|
165
166
|
|
|
166
167
|
### Attachments
|
|
167
168
|
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var NaumuApiError = class extends Error {
|
|
|
15
15
|
status;
|
|
16
16
|
upstreamMessage;
|
|
17
17
|
};
|
|
18
|
-
function safeErrorMessage(status, upstream) {
|
|
18
|
+
function safeErrorMessage(status, upstream, retryAfter) {
|
|
19
19
|
if (status === 400 || status === 422) return upstream;
|
|
20
20
|
if (status === 401) return "Unauthorized \u2014 check your API key.";
|
|
21
21
|
if (status === 403 || status === 404) {
|
|
@@ -24,13 +24,24 @@ function safeErrorMessage(status, upstream) {
|
|
|
24
24
|
if (status === 409) return "Conflict with current state.";
|
|
25
25
|
if (status === 410) return "Resource is no longer available.";
|
|
26
26
|
if (status === 413) return "Payload too large.";
|
|
27
|
-
if (status === 429) return
|
|
27
|
+
if (status === 429) return `Rate limited - wait ${parseRetryAfterSeconds(retryAfter)}s and retry`;
|
|
28
28
|
if (status >= 500) return "Server error \u2014 try again shortly.";
|
|
29
29
|
return `Request failed with status ${status}.`;
|
|
30
30
|
}
|
|
31
|
+
var DEFAULT_RETRY_AFTER_SECONDS = 5;
|
|
32
|
+
function parseRetryAfterSeconds(retryAfter) {
|
|
33
|
+
if (!retryAfter) return DEFAULT_RETRY_AFTER_SECONDS;
|
|
34
|
+
const seconds = Number.parseInt(retryAfter, 10);
|
|
35
|
+
if (Number.isFinite(seconds) && seconds > 0) return seconds;
|
|
36
|
+
const dateMs = Date.parse(retryAfter);
|
|
37
|
+
if (Number.isFinite(dateMs)) {
|
|
38
|
+
return Math.max(1, Math.ceil((dateMs - Date.now()) / 1e3));
|
|
39
|
+
}
|
|
40
|
+
return DEFAULT_RETRY_AFTER_SECONDS;
|
|
41
|
+
}
|
|
31
42
|
|
|
32
43
|
// ../mcp-core/src/version.ts
|
|
33
|
-
var NAUMU_MCP_VERSION = "0.
|
|
44
|
+
var NAUMU_MCP_VERSION = "0.14.0";
|
|
34
45
|
|
|
35
46
|
// ../mcp-core/src/client.ts
|
|
36
47
|
var HEADER_VALUE_MAX_LENGTH = 100;
|
|
@@ -38,6 +49,12 @@ var sanitizeHeaderValue = (value) => {
|
|
|
38
49
|
const cleaned = value.replace(/[^\x20-\x7e]/g, "").trim();
|
|
39
50
|
return cleaned.length > 0 ? cleaned.slice(0, HEADER_VALUE_MAX_LENGTH) : void 0;
|
|
40
51
|
};
|
|
52
|
+
function firstString(...candidates) {
|
|
53
|
+
for (const candidate of candidates) {
|
|
54
|
+
if (typeof candidate === "string" && candidate.length > 0) return candidate;
|
|
55
|
+
}
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
41
58
|
var NaumuClient = class {
|
|
42
59
|
baseUrl;
|
|
43
60
|
apiKey;
|
|
@@ -102,14 +119,18 @@ var NaumuClient = class {
|
|
|
102
119
|
let upstream;
|
|
103
120
|
try {
|
|
104
121
|
const json = JSON.parse(text);
|
|
105
|
-
upstream = json
|
|
122
|
+
upstream = firstString(json?.error, json?.message) ?? text;
|
|
106
123
|
} catch {
|
|
107
124
|
upstream = text;
|
|
108
125
|
}
|
|
109
126
|
console.error(
|
|
110
127
|
`[mcp] upstream ${res.status}: ${upstream.slice(0, 500)}`
|
|
111
128
|
);
|
|
112
|
-
throw new NaumuApiError(
|
|
129
|
+
throw new NaumuApiError(
|
|
130
|
+
res.status,
|
|
131
|
+
safeErrorMessage(res.status, upstream, res.headers.get("retry-after")),
|
|
132
|
+
upstream
|
|
133
|
+
);
|
|
113
134
|
}
|
|
114
135
|
return res.json();
|
|
115
136
|
}
|
|
@@ -132,7 +153,7 @@ var NaumuClient = class {
|
|
|
132
153
|
);
|
|
133
154
|
throw new NaumuApiError(
|
|
134
155
|
res.status,
|
|
135
|
-
safeErrorMessage(res.status, upstream),
|
|
156
|
+
safeErrorMessage(res.status, upstream, res.headers.get("retry-after")),
|
|
136
157
|
upstream
|
|
137
158
|
);
|
|
138
159
|
}
|
|
@@ -165,7 +186,7 @@ var NaumuClient = class {
|
|
|
165
186
|
);
|
|
166
187
|
throw new NaumuApiError(
|
|
167
188
|
res.status,
|
|
168
|
-
safeErrorMessage(res.status, upstream),
|
|
189
|
+
safeErrorMessage(res.status, upstream, res.headers.get("retry-after")),
|
|
169
190
|
upstream
|
|
170
191
|
);
|
|
171
192
|
}
|
|
@@ -198,7 +219,7 @@ var NaumuClient = class {
|
|
|
198
219
|
// ../mcp-core/src/instructions.ts
|
|
199
220
|
var NAUMU_INSTRUCTIONS = `The Naumu MCP server gives structured access to Naumu knowledge graphs (also called spaces). Prefer these tools over WebFetch whenever the user mentions a naumu.ai URL \u2014 Naumu pages are client-rendered React, so WebFetch returns an empty shell with no data.
|
|
200
221
|
|
|
201
|
-
Getting information about a space: use naumu_ask. It puts your question to the @Naumu agent (which has full read access and inspects the graph for you) and returns a synthesised, node-grounded answer with the exact source node ids and a confidence hint, in a single call. It is the authoritative answer for what is in a space, what is new or recently changed, how something works, or any summary - present it and its sources directly. Do NOT then re-read the graph yourself with naumu_get_schema, naumu_filter, naumu_get_node or fetch to verify or sanity-check the answer: that repeats work naumu_ask already did and is dramatically slower (it can turn a 40-second answer into minutes). Trust the answer and its cited sources. Reach for a granular read only to fetch one specific node the answer pointed to, or for a need naumu_ask genuinely cannot serve (naumu_search to locate nodes by meaning, naumu_list_threads + naumu_read_thread for conversation history). Recording the question and answer as a visible conversation in the space is expected and useful, so do not avoid naumu_ask to prevent creating a thread. To hand @Naumu work to carry out in the background (add knowledge, make changes, record a status update), use naumu_delegate.
|
|
222
|
+
Getting information about a space: use naumu_ask. It puts your question to the @Naumu agent (which has full read access and inspects the graph for you) and returns a synthesised, node-grounded answer with the exact source node ids and a confidence hint, in a single call. It is the authoritative answer for what is in a space, what is new or recently changed, how something works, or any summary - present it and its sources directly. Do NOT then re-read the graph yourself with naumu_get_schema, naumu_filter, naumu_get_node or fetch to verify or sanity-check the answer: that repeats work naumu_ask already did and is dramatically slower (it can turn a 40-second answer into minutes). Trust the answer and its cited sources. Reach for a granular read only to fetch one specific node the answer pointed to, or for a need naumu_ask genuinely cannot serve (naumu_search to locate nodes by meaning, naumu_list_threads + naumu_read_thread for conversation history). To wait for the next reply in a thread, use naumu_wait_for_activity instead of re-reading it on a timer - it blocks server-side until a new message lands. Recording the question and answer as a visible conversation in the space is expected and useful, so do not avoid naumu_ask to prevent creating a thread. To hand @Naumu work to carry out in the background (add knowledge, make changes, record a status update), use naumu_delegate.
|
|
202
223
|
|
|
203
224
|
IMPORTANT: graphId is a UUID (e.g. "0464cbfa-60ca-41b3-ac8f-bbeb8243a193"). The value in the URL right after /spaces/ is a slug (e.g. "naumu-0464cbfa"), NOT the graphId. You must resolve the slug to a graphId first.
|
|
204
225
|
|
|
@@ -1171,7 +1192,7 @@ function registerPostMessage(server2, client2) {
|
|
|
1171
1192
|
idempotentHint: false,
|
|
1172
1193
|
openWorldHint: false
|
|
1173
1194
|
},
|
|
1174
|
-
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).
|
|
1195
|
+
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). A text @-mention renders as a pill but does NOT notify or add a non-participant \u2014 to loop a person in so they get notified, use naumu_add_participants after posting. 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. By default this only appends text: @Naumu is NOT summoned unless the thread auto-responds or you mention it, so nothing is committed to the graph. Pass `invokeAgent: true` when you need @Naumu to act on the message (record a work-log entry, file a status update); it summons @Naumu unconditionally, even in paused threads, and @Naumu works in the background - poll naumu_read_thread to see what it did. To get a synthesised answer from @Naumu, use naumu_ask.',
|
|
1175
1196
|
inputSchema: z20.object({
|
|
1176
1197
|
threadId: z20.string().describe("The thread ID to post into. You must be a participant in this thread."),
|
|
1177
1198
|
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.'),
|
|
@@ -1208,25 +1229,69 @@ function registerPostMessage(server2, client2) {
|
|
|
1208
1229
|
);
|
|
1209
1230
|
}
|
|
1210
1231
|
|
|
1211
|
-
// ../mcp-core/src/tools/
|
|
1232
|
+
// ../mcp-core/src/tools/add-participants.ts
|
|
1212
1233
|
import { z as z21 } from "zod";
|
|
1234
|
+
function registerAddParticipants(server2, client2) {
|
|
1235
|
+
server2.registerTool(
|
|
1236
|
+
"naumu_add_participants",
|
|
1237
|
+
{
|
|
1238
|
+
title: "Add Participants",
|
|
1239
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
1240
|
+
description: 'Add space members to a Naumu thread as participants. This is how you loop someone into a conversation so they actually get notified: each newly added person gets an "added you to this thread" notification and a "has joined the conversation" system note, and the thread lands in their inbox. Text @-mentions in naumu_post_message do NOT notify or add non-participants \u2014 after posting a handoff or a message addressed to someone, add them here. Pass userIds (UUIDs) from naumu_list_members or naumu_get_thread participantDetails; emails are not accepted. Adding is idempotent \u2014 already-present participants are skipped silently.',
|
|
1241
|
+
inputSchema: z21.object({
|
|
1242
|
+
threadId: z21.string().describe("The thread ID to add participants to. You must be able to manage this thread."),
|
|
1243
|
+
userIds: z21.array(z21.string()).min(1).max(25).describe("User ids (UUIDs) of space members to add, from naumu_list_members or naumu_get_thread."),
|
|
1244
|
+
role: z21.enum(["editor", "viewer"]).optional().describe("Thread role for the added participants. Defaults to editor (can post); viewer is read-only.")
|
|
1245
|
+
})
|
|
1246
|
+
},
|
|
1247
|
+
async ({ threadId, userIds, role }) => {
|
|
1248
|
+
try {
|
|
1249
|
+
const data = await client2.patch(`/api/threads/${threadId}`, {
|
|
1250
|
+
addParticipants: userIds.map((userId) => ({ userId, role: role ?? "editor" }))
|
|
1251
|
+
});
|
|
1252
|
+
const participants = data?.participantDetails ?? data?.participantEmails ?? data;
|
|
1253
|
+
return {
|
|
1254
|
+
content: [
|
|
1255
|
+
{
|
|
1256
|
+
type: "text",
|
|
1257
|
+
text: JSON.stringify({ threadId, participants }, null, 2)
|
|
1258
|
+
}
|
|
1259
|
+
]
|
|
1260
|
+
};
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1263
|
+
return {
|
|
1264
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1265
|
+
isError: true
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// ../mcp-core/src/tools/read-thread.ts
|
|
1273
|
+
import { z as z22 } from "zod";
|
|
1213
1274
|
function registerReadThread(server2, client2) {
|
|
1214
1275
|
server2.registerTool(
|
|
1215
1276
|
"naumu_read_thread",
|
|
1216
1277
|
{
|
|
1217
1278
|
title: "Read Thread",
|
|
1218
1279
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1219
|
-
description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first
|
|
1220
|
-
inputSchema:
|
|
1221
|
-
threadId:
|
|
1222
|
-
before:
|
|
1223
|
-
|
|
1280
|
+
description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first, or oldest-first when you pass `after`; each message carries a `status` (`processing` while @Naumu is still composing, `complete` when done). Use this to pick up an answer after naumu_ask returns status "processing", or to read what naumu_delegate produced. Use `before` (timestamp ms) to page further back, or `after` (timestamp ms) to catch up on what arrived since you last looked. To wait for the next message instead of re-reading on a timer, use naumu_wait_for_activity. Default page size 50, max 200. Agent messages carry `memoryScope.line`, a one-line statement of which Memory scope the answer used; surface it under the answer. To read or download a message attachment, pass its `attachments[].id` to naumu_get_attachment.',
|
|
1281
|
+
inputSchema: z22.object({
|
|
1282
|
+
threadId: z22.string().describe("The thread ID to read from."),
|
|
1283
|
+
before: z22.number().optional().describe("Unix timestamp in milliseconds. Returns messages strictly older than this. Omit for the newest page."),
|
|
1284
|
+
after: z22.number().optional().describe("Unix timestamp in milliseconds. Returns only messages newer than this, oldest-first. Mutually exclusive with `before`."),
|
|
1285
|
+
afterId: z22.string().optional().describe("Message id that goes with `after` - the `latestId` from naumu_wait_for_activity. Only meaningful alongside `after`. With it, messages sharing the `after` millisecond are compared by id instead of being skipped."),
|
|
1286
|
+
limit: z22.number().int().min(1).max(200).optional().describe("Page size, default 50, max 200.")
|
|
1224
1287
|
})
|
|
1225
1288
|
},
|
|
1226
|
-
async ({ threadId, before, limit }) => {
|
|
1289
|
+
async ({ threadId, before, after, afterId, limit }) => {
|
|
1227
1290
|
try {
|
|
1228
1291
|
const params = new URLSearchParams();
|
|
1229
1292
|
if (before !== void 0) params.set("before", String(before));
|
|
1293
|
+
if (after !== void 0) params.set("after", String(after));
|
|
1294
|
+
if (afterId !== void 0) params.set("afterId", afterId);
|
|
1230
1295
|
if (limit !== void 0) params.set("limit", String(limit));
|
|
1231
1296
|
const qs = params.toString();
|
|
1232
1297
|
const path = `/api/threads/${threadId}/messages${qs ? `?${qs}` : ""}`;
|
|
@@ -1245,8 +1310,56 @@ function registerReadThread(server2, client2) {
|
|
|
1245
1310
|
);
|
|
1246
1311
|
}
|
|
1247
1312
|
|
|
1313
|
+
// ../mcp-core/src/tools/wait-for-activity.ts
|
|
1314
|
+
import { z as z23 } from "zod";
|
|
1315
|
+
function registerWaitForActivity(server2, client2) {
|
|
1316
|
+
server2.registerTool(
|
|
1317
|
+
"naumu_wait_for_activity",
|
|
1318
|
+
{
|
|
1319
|
+
title: "Wait For Thread Activity",
|
|
1320
|
+
// Pure long-poll read: parks on the server and returns messages, never
|
|
1321
|
+
// writes. Repeating the same `after` returns the same messages.
|
|
1322
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1323
|
+
description: 'Wait for the next messages in a Naumu thread. The call blocks for up to `timeoutSec` seconds server-side and returns the moment something new arrives, so this is the cheap way to attend a thread - use it instead of calling naumu_read_thread on a timer.\n\nReturns `{ messages, latest, latestId, timedOut }`. `messages` is oldest-first and each one carries `senderKind`: "self" (you), "user" (a human), "agent" (a bot or @Naumu), "system" (a system notice). Reply only to "user" messages unless your task says otherwise; ignore "agent" and "system" ones.\n\nTo keep attending, loop: call again with `after` = the `latest` you just received AND `afterId` = the `latestId`. Passing both is what stops two messages written in the same millisecond from being lost. Never reuse the old cursor after receiving messages - you will get the same ones back. If `timedOut` is true and `messages` is empty, nothing happened: call again with the same cursor.\n\nIf a returned message has `status: "processing"`, the reply is still being written - it is a placeholder that gets UPDATED in place, so waiting again will never show you the final text. Re-read that message with naumu_read_thread instead (pass `after` = its timestamp minus 1, or page back to it) until its status is `complete`.\n\nGet your first `after`/`afterId` from the newest message in naumu_read_thread. Before composing a reply call naumu_typing, then post it with naumu_post_message.',
|
|
1324
|
+
inputSchema: z23.object({
|
|
1325
|
+
threadId: z23.string().describe("The thread ID to wait on. You must be a participant."),
|
|
1326
|
+
after: z23.number().describe(
|
|
1327
|
+
"Unix timestamp in milliseconds. Only messages newer than this count. Use the `latest` value from the previous call, or the newest message timestamp from naumu_read_thread."
|
|
1328
|
+
),
|
|
1329
|
+
afterId: z23.string().optional().describe(
|
|
1330
|
+
"Message id that goes with `after` - the `latestId` from the previous call. Only meaningful alongside `after`. With it, messages sharing the `after` millisecond are compared by id instead of being skipped, so nothing written in the same millisecond is lost."
|
|
1331
|
+
),
|
|
1332
|
+
timeoutSec: z23.number().int().min(1).max(25).default(20).describe("Seconds to block before returning empty, 1 to 25, default 20."),
|
|
1333
|
+
excludeSelf: z23.boolean().default(true).describe(
|
|
1334
|
+
'Ignore your own messages (senderKind "self") when deciding whether something new arrived. Default true - keep it on so your own reply does not end the next wait immediately.'
|
|
1335
|
+
)
|
|
1336
|
+
})
|
|
1337
|
+
},
|
|
1338
|
+
async ({ threadId, after, afterId, timeoutSec, excludeSelf }) => {
|
|
1339
|
+
try {
|
|
1340
|
+
const params = new URLSearchParams();
|
|
1341
|
+
params.set("after", String(after));
|
|
1342
|
+
if (afterId !== void 0) params.set("afterId", afterId);
|
|
1343
|
+
if (timeoutSec !== void 0) params.set("timeoutSec", String(timeoutSec));
|
|
1344
|
+
if (excludeSelf !== void 0) params.set("excludeSelf", String(excludeSelf));
|
|
1345
|
+
const path = `/api/threads/${threadId}/messages/wait?${params.toString()}`;
|
|
1346
|
+
const data = await client2.get(path);
|
|
1347
|
+
return {
|
|
1348
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
1349
|
+
};
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1352
|
+
return {
|
|
1353
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1354
|
+
isError: true
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1248
1361
|
// ../mcp-core/src/tools/whoami.ts
|
|
1249
|
-
import { z as
|
|
1362
|
+
import { z as z24 } from "zod";
|
|
1250
1363
|
function registerWhoami(server2, client2) {
|
|
1251
1364
|
server2.registerTool(
|
|
1252
1365
|
"naumu_whoami",
|
|
@@ -1254,7 +1367,7 @@ function registerWhoami(server2, client2) {
|
|
|
1254
1367
|
title: "Who Am I",
|
|
1255
1368
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1256
1369
|
description: 'Return who the calling key is, so you can bootstrap before the first real operation. A bot identity key returns its Identity row (id, graphId, name, instructions, allowedTools) plus its curated MCP tool manifest. 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. The live tool list is already available from tools/list, so it is not repeated here. No arguments. Always available regardless of the permission grid.',
|
|
1257
|
-
inputSchema:
|
|
1370
|
+
inputSchema: z24.object({})
|
|
1258
1371
|
},
|
|
1259
1372
|
async () => {
|
|
1260
1373
|
try {
|
|
@@ -1274,7 +1387,7 @@ function registerWhoami(server2, client2) {
|
|
|
1274
1387
|
}
|
|
1275
1388
|
|
|
1276
1389
|
// ../mcp-core/src/tools/list-threads.ts
|
|
1277
|
-
import { z as
|
|
1390
|
+
import { z as z25 } from "zod";
|
|
1278
1391
|
function sanitizeThreadParticipants(thread) {
|
|
1279
1392
|
if (!thread || typeof thread !== "object" || !("participantEmails" in thread)) {
|
|
1280
1393
|
return thread;
|
|
@@ -1289,11 +1402,11 @@ function registerListThreads(server2, client2) {
|
|
|
1289
1402
|
title: "List Threads",
|
|
1290
1403
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1291
1404
|
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). Pass `nodeId` alongside `graphId` to list only the conversations tied to that node (attached, or that created/modified it). 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.",
|
|
1292
|
-
inputSchema:
|
|
1293
|
-
graphId:
|
|
1294
|
-
nodeId:
|
|
1295
|
-
cursor:
|
|
1296
|
-
limit:
|
|
1405
|
+
inputSchema: z25.object({
|
|
1406
|
+
graphId: z25.string().optional().describe("Graph (space) ID. Required for user API keys; omit for bot identity keys (defaults to your own graph)."),
|
|
1407
|
+
nodeId: z25.string().optional().describe("Scope the listing to conversations tied to this node. Requires `graphId`."),
|
|
1408
|
+
cursor: z25.number().int().optional().describe("Unix timestamp ms \u2014 returns threads with `lastActivityAt` strictly older than this. Omit for the newest page."),
|
|
1409
|
+
limit: z25.number().int().min(1).max(200).optional().describe("Page size, default 50, max 200.")
|
|
1297
1410
|
})
|
|
1298
1411
|
},
|
|
1299
1412
|
async ({ graphId, nodeId, cursor, limit }) => {
|
|
@@ -1344,7 +1457,7 @@ function registerListThreads(server2, client2) {
|
|
|
1344
1457
|
}
|
|
1345
1458
|
|
|
1346
1459
|
// ../mcp-core/src/tools/list-topics.ts
|
|
1347
|
-
import { z as
|
|
1460
|
+
import { z as z26 } from "zod";
|
|
1348
1461
|
function toFilingDestination(topic) {
|
|
1349
1462
|
if (!topic || typeof topic !== "object") return null;
|
|
1350
1463
|
const t = topic;
|
|
@@ -1365,8 +1478,8 @@ function registerListTopics(server2, client2) {
|
|
|
1365
1478
|
title: "List Topics",
|
|
1366
1479
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1367
1480
|
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.",
|
|
1368
|
-
inputSchema:
|
|
1369
|
-
graphId:
|
|
1481
|
+
inputSchema: z26.object({
|
|
1482
|
+
graphId: z26.string().describe("The space (graph) id to list topics for.")
|
|
1370
1483
|
})
|
|
1371
1484
|
},
|
|
1372
1485
|
async ({ graphId }) => {
|
|
@@ -1389,7 +1502,7 @@ function registerListTopics(server2, client2) {
|
|
|
1389
1502
|
}
|
|
1390
1503
|
|
|
1391
1504
|
// ../mcp-core/src/tools/create-topic.ts
|
|
1392
|
-
import { z as
|
|
1505
|
+
import { z as z27 } from "zod";
|
|
1393
1506
|
var TOPIC_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
1394
1507
|
var TOPIC_NAME_MAX_LENGTH = 50;
|
|
1395
1508
|
var RESERVED_TOPIC_NAMES = [
|
|
@@ -1423,18 +1536,18 @@ function registerCreateTopic(server2, client2) {
|
|
|
1423
1536
|
title: "Create Topic",
|
|
1424
1537
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1425
1538
|
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.',
|
|
1426
|
-
inputSchema:
|
|
1427
|
-
graphId:
|
|
1428
|
-
name:
|
|
1539
|
+
inputSchema: z27.object({
|
|
1540
|
+
graphId: z27.string().describe("The space (graph) id to create the topic in."),
|
|
1541
|
+
name: z27.string().min(1).describe(
|
|
1429
1542
|
'Topic name in channel-slug form: lowercase letters, numbers and "-" only, max 50 chars, not a reserved name. Unique per space (case-insensitive).'
|
|
1430
1543
|
),
|
|
1431
|
-
color:
|
|
1432
|
-
visibilityMode:
|
|
1544
|
+
color: z27.string().optional().describe("Optional named color token for the topic badge. Omit unless the user asked for a specific color."),
|
|
1545
|
+
visibilityMode: z27.enum(["default", "open", "closed"]).optional().describe(
|
|
1433
1546
|
'Who can see and join the topic. "open" (the default) lets any space member join, "default" is the space default, "closed" is invite-only.'
|
|
1434
1547
|
),
|
|
1435
|
-
openToWeb:
|
|
1436
|
-
webParticipation:
|
|
1437
|
-
memberIds:
|
|
1548
|
+
openToWeb: z27.boolean().optional().describe('Expose the topic publicly on the web. Invalid together with visibilityMode "closed".'),
|
|
1549
|
+
webParticipation: z27.enum(["participate", "view-only"]).optional().describe('What public web visitors may do when openToWeb is true. Defaults to "view-only".'),
|
|
1550
|
+
memberIds: z27.array(z27.string()).optional().describe(
|
|
1438
1551
|
"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."
|
|
1439
1552
|
)
|
|
1440
1553
|
})
|
|
@@ -1487,7 +1600,7 @@ function registerCreateTopic(server2, client2) {
|
|
|
1487
1600
|
}
|
|
1488
1601
|
|
|
1489
1602
|
// ../mcp-core/src/tools/get-thread.ts
|
|
1490
|
-
import { z as
|
|
1603
|
+
import { z as z28 } from "zod";
|
|
1491
1604
|
function sanitizeThreadParticipants2(thread) {
|
|
1492
1605
|
if (!thread || typeof thread !== "object" || !("participantEmails" in thread)) {
|
|
1493
1606
|
return thread;
|
|
@@ -1502,8 +1615,8 @@ function registerGetThread(server2, client2) {
|
|
|
1502
1615
|
title: "Get Thread",
|
|
1503
1616
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1504
1617
|
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.",
|
|
1505
|
-
inputSchema:
|
|
1506
|
-
threadId:
|
|
1618
|
+
inputSchema: z28.object({
|
|
1619
|
+
threadId: z28.string().describe("The thread ID to fetch.")
|
|
1507
1620
|
})
|
|
1508
1621
|
},
|
|
1509
1622
|
async ({ threadId }) => {
|
|
@@ -1525,7 +1638,7 @@ function registerGetThread(server2, client2) {
|
|
|
1525
1638
|
}
|
|
1526
1639
|
|
|
1527
1640
|
// ../mcp-core/src/tools/create-thread.ts
|
|
1528
|
-
import { z as
|
|
1641
|
+
import { z as z29 } from "zod";
|
|
1529
1642
|
function registerCreateThread(server2, client2) {
|
|
1530
1643
|
server2.registerTool(
|
|
1531
1644
|
"naumu_create_thread",
|
|
@@ -1540,23 +1653,23 @@ function registerCreateThread(server2, client2) {
|
|
|
1540
1653
|
openWorldHint: false
|
|
1541
1654
|
},
|
|
1542
1655
|
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.",
|
|
1543
|
-
inputSchema:
|
|
1544
|
-
title:
|
|
1545
|
-
participants:
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
type:
|
|
1549
|
-
userId:
|
|
1656
|
+
inputSchema: z29.object({
|
|
1657
|
+
title: z29.string().min(1).max(200).optional().describe('Thread title shown in the sidebar. If omitted, Naumu generates a default like "Conversation YYYY-MM-DD".'),
|
|
1658
|
+
participants: z29.array(
|
|
1659
|
+
z29.discriminatedUnion("type", [
|
|
1660
|
+
z29.object({
|
|
1661
|
+
type: z29.literal("user"),
|
|
1662
|
+
userId: z29.string().min(1).describe("User UUID \u2014 get these from `naumu_get_thread`/`naumu_read_thread` participant rosters or webhook payloads.")
|
|
1550
1663
|
}),
|
|
1551
|
-
|
|
1552
|
-
type:
|
|
1553
|
-
identityId:
|
|
1664
|
+
z29.object({
|
|
1665
|
+
type: z29.literal("identity"),
|
|
1666
|
+
identityId: z29.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.")
|
|
1554
1667
|
})
|
|
1555
1668
|
])
|
|
1556
1669
|
).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."),
|
|
1557
|
-
initialMessage:
|
|
1558
|
-
visibility:
|
|
1559
|
-
topicIds:
|
|
1670
|
+
initialMessage: z29.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."),
|
|
1671
|
+
visibility: z29.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."),
|
|
1672
|
+
topicIds: z29.array(z29.string()).max(8).optional().describe(
|
|
1560
1673
|
"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."
|
|
1561
1674
|
)
|
|
1562
1675
|
})
|
|
@@ -1585,7 +1698,7 @@ function registerCreateThread(server2, client2) {
|
|
|
1585
1698
|
}
|
|
1586
1699
|
|
|
1587
1700
|
// ../mcp-core/src/tools/request-attachment-upload.ts
|
|
1588
|
-
import { z as
|
|
1701
|
+
import { z as z30 } from "zod";
|
|
1589
1702
|
function registerRequestAttachmentUpload(server2, client2) {
|
|
1590
1703
|
server2.registerTool(
|
|
1591
1704
|
"naumu_request_attachment_upload",
|
|
@@ -1593,15 +1706,15 @@ function registerRequestAttachmentUpload(server2, client2) {
|
|
|
1593
1706
|
title: "Request Attachment Upload",
|
|
1594
1707
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1595
1708
|
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.",
|
|
1596
|
-
inputSchema:
|
|
1597
|
-
graphId:
|
|
1598
|
-
threadId:
|
|
1599
|
-
noteId:
|
|
1600
|
-
canvasId:
|
|
1601
|
-
fileName:
|
|
1602
|
-
fileType:
|
|
1603
|
-
fileSize:
|
|
1604
|
-
audioDurationSec:
|
|
1709
|
+
inputSchema: z30.object({
|
|
1710
|
+
graphId: z30.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."),
|
|
1711
|
+
threadId: z30.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."),
|
|
1712
|
+
noteId: z30.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."),
|
|
1713
|
+
canvasId: z30.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."),
|
|
1714
|
+
fileName: z30.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."),
|
|
1715
|
+
fileType: z30.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."),
|
|
1716
|
+
fileSize: z30.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."),
|
|
1717
|
+
audioDurationSec: z30.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.")
|
|
1605
1718
|
}).refine(
|
|
1606
1719
|
(data) => [data.threadId, data.noteId, data.canvasId].filter((v) => v !== void 0).length === 1,
|
|
1607
1720
|
{ message: "Exactly one of threadId, noteId, or canvasId is required - pick the single destination this upload is for." }
|
|
@@ -1649,7 +1762,7 @@ function registerRequestAttachmentUpload(server2, client2) {
|
|
|
1649
1762
|
}
|
|
1650
1763
|
|
|
1651
1764
|
// ../mcp-core/src/tools/persist-canvas-attachment.ts
|
|
1652
|
-
import { z as
|
|
1765
|
+
import { z as z31 } from "zod";
|
|
1653
1766
|
function registerPersistCanvasAttachment(server2, client2) {
|
|
1654
1767
|
server2.registerTool(
|
|
1655
1768
|
"naumu_persist_canvas_attachment",
|
|
@@ -1657,8 +1770,8 @@ function registerPersistCanvasAttachment(server2, client2) {
|
|
|
1657
1770
|
title: "Persist Canvas Attachment",
|
|
1658
1771
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1659
1772
|
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.",
|
|
1660
|
-
inputSchema:
|
|
1661
|
-
attachmentId:
|
|
1773
|
+
inputSchema: z31.object({
|
|
1774
|
+
attachmentId: z31.string().min(1).describe("The `attachmentId` returned by `naumu_request_attachment_upload` for this canvas.")
|
|
1662
1775
|
})
|
|
1663
1776
|
},
|
|
1664
1777
|
async ({ attachmentId }) => {
|
|
@@ -1679,7 +1792,7 @@ function registerPersistCanvasAttachment(server2, client2) {
|
|
|
1679
1792
|
}
|
|
1680
1793
|
|
|
1681
1794
|
// ../mcp-core/src/tools/get-attachment.ts
|
|
1682
|
-
import { z as
|
|
1795
|
+
import { z as z32 } from "zod";
|
|
1683
1796
|
var DOWNLOAD_URL_TTL_SECONDS = 900;
|
|
1684
1797
|
var MAX_INLINE_PREVIEW_BYTES = 4 * 1024 * 1024;
|
|
1685
1798
|
function normalizeAttachmentId(input) {
|
|
@@ -1700,8 +1813,8 @@ function registerGetAttachment(server2, client2) {
|
|
|
1700
1813
|
title: "Get Attachment",
|
|
1701
1814
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1702
1815
|
description: "Read a chat attachment. Pass the `attachmentId` from a message's `attachments[].id` in naumu_read_thread (its `url` field works too - the id is extracted from it), and this resolves it into a short-lived download URL for the actual bytes.\n\nReturns a JSON text block with `{ attachmentId, downloadUrl, expiresInSeconds, note }`. Use `downloadUrl` EXACTLY as given:\n\u2022 GET it with no Authorization header - the URL itself is the auth, and adding one makes S3 answer 403.\n\u2022 It expires about 15 minutes after this call. Fetch it now; re-call this tool for a fresh URL rather than holding one.\n\u2022 Do not log it, quote it back to the user, or store it - it is a bearer capability for its whole lifetime.\n\nWhen the attachment is an image (or a video or PDF that has a generated poster), a downsized preview is also returned inline as an image block, so a visual attachment can often be understood without fetching anything. The preview is a thumbnail, not the original - fetch `downloadUrl` when you need full resolution or the exact file.\n\nAccess is checked the same way it is for a person: you only resolve attachments in threads you can already read.",
|
|
1703
|
-
inputSchema:
|
|
1704
|
-
attachmentId:
|
|
1816
|
+
inputSchema: z32.object({
|
|
1817
|
+
attachmentId: z32.string().min(1).describe("Attachment id, from `attachments[].id` on a message returned by naumu_read_thread. A full or relative download URL is also accepted - the id is extracted from its last path segment.")
|
|
1705
1818
|
})
|
|
1706
1819
|
},
|
|
1707
1820
|
async ({ attachmentId }) => {
|
|
@@ -1751,7 +1864,7 @@ function registerGetAttachment(server2, client2) {
|
|
|
1751
1864
|
}
|
|
1752
1865
|
|
|
1753
1866
|
// ../mcp-core/src/tools/add-reaction.ts
|
|
1754
|
-
import { z as
|
|
1867
|
+
import { z as z33 } from "zod";
|
|
1755
1868
|
function registerAddReaction(server2, client2) {
|
|
1756
1869
|
server2.registerTool(
|
|
1757
1870
|
"naumu_add_reaction",
|
|
@@ -1759,10 +1872,10 @@ function registerAddReaction(server2, client2) {
|
|
|
1759
1872
|
title: "Add Reaction",
|
|
1760
1873
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1761
1874
|
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.',
|
|
1762
|
-
inputSchema:
|
|
1763
|
-
threadId:
|
|
1764
|
-
messageId:
|
|
1765
|
-
emoji:
|
|
1875
|
+
inputSchema: z33.object({
|
|
1876
|
+
threadId: z33.string().describe("Thread containing the message. You must be a participant."),
|
|
1877
|
+
messageId: z33.string().describe("The message to react to."),
|
|
1878
|
+
emoji: z33.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.')
|
|
1766
1879
|
})
|
|
1767
1880
|
},
|
|
1768
1881
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1786,7 +1899,7 @@ function registerAddReaction(server2, client2) {
|
|
|
1786
1899
|
}
|
|
1787
1900
|
|
|
1788
1901
|
// ../mcp-core/src/tools/remove-reaction.ts
|
|
1789
|
-
import { z as
|
|
1902
|
+
import { z as z34 } from "zod";
|
|
1790
1903
|
function registerRemoveReaction(server2, client2) {
|
|
1791
1904
|
server2.registerTool(
|
|
1792
1905
|
"naumu_remove_reaction",
|
|
@@ -1794,10 +1907,10 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1794
1907
|
title: "Remove Reaction",
|
|
1795
1908
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1796
1909
|
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.",
|
|
1797
|
-
inputSchema:
|
|
1798
|
-
threadId:
|
|
1799
|
-
messageId:
|
|
1800
|
-
emoji:
|
|
1910
|
+
inputSchema: z34.object({
|
|
1911
|
+
threadId: z34.string().describe("Thread containing the message. You must be a participant."),
|
|
1912
|
+
messageId: z34.string().describe("The message to remove your reaction from."),
|
|
1913
|
+
emoji: z34.string().min(1).describe("Emoji character to remove (must match what you originally reacted with).")
|
|
1801
1914
|
})
|
|
1802
1915
|
},
|
|
1803
1916
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1821,7 +1934,7 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1821
1934
|
}
|
|
1822
1935
|
|
|
1823
1936
|
// ../mcp-core/src/tools/naumu-typing.ts
|
|
1824
|
-
import { z as
|
|
1937
|
+
import { z as z35 } from "zod";
|
|
1825
1938
|
function registerNaumuTyping(server2, client2) {
|
|
1826
1939
|
server2.registerTool(
|
|
1827
1940
|
"naumu_typing",
|
|
@@ -1832,9 +1945,9 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1832
1945
|
// repeating the same state is a no-op renew, so idempotent.
|
|
1833
1946
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1834
1947
|
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.',
|
|
1835
|
-
inputSchema:
|
|
1836
|
-
threadId:
|
|
1837
|
-
state:
|
|
1948
|
+
inputSchema: z35.object({
|
|
1949
|
+
threadId: z35.string().describe("The thread ID to set typing in. You must be a participant."),
|
|
1950
|
+
state: z35.enum(["start", "stop"]).describe('"start" acquires/renews the lease; "stop" ends it and clears the pill immediately.')
|
|
1838
1951
|
})
|
|
1839
1952
|
},
|
|
1840
1953
|
async ({ threadId, state }) => {
|
|
@@ -1855,7 +1968,7 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1855
1968
|
}
|
|
1856
1969
|
|
|
1857
1970
|
// ../mcp-core/src/tools/note-read.ts
|
|
1858
|
-
import { z as
|
|
1971
|
+
import { z as z36 } from "zod";
|
|
1859
1972
|
function registerNoteRead(server2, client2) {
|
|
1860
1973
|
server2.registerTool(
|
|
1861
1974
|
"naumu_note_read",
|
|
@@ -1863,8 +1976,8 @@ function registerNoteRead(server2, client2) {
|
|
|
1863
1976
|
title: "Read Note",
|
|
1864
1977
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1865
1978
|
description: "Read the current contents of a note as markdown, with its title and `connections` (the graph nodes the note is tied to); 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.",
|
|
1866
|
-
inputSchema:
|
|
1867
|
-
noteId:
|
|
1979
|
+
inputSchema: z36.object({
|
|
1980
|
+
noteId: z36.string().describe("The note (Thought) ID")
|
|
1868
1981
|
})
|
|
1869
1982
|
},
|
|
1870
1983
|
async ({ noteId }) => {
|
|
@@ -1877,7 +1990,7 @@ function registerNoteRead(server2, client2) {
|
|
|
1877
1990
|
}
|
|
1878
1991
|
|
|
1879
1992
|
// ../mcp-core/src/tools/note-append.ts
|
|
1880
|
-
import { z as
|
|
1993
|
+
import { z as z37 } from "zod";
|
|
1881
1994
|
function registerNoteAppend(server2, client2) {
|
|
1882
1995
|
server2.registerTool(
|
|
1883
1996
|
"naumu_note_append",
|
|
@@ -1885,9 +1998,9 @@ function registerNoteAppend(server2, client2) {
|
|
|
1885
1998
|
title: "Append to Note",
|
|
1886
1999
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1887
2000
|
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.",
|
|
1888
|
-
inputSchema:
|
|
1889
|
-
noteId:
|
|
1890
|
-
markdown:
|
|
2001
|
+
inputSchema: z37.object({
|
|
2002
|
+
noteId: z37.string().describe("The note (Thought) ID to append to"),
|
|
2003
|
+
markdown: z37.string().min(1).describe("Markdown content to append at the end of the note")
|
|
1891
2004
|
})
|
|
1892
2005
|
},
|
|
1893
2006
|
async ({ noteId, markdown }) => {
|
|
@@ -1900,7 +2013,7 @@ function registerNoteAppend(server2, client2) {
|
|
|
1900
2013
|
}
|
|
1901
2014
|
|
|
1902
2015
|
// ../mcp-core/src/tools/note-insert.ts
|
|
1903
|
-
import { z as
|
|
2016
|
+
import { z as z38 } from "zod";
|
|
1904
2017
|
function registerNoteInsert(server2, client2) {
|
|
1905
2018
|
server2.registerTool(
|
|
1906
2019
|
"naumu_note_insert",
|
|
@@ -1908,10 +2021,10 @@ function registerNoteInsert(server2, client2) {
|
|
|
1908
2021
|
title: "Insert After Heading",
|
|
1909
2022
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1910
2023
|
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.",
|
|
1911
|
-
inputSchema:
|
|
1912
|
-
noteId:
|
|
1913
|
-
headingText:
|
|
1914
|
-
markdown:
|
|
2024
|
+
inputSchema: z38.object({
|
|
2025
|
+
noteId: z38.string().describe("The note (Thought) ID"),
|
|
2026
|
+
headingText: z38.string().min(1).describe("Exact text of the heading whose section the new content follows"),
|
|
2027
|
+
markdown: z38.string().min(1).describe("Markdown content to insert at the end of that section")
|
|
1915
2028
|
})
|
|
1916
2029
|
},
|
|
1917
2030
|
async ({ noteId, headingText, markdown }) => {
|
|
@@ -1927,7 +2040,7 @@ function registerNoteInsert(server2, client2) {
|
|
|
1927
2040
|
}
|
|
1928
2041
|
|
|
1929
2042
|
// ../mcp-core/src/tools/note-replace-section.ts
|
|
1930
|
-
import { z as
|
|
2043
|
+
import { z as z39 } from "zod";
|
|
1931
2044
|
function registerNoteReplaceSection(server2, client2) {
|
|
1932
2045
|
server2.registerTool(
|
|
1933
2046
|
"naumu_note_replace_section",
|
|
@@ -1935,11 +2048,11 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1935
2048
|
title: "Replace Section",
|
|
1936
2049
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1937
2050
|
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.",
|
|
1938
|
-
inputSchema:
|
|
1939
|
-
noteId:
|
|
1940
|
-
headingText:
|
|
1941
|
-
markdown:
|
|
1942
|
-
keepHeading:
|
|
2051
|
+
inputSchema: z39.object({
|
|
2052
|
+
noteId: z39.string().describe("The note (Thought) ID"),
|
|
2053
|
+
headingText: z39.string().min(1).describe("Exact text of the heading anchoring the section"),
|
|
2054
|
+
markdown: z39.string().describe("Replacement markdown for the section body"),
|
|
2055
|
+
keepHeading: z39.boolean().optional().describe("Whether to keep the heading row itself. Default true.")
|
|
1943
2056
|
})
|
|
1944
2057
|
},
|
|
1945
2058
|
async ({ noteId, headingText, markdown, keepHeading }) => {
|
|
@@ -1956,7 +2069,7 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1956
2069
|
}
|
|
1957
2070
|
|
|
1958
2071
|
// ../mcp-core/src/tools/note-delete-section.ts
|
|
1959
|
-
import { z as
|
|
2072
|
+
import { z as z40 } from "zod";
|
|
1960
2073
|
function registerNoteDeleteSection(server2, client2) {
|
|
1961
2074
|
server2.registerTool(
|
|
1962
2075
|
"naumu_note_delete_section",
|
|
@@ -1964,9 +2077,9 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1964
2077
|
title: "Delete Section",
|
|
1965
2078
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1966
2079
|
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.",
|
|
1967
|
-
inputSchema:
|
|
1968
|
-
noteId:
|
|
1969
|
-
headingText:
|
|
2080
|
+
inputSchema: z40.object({
|
|
2081
|
+
noteId: z40.string().describe("The note (Thought) ID"),
|
|
2082
|
+
headingText: z40.string().min(1).describe("Exact text of the heading whose section will be deleted")
|
|
1970
2083
|
})
|
|
1971
2084
|
},
|
|
1972
2085
|
async ({ noteId, headingText }) => {
|
|
@@ -1981,7 +2094,7 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1981
2094
|
}
|
|
1982
2095
|
|
|
1983
2096
|
// ../mcp-core/src/tools/note-replace.ts
|
|
1984
|
-
import { z as
|
|
2097
|
+
import { z as z41 } from "zod";
|
|
1985
2098
|
function registerNoteReplace(server2, client2) {
|
|
1986
2099
|
server2.registerTool(
|
|
1987
2100
|
"naumu_note_replace",
|
|
@@ -1989,9 +2102,9 @@ function registerNoteReplace(server2, client2) {
|
|
|
1989
2102
|
title: "Replace Note",
|
|
1990
2103
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1991
2104
|
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.",
|
|
1992
|
-
inputSchema:
|
|
1993
|
-
noteId:
|
|
1994
|
-
markdown:
|
|
2105
|
+
inputSchema: z41.object({
|
|
2106
|
+
noteId: z41.string().describe("The note (Thought) ID"),
|
|
2107
|
+
markdown: z41.string().describe("New markdown content for the entire note")
|
|
1995
2108
|
})
|
|
1996
2109
|
},
|
|
1997
2110
|
async ({ noteId, markdown }) => {
|
|
@@ -2004,7 +2117,7 @@ function registerNoteReplace(server2, client2) {
|
|
|
2004
2117
|
}
|
|
2005
2118
|
|
|
2006
2119
|
// ../mcp-core/src/tools/note-find-replace.ts
|
|
2007
|
-
import { z as
|
|
2120
|
+
import { z as z42 } from "zod";
|
|
2008
2121
|
function registerNoteFindReplace(server2, client2) {
|
|
2009
2122
|
server2.registerTool(
|
|
2010
2123
|
"naumu_note_find_replace",
|
|
@@ -2012,11 +2125,11 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
2012
2125
|
title: "Find/Replace in Note",
|
|
2013
2126
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
2014
2127
|
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.",
|
|
2015
|
-
inputSchema:
|
|
2016
|
-
noteId:
|
|
2017
|
-
find:
|
|
2018
|
-
replace:
|
|
2019
|
-
all:
|
|
2128
|
+
inputSchema: z42.object({
|
|
2129
|
+
noteId: z42.string().describe("The note (Thought) ID"),
|
|
2130
|
+
find: z42.string().min(1).describe("Substring to search for. Literal - no regex."),
|
|
2131
|
+
replace: z42.string().describe("Replacement string. May be empty to delete the match."),
|
|
2132
|
+
all: z42.boolean().optional().describe("Replace all occurrences (default true). Pass false to replace only the first.")
|
|
2020
2133
|
})
|
|
2021
2134
|
},
|
|
2022
2135
|
async ({ noteId, find, replace, all }) => {
|
|
@@ -2032,8 +2145,43 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
2032
2145
|
);
|
|
2033
2146
|
}
|
|
2034
2147
|
|
|
2148
|
+
// ../mcp-core/src/tools/note-batch.ts
|
|
2149
|
+
import { z as z43 } from "zod";
|
|
2150
|
+
var MAX_BATCH_OPS = 20;
|
|
2151
|
+
var opSchema = z43.object({
|
|
2152
|
+
op: z43.enum(["append", "insertAfter", "replaceSection", "deleteSection", "replace", "findReplace"]).describe("Which edit to perform."),
|
|
2153
|
+
markdown: z43.string().optional().describe("Markdown payload. Required for append, insertAfter, replaceSection, replace."),
|
|
2154
|
+
heading: z43.string().optional().describe(
|
|
2155
|
+
'Target heading text. Required for insertAfter, replaceSection, deleteSection. Accepts the markdown form ("## Title") or the bare text.'
|
|
2156
|
+
),
|
|
2157
|
+
keepHeading: z43.boolean().optional().describe("replaceSection only: keep the heading row and replace just its body (default true)."),
|
|
2158
|
+
find: z43.string().optional().describe("findReplace only: literal substring to search for."),
|
|
2159
|
+
replace: z43.string().optional().describe("findReplace only: replacement string. May be empty to delete the match."),
|
|
2160
|
+
all: z43.boolean().optional().describe("findReplace only: replace every occurrence (default true).")
|
|
2161
|
+
}).describe("One edit in the batch.");
|
|
2162
|
+
function registerNoteBatch(server2, client2) {
|
|
2163
|
+
server2.registerTool(
|
|
2164
|
+
"naumu_note_batch",
|
|
2165
|
+
{
|
|
2166
|
+
title: "Batch Edit Note",
|
|
2167
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
2168
|
+
description: "Apply several edits to ONE note in a single write. This is the preferred way to make more than one change to the same note: the whole list runs in one document transaction, so readers see one update event instead of one per edit, and you pay one round trip instead of N. Each entry mirrors a single-op note tool - `append` {markdown}, `insertAfter` {heading, markdown}, `replaceSection` {heading, markdown, keepHeading?}, `deleteSection` {heading}, `replace` {markdown}, `findReplace` {find, replace, all?} - and they run in the order given, each seeing the result of the one before. Max " + MAX_BATCH_OPS + " ops. All-or-nothing: the batch is rehearsed first, so if any op fails (most often a heading that does not exist) the note is left completely untouched and the error names the failing index. Media works as in the single-op tools: write `` on its own line, with an id presigned by `naumu_request_attachment_upload` for this same `noteId`.",
|
|
2169
|
+
inputSchema: z43.object({
|
|
2170
|
+
noteId: z43.string().describe("The note (Thought) ID to edit"),
|
|
2171
|
+
ops: z43.array(opSchema).min(1).max(MAX_BATCH_OPS).describe(`Edits to apply, in order. Max ${MAX_BATCH_OPS}.`)
|
|
2172
|
+
})
|
|
2173
|
+
},
|
|
2174
|
+
async ({ noteId, ops }) => {
|
|
2175
|
+
const data = await client2.post(`/api/notes/${noteId}/batch`, { ops });
|
|
2176
|
+
return {
|
|
2177
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2035
2183
|
// ../mcp-core/src/tools/create-note.ts
|
|
2036
|
-
import { z as
|
|
2184
|
+
import { z as z44 } from "zod";
|
|
2037
2185
|
function registerCreateNote(server2, client2) {
|
|
2038
2186
|
server2.registerTool(
|
|
2039
2187
|
"naumu_create_note",
|
|
@@ -2041,12 +2189,12 @@ function registerCreateNote(server2, client2) {
|
|
|
2041
2189
|
title: "Create Note",
|
|
2042
2190
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
2043
2191
|
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.",
|
|
2044
|
-
inputSchema:
|
|
2045
|
-
graphId:
|
|
2046
|
-
title:
|
|
2047
|
-
markdown:
|
|
2048
|
-
sharedWithSpace:
|
|
2049
|
-
topicIds:
|
|
2192
|
+
inputSchema: z44.object({
|
|
2193
|
+
graphId: z44.string().describe("The graph ID to create the note in"),
|
|
2194
|
+
title: z44.string().optional().describe("Optional title for the note"),
|
|
2195
|
+
markdown: z44.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."),
|
|
2196
|
+
sharedWithSpace: z44.boolean().optional().describe("Share the note with everyone in the space. Omit (or false) to keep it private to its participants."),
|
|
2197
|
+
topicIds: z44.array(z44.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.")
|
|
2050
2198
|
})
|
|
2051
2199
|
},
|
|
2052
2200
|
async ({ graphId, title, markdown, sharedWithSpace, topicIds }) => {
|
|
@@ -2059,7 +2207,7 @@ function registerCreateNote(server2, client2) {
|
|
|
2059
2207
|
}
|
|
2060
2208
|
|
|
2061
2209
|
// ../mcp-core/src/tools/list-schema-violations.ts
|
|
2062
|
-
import { z as
|
|
2210
|
+
import { z as z45 } from "zod";
|
|
2063
2211
|
var DEFAULT_EXAMPLE_LIMIT = 5;
|
|
2064
2212
|
var rowsForKind = (violations, kind) => {
|
|
2065
2213
|
const rows = [];
|
|
@@ -2085,12 +2233,12 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
2085
2233
|
title: "List Schema Violations",
|
|
2086
2234
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2087
2235
|
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.",
|
|
2088
|
-
inputSchema:
|
|
2089
|
-
graphId:
|
|
2090
|
-
kind:
|
|
2236
|
+
inputSchema: z45.object({
|
|
2237
|
+
graphId: z45.string().describe("The graph ID"),
|
|
2238
|
+
kind: z45.string().optional().describe(
|
|
2091
2239
|
'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.'
|
|
2092
2240
|
),
|
|
2093
|
-
limit:
|
|
2241
|
+
limit: z45.number().int().min(1).optional().describe(
|
|
2094
2242
|
"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)."
|
|
2095
2243
|
)
|
|
2096
2244
|
})
|
|
@@ -2142,7 +2290,7 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
2142
2290
|
}
|
|
2143
2291
|
|
|
2144
2292
|
// ../mcp-core/src/tools/list-dense-nodes.ts
|
|
2145
|
-
import { z as
|
|
2293
|
+
import { z as z46 } from "zod";
|
|
2146
2294
|
function registerListDenseNodes(server2, client2) {
|
|
2147
2295
|
server2.registerTool(
|
|
2148
2296
|
"naumu_list_dense_nodes",
|
|
@@ -2150,10 +2298,10 @@ function registerListDenseNodes(server2, client2) {
|
|
|
2150
2298
|
title: "List Dense Nodes",
|
|
2151
2299
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2152
2300
|
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.',
|
|
2153
|
-
inputSchema:
|
|
2154
|
-
graphId:
|
|
2155
|
-
minConnections:
|
|
2156
|
-
nodeTypes:
|
|
2301
|
+
inputSchema: z46.object({
|
|
2302
|
+
graphId: z46.string().describe("The graph ID"),
|
|
2303
|
+
minConnections: z46.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."),
|
|
2304
|
+
nodeTypes: z46.array(z46.string()).optional().describe("Optional list of node types to restrict the scan to.")
|
|
2157
2305
|
})
|
|
2158
2306
|
},
|
|
2159
2307
|
async ({ graphId, minConnections, nodeTypes }) => {
|
|
@@ -2171,7 +2319,7 @@ function registerListDenseNodes(server2, client2) {
|
|
|
2171
2319
|
}
|
|
2172
2320
|
|
|
2173
2321
|
// ../mcp-core/src/tools/list-node-connections.ts
|
|
2174
|
-
import { z as
|
|
2322
|
+
import { z as z47 } from "zod";
|
|
2175
2323
|
function registerListNodeConnections(server2, client2) {
|
|
2176
2324
|
server2.registerTool(
|
|
2177
2325
|
"naumu_list_node_connections",
|
|
@@ -2179,11 +2327,11 @@ function registerListNodeConnections(server2, client2) {
|
|
|
2179
2327
|
title: "List Node Connections",
|
|
2180
2328
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2181
2329
|
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}}] }`.',
|
|
2182
|
-
inputSchema:
|
|
2183
|
-
graphId:
|
|
2184
|
-
nodeId:
|
|
2185
|
-
edgeType:
|
|
2186
|
-
direction:
|
|
2330
|
+
inputSchema: z47.object({
|
|
2331
|
+
graphId: z47.string().describe("The graph ID"),
|
|
2332
|
+
nodeId: z47.string().describe("The node ID to inspect"),
|
|
2333
|
+
edgeType: z47.string().optional().describe('Restrict to a specific relation label (e.g. "ASSOCIATED_WITH"). Case-insensitive; non-alphanum chars are normalized.'),
|
|
2334
|
+
direction: z47.enum(["in", "out", "both"]).optional().describe('Edge direction filter - "in" (incoming), "out" (outgoing), "both" (default).')
|
|
2187
2335
|
})
|
|
2188
2336
|
},
|
|
2189
2337
|
async ({ graphId, nodeId, edgeType, direction }) => {
|
|
@@ -2201,7 +2349,7 @@ function registerListNodeConnections(server2, client2) {
|
|
|
2201
2349
|
}
|
|
2202
2350
|
|
|
2203
2351
|
// ../mcp-core/src/tools/reparent.ts
|
|
2204
|
-
import { z as
|
|
2352
|
+
import { z as z48 } from "zod";
|
|
2205
2353
|
function registerReparent(server2, client2) {
|
|
2206
2354
|
server2.registerTool(
|
|
2207
2355
|
"naumu_reparent",
|
|
@@ -2209,11 +2357,11 @@ function registerReparent(server2, client2) {
|
|
|
2209
2357
|
title: "Reparent Node",
|
|
2210
2358
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2211
2359
|
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"}`.',
|
|
2212
|
-
inputSchema:
|
|
2213
|
-
graphId:
|
|
2214
|
-
nodeId:
|
|
2215
|
-
newParentId:
|
|
2216
|
-
newRelation:
|
|
2360
|
+
inputSchema: z48.object({
|
|
2361
|
+
graphId: z48.string().describe("The graph ID"),
|
|
2362
|
+
nodeId: z48.string().describe("The child node to reparent"),
|
|
2363
|
+
newParentId: z48.string().describe("The new parent node id"),
|
|
2364
|
+
newRelation: z48.string().describe('The new parent edge relation label (e.g. "PART_OF"). Must be valid per the schema for (child.type, relation, parent.type).')
|
|
2217
2365
|
})
|
|
2218
2366
|
},
|
|
2219
2367
|
async ({ graphId, nodeId, newParentId, newRelation }) => {
|
|
@@ -2229,7 +2377,7 @@ function registerReparent(server2, client2) {
|
|
|
2229
2377
|
}
|
|
2230
2378
|
|
|
2231
2379
|
// ../mcp-core/src/tools/batch-reparent.ts
|
|
2232
|
-
import { z as
|
|
2380
|
+
import { z as z49 } from "zod";
|
|
2233
2381
|
function registerBatchReparent(server2, client2) {
|
|
2234
2382
|
server2.registerTool(
|
|
2235
2383
|
"naumu_batch_reparent",
|
|
@@ -2237,11 +2385,11 @@ function registerBatchReparent(server2, client2) {
|
|
|
2237
2385
|
title: "Batch Reparent Nodes",
|
|
2238
2386
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2239
2387
|
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?}]`.',
|
|
2240
|
-
inputSchema:
|
|
2241
|
-
graphId:
|
|
2242
|
-
newParentId:
|
|
2243
|
-
newRelation:
|
|
2244
|
-
nodeIds:
|
|
2388
|
+
inputSchema: z49.object({
|
|
2389
|
+
graphId: z49.string().describe("The graph ID"),
|
|
2390
|
+
newParentId: z49.string().describe("Parent node id every nodeId in the batch will be parented to"),
|
|
2391
|
+
newRelation: z49.string().describe("Parent edge relation label (must be valid per schema for child.type \u2192 parent.type)"),
|
|
2392
|
+
nodeIds: z49.array(z49.string()).min(1).max(25).describe("1\u201325 child node ids to reparent under `newParentId`")
|
|
2245
2393
|
})
|
|
2246
2394
|
},
|
|
2247
2395
|
async ({ graphId, newParentId, newRelation, nodeIds }) => {
|
|
@@ -2258,7 +2406,7 @@ function registerBatchReparent(server2, client2) {
|
|
|
2258
2406
|
}
|
|
2259
2407
|
|
|
2260
2408
|
// ../mcp-core/src/tools/chatgpt-search.ts
|
|
2261
|
-
import { z as
|
|
2409
|
+
import { z as z50 } from "zod";
|
|
2262
2410
|
|
|
2263
2411
|
// ../mcp-core/src/public-origin.ts
|
|
2264
2412
|
function publicOrigin() {
|
|
@@ -2312,8 +2460,8 @@ function registerChatgptSearch(server2, client2) {
|
|
|
2312
2460
|
title: "Search",
|
|
2313
2461
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2314
2462
|
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.)",
|
|
2315
|
-
inputSchema:
|
|
2316
|
-
query:
|
|
2463
|
+
inputSchema: z50.object({
|
|
2464
|
+
query: z50.string().describe(
|
|
2317
2465
|
"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."
|
|
2318
2466
|
)
|
|
2319
2467
|
})
|
|
@@ -2347,7 +2495,7 @@ function registerChatgptSearch(server2, client2) {
|
|
|
2347
2495
|
}
|
|
2348
2496
|
|
|
2349
2497
|
// ../mcp-core/src/tools/chatgpt-fetch.ts
|
|
2350
|
-
import { z as
|
|
2498
|
+
import { z as z51 } from "zod";
|
|
2351
2499
|
var NON_ATTRIBUTE_PROPS = /* @__PURE__ */ new Set([
|
|
2352
2500
|
"id",
|
|
2353
2501
|
"label",
|
|
@@ -2441,8 +2589,8 @@ function registerChatgptFetch(server2, client2) {
|
|
|
2441
2589
|
title: "Fetch",
|
|
2442
2590
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2443
2591
|
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.",
|
|
2444
|
-
inputSchema:
|
|
2445
|
-
id:
|
|
2592
|
+
inputSchema: z51.object({
|
|
2593
|
+
id: z51.string().describe("A resource id from a previous `search` result, in the form `<graphId>:<nodeId>`.")
|
|
2446
2594
|
})
|
|
2447
2595
|
},
|
|
2448
2596
|
async ({ id }) => {
|
|
@@ -2485,7 +2633,7 @@ function registerChatgptFetch(server2, client2) {
|
|
|
2485
2633
|
}
|
|
2486
2634
|
|
|
2487
2635
|
// ../mcp-core/src/tools/admission-status.ts
|
|
2488
|
-
import { z as
|
|
2636
|
+
import { z as z52 } from "zod";
|
|
2489
2637
|
function registerAdmissionStatus(server2, client2) {
|
|
2490
2638
|
server2.registerTool(
|
|
2491
2639
|
"naumu_admission_status",
|
|
@@ -2493,8 +2641,8 @@ function registerAdmissionStatus(server2, client2) {
|
|
|
2493
2641
|
title: "Admission Status",
|
|
2494
2642
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2495
2643
|
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.",
|
|
2496
|
-
inputSchema:
|
|
2497
|
-
graphId:
|
|
2644
|
+
inputSchema: z52.object({
|
|
2645
|
+
graphId: z52.string().describe("The space (graph) ID to inspect admission for. You must be a member of this space.")
|
|
2498
2646
|
})
|
|
2499
2647
|
},
|
|
2500
2648
|
async ({ graphId }) => {
|
|
@@ -2524,7 +2672,7 @@ function registerAdmissionStatus(server2, client2) {
|
|
|
2524
2672
|
}
|
|
2525
2673
|
|
|
2526
2674
|
// ../mcp-core/src/tools/whitelist-members.ts
|
|
2527
|
-
import { z as
|
|
2675
|
+
import { z as z53 } from "zod";
|
|
2528
2676
|
function registerWhitelistMembers(server2, client2) {
|
|
2529
2677
|
server2.registerTool(
|
|
2530
2678
|
"naumu_whitelist_members",
|
|
@@ -2537,10 +2685,10 @@ function registerWhitelistMembers(server2, client2) {
|
|
|
2537
2685
|
openWorldHint: false
|
|
2538
2686
|
},
|
|
2539
2687
|
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.",
|
|
2540
|
-
inputSchema:
|
|
2541
|
-
graphId:
|
|
2542
|
-
emails:
|
|
2543
|
-
repoInit:
|
|
2688
|
+
inputSchema: z53.object({
|
|
2689
|
+
graphId: z53.string().describe("The space (graph) ID to whitelist emails for. You must be a member of this space."),
|
|
2690
|
+
emails: z53.array(z53.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."),
|
|
2691
|
+
repoInit: z53.boolean().optional().describe("Set true when this whitelist is being seeded as part of the repo init flow, so onboarding is tracked correctly.")
|
|
2544
2692
|
})
|
|
2545
2693
|
},
|
|
2546
2694
|
async ({ graphId, emails, repoInit }) => {
|
|
@@ -2564,7 +2712,7 @@ function registerWhitelistMembers(server2, client2) {
|
|
|
2564
2712
|
}
|
|
2565
2713
|
|
|
2566
2714
|
// ../mcp-core/src/tools/resolve-admission.ts
|
|
2567
|
-
import { z as
|
|
2715
|
+
import { z as z54 } from "zod";
|
|
2568
2716
|
function registerResolveAdmission(server2, client2) {
|
|
2569
2717
|
server2.registerTool(
|
|
2570
2718
|
"naumu_resolve_admission",
|
|
@@ -2577,9 +2725,9 @@ function registerResolveAdmission(server2, client2) {
|
|
|
2577
2725
|
openWorldHint: false
|
|
2578
2726
|
},
|
|
2579
2727
|
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.",
|
|
2580
|
-
inputSchema:
|
|
2581
|
-
graphId:
|
|
2582
|
-
gitEmailHint:
|
|
2728
|
+
inputSchema: z54.object({
|
|
2729
|
+
graphId: z54.string().describe("The space (graph) ID referenced by the repo .naumu file that the user wants to join."),
|
|
2730
|
+
gitEmailHint: z54.string().optional().describe("The email from `git config user.email`, used to match whitelist entries and auto-join domains.")
|
|
2583
2731
|
})
|
|
2584
2732
|
},
|
|
2585
2733
|
async ({ graphId, gitEmailHint }) => {
|
|
@@ -2603,7 +2751,7 @@ function registerResolveAdmission(server2, client2) {
|
|
|
2603
2751
|
}
|
|
2604
2752
|
|
|
2605
2753
|
// ../mcp-core/src/tools/resolve-join-request.ts
|
|
2606
|
-
import { z as
|
|
2754
|
+
import { z as z55 } from "zod";
|
|
2607
2755
|
function registerResolveJoinRequest(server2, client2) {
|
|
2608
2756
|
server2.registerTool(
|
|
2609
2757
|
"naumu_resolve_join_request",
|
|
@@ -2616,10 +2764,10 @@ function registerResolveJoinRequest(server2, client2) {
|
|
|
2616
2764
|
openWorldHint: false
|
|
2617
2765
|
},
|
|
2618
2766
|
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.",
|
|
2619
|
-
inputSchema:
|
|
2620
|
-
graphId:
|
|
2621
|
-
requestId:
|
|
2622
|
-
action:
|
|
2767
|
+
inputSchema: z55.object({
|
|
2768
|
+
graphId: z55.string().describe("The space (graph) ID the request is for. You must be a member of this space."),
|
|
2769
|
+
requestId: z55.string().describe("The pending join request ID, taken from naumu_admission_status."),
|
|
2770
|
+
action: z55.enum(["approve", "deny"]).describe("approve adds the requester as a member; deny rejects the request.")
|
|
2623
2771
|
})
|
|
2624
2772
|
},
|
|
2625
2773
|
async ({ graphId, requestId, action }) => {
|
|
@@ -2688,7 +2836,9 @@ var TOOL_REGISTRARS = {
|
|
|
2688
2836
|
naumu_delegate: registerDelegate,
|
|
2689
2837
|
// naumu_traverse omitted on purpose — backend stub returns 503 (see import note).
|
|
2690
2838
|
naumu_post_message: registerPostMessage,
|
|
2839
|
+
naumu_add_participants: registerAddParticipants,
|
|
2691
2840
|
naumu_read_thread: registerReadThread,
|
|
2841
|
+
naumu_wait_for_activity: registerWaitForActivity,
|
|
2692
2842
|
naumu_whoami: registerWhoami,
|
|
2693
2843
|
naumu_list_threads: registerListThreads,
|
|
2694
2844
|
naumu_list_topics: registerListTopics,
|
|
@@ -2714,6 +2864,7 @@ var TOOL_REGISTRARS = {
|
|
|
2714
2864
|
naumu_note_delete_section: registerNoteDeleteSection,
|
|
2715
2865
|
naumu_note_replace: registerNoteReplace,
|
|
2716
2866
|
naumu_note_find_replace: registerNoteFindReplace,
|
|
2867
|
+
naumu_note_batch: registerNoteBatch,
|
|
2717
2868
|
naumu_create_note: registerCreateNote,
|
|
2718
2869
|
naumu_list_schema_violations: registerListSchemaViolations,
|
|
2719
2870
|
naumu_list_dense_nodes: registerListDenseNodes,
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "ai.naumu/mcp",
|
|
4
4
|
"title": "Naumu",
|
|
5
5
|
"description": "Search, extend, and act on your team's Naumu knowledge graph: notes, threads, and nodes.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.14.0",
|
|
7
7
|
"websiteUrl": "https://naumu.ai",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/naumu-ai/mcp",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"registryType": "npm",
|
|
28
28
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
29
29
|
"identifier": "@naumu/mcp",
|
|
30
|
-
"version": "0.
|
|
30
|
+
"version": "0.14.0",
|
|
31
31
|
"runtimeHint": "npx",
|
|
32
32
|
"transport": { "type": "stdio" },
|
|
33
33
|
"environmentVariables": [
|