@naumu/mcp 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +293 -114
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -29,18 +29,71 @@ function safeErrorMessage(status, upstream) {
|
|
|
29
29
|
return `Request failed with status ${status}.`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// ../mcp-core/src/version.ts
|
|
33
|
+
var NAUMU_MCP_VERSION = "0.11.1";
|
|
34
|
+
|
|
32
35
|
// ../mcp-core/src/client.ts
|
|
36
|
+
var HEADER_VALUE_MAX_LENGTH = 100;
|
|
37
|
+
var sanitizeHeaderValue = (value) => {
|
|
38
|
+
const cleaned = value.replace(/[^\x20-\x7e]/g, "").trim();
|
|
39
|
+
return cleaned.length > 0 ? cleaned.slice(0, HEADER_VALUE_MAX_LENGTH) : void 0;
|
|
40
|
+
};
|
|
33
41
|
var NaumuClient = class {
|
|
34
42
|
baseUrl;
|
|
35
43
|
apiKey;
|
|
36
|
-
|
|
44
|
+
reportMcpClientHeaders;
|
|
45
|
+
mcpClientInfo;
|
|
46
|
+
pendingToolName;
|
|
47
|
+
constructor(baseUrl, apiKey2, options = {}) {
|
|
37
48
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
38
49
|
this.apiKey = apiKey2;
|
|
50
|
+
this.reportMcpClientHeaders = options.reportMcpClientHeaders === true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Record the harness identity learned from the MCP initialize handshake
|
|
54
|
+
* (see `server.server.getClientVersion()` in the stdio entrypoint).
|
|
55
|
+
*/
|
|
56
|
+
setMcpClientInfo(info) {
|
|
57
|
+
this.mcpClientInfo = info;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Mark which MCP tool is about to run. Set by the tool-attribution wrapper
|
|
61
|
+
* immediately before a handler executes.
|
|
62
|
+
*/
|
|
63
|
+
setPendingToolName(toolName) {
|
|
64
|
+
this.pendingToolName = toolName;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Consume the pending tool name — FIRST request only.
|
|
68
|
+
*
|
|
69
|
+
* One MCP tool call can fan out into several REST calls (e.g. a schema fetch
|
|
70
|
+
* before the write). Consuming on the first request means the backend counts
|
|
71
|
+
* exactly one `mcp_tool_call` per tool invocation; the follow-up calls arrive
|
|
72
|
+
* without the tool header and are deliberately not tracked.
|
|
73
|
+
*/
|
|
74
|
+
consumePendingToolName() {
|
|
75
|
+
const toolName = this.pendingToolName;
|
|
76
|
+
this.pendingToolName = void 0;
|
|
77
|
+
return toolName;
|
|
78
|
+
}
|
|
79
|
+
/** Opt-in usage-attribution headers. Empty unless `reportMcpClientHeaders` is on. */
|
|
80
|
+
mcpHeaders() {
|
|
81
|
+
if (!this.reportMcpClientHeaders) return {};
|
|
82
|
+
const headers = { "X-Naumu-Mcp-Version": NAUMU_MCP_VERSION };
|
|
83
|
+
const clientName = this.mcpClientInfo?.name ? sanitizeHeaderValue(this.mcpClientInfo.name) : void 0;
|
|
84
|
+
if (clientName) headers["X-Naumu-Mcp-Client"] = clientName;
|
|
85
|
+
const clientVersion = this.mcpClientInfo?.version ? sanitizeHeaderValue(this.mcpClientInfo.version) : void 0;
|
|
86
|
+
if (clientVersion) headers["X-Naumu-Mcp-Client-Version"] = clientVersion;
|
|
87
|
+
const pendingTool = this.consumePendingToolName();
|
|
88
|
+
const toolName = pendingTool ? sanitizeHeaderValue(pendingTool) : void 0;
|
|
89
|
+
if (toolName) headers["X-Naumu-Mcp-Tool"] = toolName;
|
|
90
|
+
return headers;
|
|
39
91
|
}
|
|
40
92
|
headers() {
|
|
41
93
|
return {
|
|
42
94
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
43
|
-
"Content-Type": "application/json"
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
...this.mcpHeaders()
|
|
44
97
|
};
|
|
45
98
|
}
|
|
46
99
|
async handleResponse(res) {
|
|
@@ -70,7 +123,7 @@ var NaumuClient = class {
|
|
|
70
123
|
async getBinary(path) {
|
|
71
124
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
72
125
|
method: "GET",
|
|
73
|
-
headers: { Authorization: `Bearer ${this.apiKey}
|
|
126
|
+
headers: { Authorization: `Bearer ${this.apiKey}`, ...this.mcpHeaders() }
|
|
74
127
|
});
|
|
75
128
|
if (!res.ok) {
|
|
76
129
|
const upstream = await res.text().catch(() => "Unknown error");
|
|
@@ -89,6 +142,33 @@ var NaumuClient = class {
|
|
|
89
142
|
contentType: res.headers.get("content-type") ?? "application/octet-stream"
|
|
90
143
|
};
|
|
91
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Follow a redirect-issuing endpoint one hop and return the `Location` it
|
|
147
|
+
* points at, without fetching the target. Any non-3xx answer is a failure
|
|
148
|
+
* (403/404/…) and is mapped through the same safe-error path as the rest.
|
|
149
|
+
*/
|
|
150
|
+
async getRedirectLocation(path) {
|
|
151
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
152
|
+
method: "GET",
|
|
153
|
+
redirect: "manual",
|
|
154
|
+
headers: { Authorization: `Bearer ${this.apiKey}`, ...this.mcpHeaders() }
|
|
155
|
+
});
|
|
156
|
+
if (res.status >= 300 && res.status < 400) {
|
|
157
|
+
const location = res.headers.get("location");
|
|
158
|
+
if (location) return location;
|
|
159
|
+
console.error(`[mcp] upstream redirect ${res.status} without a Location header`);
|
|
160
|
+
throw new NaumuApiError(res.status, "Server error \u2014 try again shortly.");
|
|
161
|
+
}
|
|
162
|
+
const upstream = await res.text().catch(() => "Unknown error");
|
|
163
|
+
console.error(
|
|
164
|
+
`[mcp] upstream redirect ${res.status}: ${upstream.slice(0, 500)}`
|
|
165
|
+
);
|
|
166
|
+
throw new NaumuApiError(
|
|
167
|
+
res.status,
|
|
168
|
+
safeErrorMessage(res.status, upstream),
|
|
169
|
+
upstream
|
|
170
|
+
);
|
|
171
|
+
}
|
|
92
172
|
async post(path, body) {
|
|
93
173
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
94
174
|
method: "POST",
|
|
@@ -133,9 +213,6 @@ URL \u2192 tool mapping (the value after /spaces/ is the slug \u2014 resolve it
|
|
|
133
213
|
|
|
134
214
|
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
215
|
|
|
136
|
-
// ../mcp-core/src/version.ts
|
|
137
|
-
var NAUMU_MCP_VERSION = "0.11.0";
|
|
138
|
-
|
|
139
216
|
// ../mcp-core/src/tools/list-graphs.ts
|
|
140
217
|
import { z } from "zod";
|
|
141
218
|
function registerListGraphs(server2, client2) {
|
|
@@ -1134,7 +1211,7 @@ function registerReadThread(server2, client2) {
|
|
|
1134
1211
|
{
|
|
1135
1212
|
title: "Read Thread",
|
|
1136
1213
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1137
|
-
description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first; each message carries a `status` (`processing` while @Naumu is still composing, `complete` when done). Use this to pick up an answer after naumu_ask returns status "processing", or to read what naumu_delegate produced. Use `before` (timestamp ms) to page further back. Default page size 50, max 200.',
|
|
1214
|
+
description: 'Read messages from a Naumu thread. Returns paginated history ordered newest-first; each message carries a `status` (`processing` while @Naumu is still composing, `complete` when done). Use this to pick up an answer after naumu_ask returns status "processing", or to read what naumu_delegate produced. Use `before` (timestamp ms) to page further back. Default page size 50, max 200. To read or download a message attachment, pass its `attachments[].id` to naumu_get_attachment.',
|
|
1138
1215
|
inputSchema: z21.object({
|
|
1139
1216
|
threadId: z21.string().describe("The thread ID to read from."),
|
|
1140
1217
|
before: z21.number().optional().describe("Unix timestamp in milliseconds. Returns messages strictly older than this. Omit for the newest page."),
|
|
@@ -1580,8 +1657,80 @@ function registerPersistCanvasAttachment(server2, client2) {
|
|
|
1580
1657
|
);
|
|
1581
1658
|
}
|
|
1582
1659
|
|
|
1583
|
-
// ../mcp-core/src/tools/
|
|
1660
|
+
// ../mcp-core/src/tools/get-attachment.ts
|
|
1584
1661
|
import { z as z30 } from "zod";
|
|
1662
|
+
var DOWNLOAD_URL_TTL_SECONDS = 900;
|
|
1663
|
+
var MAX_INLINE_PREVIEW_BYTES = 4 * 1024 * 1024;
|
|
1664
|
+
function normalizeAttachmentId(input) {
|
|
1665
|
+
const trimmed = input.trim();
|
|
1666
|
+
const withoutQuery = trimmed.split(/[?#]/)[0];
|
|
1667
|
+
const segments = withoutQuery.split("/").filter((segment) => segment.length > 0);
|
|
1668
|
+
const last = segments[segments.length - 1] ?? "";
|
|
1669
|
+
try {
|
|
1670
|
+
return decodeURIComponent(last);
|
|
1671
|
+
} catch {
|
|
1672
|
+
return last;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function registerGetAttachment(server2, client2) {
|
|
1676
|
+
server2.registerTool(
|
|
1677
|
+
"naumu_get_attachment",
|
|
1678
|
+
{
|
|
1679
|
+
title: "Get Attachment",
|
|
1680
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1681
|
+
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.",
|
|
1682
|
+
inputSchema: z30.object({
|
|
1683
|
+
attachmentId: z30.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.")
|
|
1684
|
+
})
|
|
1685
|
+
},
|
|
1686
|
+
async ({ attachmentId }) => {
|
|
1687
|
+
try {
|
|
1688
|
+
const id = normalizeAttachmentId(attachmentId);
|
|
1689
|
+
const encoded = encodeURIComponent(id);
|
|
1690
|
+
const downloadUrl = await client2.getRedirectLocation(
|
|
1691
|
+
`/api/attachments/download/${encoded}`
|
|
1692
|
+
);
|
|
1693
|
+
const content = [];
|
|
1694
|
+
try {
|
|
1695
|
+
const preview = await client2.getBinary(
|
|
1696
|
+
`/api/attachments/download/${encoded}?thumbnail=preview`
|
|
1697
|
+
);
|
|
1698
|
+
if (preview.contentType.startsWith("image/") && preview.buffer.length <= MAX_INLINE_PREVIEW_BYTES) {
|
|
1699
|
+
content.push({
|
|
1700
|
+
type: "image",
|
|
1701
|
+
data: Buffer.from(preview.buffer).toString("base64"),
|
|
1702
|
+
mimeType: preview.contentType
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
} catch {
|
|
1706
|
+
}
|
|
1707
|
+
content.push({
|
|
1708
|
+
type: "text",
|
|
1709
|
+
text: JSON.stringify(
|
|
1710
|
+
{
|
|
1711
|
+
attachmentId: id,
|
|
1712
|
+
downloadUrl,
|
|
1713
|
+
expiresInSeconds: DOWNLOAD_URL_TTL_SECONDS,
|
|
1714
|
+
note: "downloadUrl is presigned - fetch it WITHOUT any Authorization header. It expires in about 15 minutes; do not log or store it."
|
|
1715
|
+
},
|
|
1716
|
+
null,
|
|
1717
|
+
2
|
|
1718
|
+
)
|
|
1719
|
+
});
|
|
1720
|
+
return { content };
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1723
|
+
return {
|
|
1724
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1725
|
+
isError: true
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// ../mcp-core/src/tools/add-reaction.ts
|
|
1733
|
+
import { z as z31 } from "zod";
|
|
1585
1734
|
function registerAddReaction(server2, client2) {
|
|
1586
1735
|
server2.registerTool(
|
|
1587
1736
|
"naumu_add_reaction",
|
|
@@ -1589,10 +1738,10 @@ function registerAddReaction(server2, client2) {
|
|
|
1589
1738
|
title: "Add Reaction",
|
|
1590
1739
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1591
1740
|
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.',
|
|
1592
|
-
inputSchema:
|
|
1593
|
-
threadId:
|
|
1594
|
-
messageId:
|
|
1595
|
-
emoji:
|
|
1741
|
+
inputSchema: z31.object({
|
|
1742
|
+
threadId: z31.string().describe("Thread containing the message. You must be a participant."),
|
|
1743
|
+
messageId: z31.string().describe("The message to react to."),
|
|
1744
|
+
emoji: z31.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.')
|
|
1596
1745
|
})
|
|
1597
1746
|
},
|
|
1598
1747
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1616,7 +1765,7 @@ function registerAddReaction(server2, client2) {
|
|
|
1616
1765
|
}
|
|
1617
1766
|
|
|
1618
1767
|
// ../mcp-core/src/tools/remove-reaction.ts
|
|
1619
|
-
import { z as
|
|
1768
|
+
import { z as z32 } from "zod";
|
|
1620
1769
|
function registerRemoveReaction(server2, client2) {
|
|
1621
1770
|
server2.registerTool(
|
|
1622
1771
|
"naumu_remove_reaction",
|
|
@@ -1624,10 +1773,10 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1624
1773
|
title: "Remove Reaction",
|
|
1625
1774
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1626
1775
|
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.",
|
|
1627
|
-
inputSchema:
|
|
1628
|
-
threadId:
|
|
1629
|
-
messageId:
|
|
1630
|
-
emoji:
|
|
1776
|
+
inputSchema: z32.object({
|
|
1777
|
+
threadId: z32.string().describe("Thread containing the message. You must be a participant."),
|
|
1778
|
+
messageId: z32.string().describe("The message to remove your reaction from."),
|
|
1779
|
+
emoji: z32.string().min(1).describe("Emoji character to remove (must match what you originally reacted with).")
|
|
1631
1780
|
})
|
|
1632
1781
|
},
|
|
1633
1782
|
async ({ threadId, messageId, emoji }) => {
|
|
@@ -1651,7 +1800,7 @@ function registerRemoveReaction(server2, client2) {
|
|
|
1651
1800
|
}
|
|
1652
1801
|
|
|
1653
1802
|
// ../mcp-core/src/tools/naumu-typing.ts
|
|
1654
|
-
import { z as
|
|
1803
|
+
import { z as z33 } from "zod";
|
|
1655
1804
|
function registerNaumuTyping(server2, client2) {
|
|
1656
1805
|
server2.registerTool(
|
|
1657
1806
|
"naumu_typing",
|
|
@@ -1662,9 +1811,9 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1662
1811
|
// repeating the same state is a no-op renew, so idempotent.
|
|
1663
1812
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1664
1813
|
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.',
|
|
1665
|
-
inputSchema:
|
|
1666
|
-
threadId:
|
|
1667
|
-
state:
|
|
1814
|
+
inputSchema: z33.object({
|
|
1815
|
+
threadId: z33.string().describe("The thread ID to set typing in. You must be a participant."),
|
|
1816
|
+
state: z33.enum(["start", "stop"]).describe('"start" acquires/renews the lease; "stop" ends it and clears the pill immediately.')
|
|
1668
1817
|
})
|
|
1669
1818
|
},
|
|
1670
1819
|
async ({ threadId, state }) => {
|
|
@@ -1685,7 +1834,7 @@ function registerNaumuTyping(server2, client2) {
|
|
|
1685
1834
|
}
|
|
1686
1835
|
|
|
1687
1836
|
// ../mcp-core/src/tools/note-read.ts
|
|
1688
|
-
import { z as
|
|
1837
|
+
import { z as z34 } from "zod";
|
|
1689
1838
|
function registerNoteRead(server2, client2) {
|
|
1690
1839
|
server2.registerTool(
|
|
1691
1840
|
"naumu_note_read",
|
|
@@ -1693,8 +1842,8 @@ function registerNoteRead(server2, client2) {
|
|
|
1693
1842
|
title: "Read Note",
|
|
1694
1843
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1695
1844
|
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.",
|
|
1696
|
-
inputSchema:
|
|
1697
|
-
noteId:
|
|
1845
|
+
inputSchema: z34.object({
|
|
1846
|
+
noteId: z34.string().describe("The note (Thought) ID")
|
|
1698
1847
|
})
|
|
1699
1848
|
},
|
|
1700
1849
|
async ({ noteId }) => {
|
|
@@ -1707,7 +1856,7 @@ function registerNoteRead(server2, client2) {
|
|
|
1707
1856
|
}
|
|
1708
1857
|
|
|
1709
1858
|
// ../mcp-core/src/tools/note-append.ts
|
|
1710
|
-
import { z as
|
|
1859
|
+
import { z as z35 } from "zod";
|
|
1711
1860
|
function registerNoteAppend(server2, client2) {
|
|
1712
1861
|
server2.registerTool(
|
|
1713
1862
|
"naumu_note_append",
|
|
@@ -1715,9 +1864,9 @@ function registerNoteAppend(server2, client2) {
|
|
|
1715
1864
|
title: "Append to Note",
|
|
1716
1865
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1717
1866
|
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:
|
|
1719
|
-
noteId:
|
|
1720
|
-
markdown:
|
|
1867
|
+
inputSchema: z35.object({
|
|
1868
|
+
noteId: z35.string().describe("The note (Thought) ID to append to"),
|
|
1869
|
+
markdown: z35.string().min(1).describe("Markdown content to append at the end of the note")
|
|
1721
1870
|
})
|
|
1722
1871
|
},
|
|
1723
1872
|
async ({ noteId, markdown }) => {
|
|
@@ -1730,7 +1879,7 @@ function registerNoteAppend(server2, client2) {
|
|
|
1730
1879
|
}
|
|
1731
1880
|
|
|
1732
1881
|
// ../mcp-core/src/tools/note-insert.ts
|
|
1733
|
-
import { z as
|
|
1882
|
+
import { z as z36 } from "zod";
|
|
1734
1883
|
function registerNoteInsert(server2, client2) {
|
|
1735
1884
|
server2.registerTool(
|
|
1736
1885
|
"naumu_note_insert",
|
|
@@ -1738,10 +1887,10 @@ function registerNoteInsert(server2, client2) {
|
|
|
1738
1887
|
title: "Insert After Heading",
|
|
1739
1888
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1740
1889
|
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:
|
|
1742
|
-
noteId:
|
|
1743
|
-
headingText:
|
|
1744
|
-
markdown:
|
|
1890
|
+
inputSchema: z36.object({
|
|
1891
|
+
noteId: z36.string().describe("The note (Thought) ID"),
|
|
1892
|
+
headingText: z36.string().min(1).describe("Exact text of the heading whose section the new content follows"),
|
|
1893
|
+
markdown: z36.string().min(1).describe("Markdown content to insert at the end of that section")
|
|
1745
1894
|
})
|
|
1746
1895
|
},
|
|
1747
1896
|
async ({ noteId, headingText, markdown }) => {
|
|
@@ -1757,7 +1906,7 @@ function registerNoteInsert(server2, client2) {
|
|
|
1757
1906
|
}
|
|
1758
1907
|
|
|
1759
1908
|
// ../mcp-core/src/tools/note-replace-section.ts
|
|
1760
|
-
import { z as
|
|
1909
|
+
import { z as z37 } from "zod";
|
|
1761
1910
|
function registerNoteReplaceSection(server2, client2) {
|
|
1762
1911
|
server2.registerTool(
|
|
1763
1912
|
"naumu_note_replace_section",
|
|
@@ -1765,11 +1914,11 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1765
1914
|
title: "Replace Section",
|
|
1766
1915
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1767
1916
|
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:
|
|
1769
|
-
noteId:
|
|
1770
|
-
headingText:
|
|
1771
|
-
markdown:
|
|
1772
|
-
keepHeading:
|
|
1917
|
+
inputSchema: z37.object({
|
|
1918
|
+
noteId: z37.string().describe("The note (Thought) ID"),
|
|
1919
|
+
headingText: z37.string().min(1).describe("Exact text of the heading anchoring the section"),
|
|
1920
|
+
markdown: z37.string().describe("Replacement markdown for the section body"),
|
|
1921
|
+
keepHeading: z37.boolean().optional().describe("Whether to keep the heading row itself. Default true.")
|
|
1773
1922
|
})
|
|
1774
1923
|
},
|
|
1775
1924
|
async ({ noteId, headingText, markdown, keepHeading }) => {
|
|
@@ -1786,7 +1935,7 @@ function registerNoteReplaceSection(server2, client2) {
|
|
|
1786
1935
|
}
|
|
1787
1936
|
|
|
1788
1937
|
// ../mcp-core/src/tools/note-delete-section.ts
|
|
1789
|
-
import { z as
|
|
1938
|
+
import { z as z38 } from "zod";
|
|
1790
1939
|
function registerNoteDeleteSection(server2, client2) {
|
|
1791
1940
|
server2.registerTool(
|
|
1792
1941
|
"naumu_note_delete_section",
|
|
@@ -1794,9 +1943,9 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1794
1943
|
title: "Delete Section",
|
|
1795
1944
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1796
1945
|
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.",
|
|
1797
|
-
inputSchema:
|
|
1798
|
-
noteId:
|
|
1799
|
-
headingText:
|
|
1946
|
+
inputSchema: z38.object({
|
|
1947
|
+
noteId: z38.string().describe("The note (Thought) ID"),
|
|
1948
|
+
headingText: z38.string().min(1).describe("Exact text of the heading whose section will be deleted")
|
|
1800
1949
|
})
|
|
1801
1950
|
},
|
|
1802
1951
|
async ({ noteId, headingText }) => {
|
|
@@ -1811,7 +1960,7 @@ function registerNoteDeleteSection(server2, client2) {
|
|
|
1811
1960
|
}
|
|
1812
1961
|
|
|
1813
1962
|
// ../mcp-core/src/tools/note-replace.ts
|
|
1814
|
-
import { z as
|
|
1963
|
+
import { z as z39 } from "zod";
|
|
1815
1964
|
function registerNoteReplace(server2, client2) {
|
|
1816
1965
|
server2.registerTool(
|
|
1817
1966
|
"naumu_note_replace",
|
|
@@ -1819,9 +1968,9 @@ function registerNoteReplace(server2, client2) {
|
|
|
1819
1968
|
title: "Replace Note",
|
|
1820
1969
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1821
1970
|
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:
|
|
1823
|
-
noteId:
|
|
1824
|
-
markdown:
|
|
1971
|
+
inputSchema: z39.object({
|
|
1972
|
+
noteId: z39.string().describe("The note (Thought) ID"),
|
|
1973
|
+
markdown: z39.string().describe("New markdown content for the entire note")
|
|
1825
1974
|
})
|
|
1826
1975
|
},
|
|
1827
1976
|
async ({ noteId, markdown }) => {
|
|
@@ -1834,7 +1983,7 @@ function registerNoteReplace(server2, client2) {
|
|
|
1834
1983
|
}
|
|
1835
1984
|
|
|
1836
1985
|
// ../mcp-core/src/tools/note-find-replace.ts
|
|
1837
|
-
import { z as
|
|
1986
|
+
import { z as z40 } from "zod";
|
|
1838
1987
|
function registerNoteFindReplace(server2, client2) {
|
|
1839
1988
|
server2.registerTool(
|
|
1840
1989
|
"naumu_note_find_replace",
|
|
@@ -1842,11 +1991,11 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
1842
1991
|
title: "Find/Replace in Note",
|
|
1843
1992
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
1844
1993
|
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:
|
|
1846
|
-
noteId:
|
|
1847
|
-
find:
|
|
1848
|
-
replace:
|
|
1849
|
-
all:
|
|
1994
|
+
inputSchema: z40.object({
|
|
1995
|
+
noteId: z40.string().describe("The note (Thought) ID"),
|
|
1996
|
+
find: z40.string().min(1).describe("Substring to search for. Literal - no regex."),
|
|
1997
|
+
replace: z40.string().describe("Replacement string. May be empty to delete the match."),
|
|
1998
|
+
all: z40.boolean().optional().describe("Replace all occurrences (default true). Pass false to replace only the first.")
|
|
1850
1999
|
})
|
|
1851
2000
|
},
|
|
1852
2001
|
async ({ noteId, find, replace, all }) => {
|
|
@@ -1863,7 +2012,7 @@ function registerNoteFindReplace(server2, client2) {
|
|
|
1863
2012
|
}
|
|
1864
2013
|
|
|
1865
2014
|
// ../mcp-core/src/tools/create-note.ts
|
|
1866
|
-
import { z as
|
|
2015
|
+
import { z as z41 } from "zod";
|
|
1867
2016
|
function registerCreateNote(server2, client2) {
|
|
1868
2017
|
server2.registerTool(
|
|
1869
2018
|
"naumu_create_note",
|
|
@@ -1871,12 +2020,12 @@ function registerCreateNote(server2, client2) {
|
|
|
1871
2020
|
title: "Create Note",
|
|
1872
2021
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1873
2022
|
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:
|
|
1875
|
-
graphId:
|
|
1876
|
-
title:
|
|
1877
|
-
markdown:
|
|
1878
|
-
sharedWithSpace:
|
|
1879
|
-
topicIds:
|
|
2023
|
+
inputSchema: z41.object({
|
|
2024
|
+
graphId: z41.string().describe("The graph ID to create the note in"),
|
|
2025
|
+
title: z41.string().optional().describe("Optional title for the note"),
|
|
2026
|
+
markdown: z41.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."),
|
|
2027
|
+
sharedWithSpace: z41.boolean().optional().describe("Share the note with everyone in the space. Omit (or false) to keep it private to its participants."),
|
|
2028
|
+
topicIds: z41.array(z41.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.")
|
|
1880
2029
|
})
|
|
1881
2030
|
},
|
|
1882
2031
|
async ({ graphId, title, markdown, sharedWithSpace, topicIds }) => {
|
|
@@ -1889,7 +2038,7 @@ function registerCreateNote(server2, client2) {
|
|
|
1889
2038
|
}
|
|
1890
2039
|
|
|
1891
2040
|
// ../mcp-core/src/tools/list-schema-violations.ts
|
|
1892
|
-
import { z as
|
|
2041
|
+
import { z as z42 } from "zod";
|
|
1893
2042
|
var DEFAULT_EXAMPLE_LIMIT = 5;
|
|
1894
2043
|
var rowsForKind = (violations, kind) => {
|
|
1895
2044
|
const rows = [];
|
|
@@ -1915,12 +2064,12 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
1915
2064
|
title: "List Schema Violations",
|
|
1916
2065
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1917
2066
|
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.",
|
|
1918
|
-
inputSchema:
|
|
1919
|
-
graphId:
|
|
1920
|
-
kind:
|
|
2067
|
+
inputSchema: z42.object({
|
|
2068
|
+
graphId: z42.string().describe("The graph ID"),
|
|
2069
|
+
kind: z42.string().optional().describe(
|
|
1921
2070
|
'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.'
|
|
1922
2071
|
),
|
|
1923
|
-
limit:
|
|
2072
|
+
limit: z42.number().int().min(1).optional().describe(
|
|
1924
2073
|
"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)."
|
|
1925
2074
|
)
|
|
1926
2075
|
})
|
|
@@ -1972,7 +2121,7 @@ function registerListSchemaViolations(server2, client2) {
|
|
|
1972
2121
|
}
|
|
1973
2122
|
|
|
1974
2123
|
// ../mcp-core/src/tools/list-dense-nodes.ts
|
|
1975
|
-
import { z as
|
|
2124
|
+
import { z as z43 } from "zod";
|
|
1976
2125
|
function registerListDenseNodes(server2, client2) {
|
|
1977
2126
|
server2.registerTool(
|
|
1978
2127
|
"naumu_list_dense_nodes",
|
|
@@ -1980,10 +2129,10 @@ function registerListDenseNodes(server2, client2) {
|
|
|
1980
2129
|
title: "List Dense Nodes",
|
|
1981
2130
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1982
2131
|
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.',
|
|
1983
|
-
inputSchema:
|
|
1984
|
-
graphId:
|
|
1985
|
-
minConnections:
|
|
1986
|
-
nodeTypes:
|
|
2132
|
+
inputSchema: z43.object({
|
|
2133
|
+
graphId: z43.string().describe("The graph ID"),
|
|
2134
|
+
minConnections: z43.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."),
|
|
2135
|
+
nodeTypes: z43.array(z43.string()).optional().describe("Optional list of node types to restrict the scan to.")
|
|
1987
2136
|
})
|
|
1988
2137
|
},
|
|
1989
2138
|
async ({ graphId, minConnections, nodeTypes }) => {
|
|
@@ -2001,7 +2150,7 @@ function registerListDenseNodes(server2, client2) {
|
|
|
2001
2150
|
}
|
|
2002
2151
|
|
|
2003
2152
|
// ../mcp-core/src/tools/list-node-connections.ts
|
|
2004
|
-
import { z as
|
|
2153
|
+
import { z as z44 } from "zod";
|
|
2005
2154
|
function registerListNodeConnections(server2, client2) {
|
|
2006
2155
|
server2.registerTool(
|
|
2007
2156
|
"naumu_list_node_connections",
|
|
@@ -2009,11 +2158,11 @@ function registerListNodeConnections(server2, client2) {
|
|
|
2009
2158
|
title: "List Node Connections",
|
|
2010
2159
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2011
2160
|
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}}] }`.',
|
|
2012
|
-
inputSchema:
|
|
2013
|
-
graphId:
|
|
2014
|
-
nodeId:
|
|
2015
|
-
edgeType:
|
|
2016
|
-
direction:
|
|
2161
|
+
inputSchema: z44.object({
|
|
2162
|
+
graphId: z44.string().describe("The graph ID"),
|
|
2163
|
+
nodeId: z44.string().describe("The node ID to inspect"),
|
|
2164
|
+
edgeType: z44.string().optional().describe('Restrict to a specific relation label (e.g. "ASSOCIATED_WITH"). Case-insensitive; non-alphanum chars are normalized.'),
|
|
2165
|
+
direction: z44.enum(["in", "out", "both"]).optional().describe('Edge direction filter - "in" (incoming), "out" (outgoing), "both" (default).')
|
|
2017
2166
|
})
|
|
2018
2167
|
},
|
|
2019
2168
|
async ({ graphId, nodeId, edgeType, direction }) => {
|
|
@@ -2031,7 +2180,7 @@ function registerListNodeConnections(server2, client2) {
|
|
|
2031
2180
|
}
|
|
2032
2181
|
|
|
2033
2182
|
// ../mcp-core/src/tools/reparent.ts
|
|
2034
|
-
import { z as
|
|
2183
|
+
import { z as z45 } from "zod";
|
|
2035
2184
|
function registerReparent(server2, client2) {
|
|
2036
2185
|
server2.registerTool(
|
|
2037
2186
|
"naumu_reparent",
|
|
@@ -2039,11 +2188,11 @@ function registerReparent(server2, client2) {
|
|
|
2039
2188
|
title: "Reparent Node",
|
|
2040
2189
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2041
2190
|
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"}`.',
|
|
2042
|
-
inputSchema:
|
|
2043
|
-
graphId:
|
|
2044
|
-
nodeId:
|
|
2045
|
-
newParentId:
|
|
2046
|
-
newRelation:
|
|
2191
|
+
inputSchema: z45.object({
|
|
2192
|
+
graphId: z45.string().describe("The graph ID"),
|
|
2193
|
+
nodeId: z45.string().describe("The child node to reparent"),
|
|
2194
|
+
newParentId: z45.string().describe("The new parent node id"),
|
|
2195
|
+
newRelation: z45.string().describe('The new parent edge relation label (e.g. "PART_OF"). Must be valid per the schema for (child.type, relation, parent.type).')
|
|
2047
2196
|
})
|
|
2048
2197
|
},
|
|
2049
2198
|
async ({ graphId, nodeId, newParentId, newRelation }) => {
|
|
@@ -2059,7 +2208,7 @@ function registerReparent(server2, client2) {
|
|
|
2059
2208
|
}
|
|
2060
2209
|
|
|
2061
2210
|
// ../mcp-core/src/tools/batch-reparent.ts
|
|
2062
|
-
import { z as
|
|
2211
|
+
import { z as z46 } from "zod";
|
|
2063
2212
|
function registerBatchReparent(server2, client2) {
|
|
2064
2213
|
server2.registerTool(
|
|
2065
2214
|
"naumu_batch_reparent",
|
|
@@ -2067,11 +2216,11 @@ function registerBatchReparent(server2, client2) {
|
|
|
2067
2216
|
title: "Batch Reparent Nodes",
|
|
2068
2217
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
2069
2218
|
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?}]`.',
|
|
2070
|
-
inputSchema:
|
|
2071
|
-
graphId:
|
|
2072
|
-
newParentId:
|
|
2073
|
-
newRelation:
|
|
2074
|
-
nodeIds:
|
|
2219
|
+
inputSchema: z46.object({
|
|
2220
|
+
graphId: z46.string().describe("The graph ID"),
|
|
2221
|
+
newParentId: z46.string().describe("Parent node id every nodeId in the batch will be parented to"),
|
|
2222
|
+
newRelation: z46.string().describe("Parent edge relation label (must be valid per schema for child.type \u2192 parent.type)"),
|
|
2223
|
+
nodeIds: z46.array(z46.string()).min(1).max(25).describe("1\u201325 child node ids to reparent under `newParentId`")
|
|
2075
2224
|
})
|
|
2076
2225
|
},
|
|
2077
2226
|
async ({ graphId, newParentId, newRelation, nodeIds }) => {
|
|
@@ -2088,7 +2237,7 @@ function registerBatchReparent(server2, client2) {
|
|
|
2088
2237
|
}
|
|
2089
2238
|
|
|
2090
2239
|
// ../mcp-core/src/tools/chatgpt-search.ts
|
|
2091
|
-
import { z as
|
|
2240
|
+
import { z as z47 } from "zod";
|
|
2092
2241
|
|
|
2093
2242
|
// ../mcp-core/src/public-origin.ts
|
|
2094
2243
|
function publicOrigin() {
|
|
@@ -2142,8 +2291,8 @@ function registerChatgptSearch(server2, client2) {
|
|
|
2142
2291
|
title: "Search",
|
|
2143
2292
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2144
2293
|
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.)",
|
|
2145
|
-
inputSchema:
|
|
2146
|
-
query:
|
|
2294
|
+
inputSchema: z47.object({
|
|
2295
|
+
query: z47.string().describe(
|
|
2147
2296
|
"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
2297
|
)
|
|
2149
2298
|
})
|
|
@@ -2177,7 +2326,7 @@ function registerChatgptSearch(server2, client2) {
|
|
|
2177
2326
|
}
|
|
2178
2327
|
|
|
2179
2328
|
// ../mcp-core/src/tools/chatgpt-fetch.ts
|
|
2180
|
-
import { z as
|
|
2329
|
+
import { z as z48 } from "zod";
|
|
2181
2330
|
var NON_ATTRIBUTE_PROPS = /* @__PURE__ */ new Set([
|
|
2182
2331
|
"id",
|
|
2183
2332
|
"label",
|
|
@@ -2242,8 +2391,8 @@ function registerChatgptFetch(server2, client2) {
|
|
|
2242
2391
|
title: "Fetch",
|
|
2243
2392
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2244
2393
|
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.",
|
|
2245
|
-
inputSchema:
|
|
2246
|
-
id:
|
|
2394
|
+
inputSchema: z48.object({
|
|
2395
|
+
id: z48.string().describe("A resource id from a previous `search` result, in the form `<graphId>:<nodeId>`.")
|
|
2247
2396
|
})
|
|
2248
2397
|
},
|
|
2249
2398
|
async ({ id }) => {
|
|
@@ -2286,7 +2435,7 @@ function registerChatgptFetch(server2, client2) {
|
|
|
2286
2435
|
}
|
|
2287
2436
|
|
|
2288
2437
|
// ../mcp-core/src/tools/admission-status.ts
|
|
2289
|
-
import { z as
|
|
2438
|
+
import { z as z49 } from "zod";
|
|
2290
2439
|
function registerAdmissionStatus(server2, client2) {
|
|
2291
2440
|
server2.registerTool(
|
|
2292
2441
|
"naumu_admission_status",
|
|
@@ -2294,8 +2443,8 @@ function registerAdmissionStatus(server2, client2) {
|
|
|
2294
2443
|
title: "Admission Status",
|
|
2295
2444
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
2296
2445
|
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.",
|
|
2297
|
-
inputSchema:
|
|
2298
|
-
graphId:
|
|
2446
|
+
inputSchema: z49.object({
|
|
2447
|
+
graphId: z49.string().describe("The space (graph) ID to inspect admission for. You must be a member of this space.")
|
|
2299
2448
|
})
|
|
2300
2449
|
},
|
|
2301
2450
|
async ({ graphId }) => {
|
|
@@ -2325,7 +2474,7 @@ function registerAdmissionStatus(server2, client2) {
|
|
|
2325
2474
|
}
|
|
2326
2475
|
|
|
2327
2476
|
// ../mcp-core/src/tools/whitelist-members.ts
|
|
2328
|
-
import { z as
|
|
2477
|
+
import { z as z50 } from "zod";
|
|
2329
2478
|
function registerWhitelistMembers(server2, client2) {
|
|
2330
2479
|
server2.registerTool(
|
|
2331
2480
|
"naumu_whitelist_members",
|
|
@@ -2338,10 +2487,10 @@ function registerWhitelistMembers(server2, client2) {
|
|
|
2338
2487
|
openWorldHint: false
|
|
2339
2488
|
},
|
|
2340
2489
|
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.",
|
|
2341
|
-
inputSchema:
|
|
2342
|
-
graphId:
|
|
2343
|
-
emails:
|
|
2344
|
-
repoInit:
|
|
2490
|
+
inputSchema: z50.object({
|
|
2491
|
+
graphId: z50.string().describe("The space (graph) ID to whitelist emails for. You must be a member of this space."),
|
|
2492
|
+
emails: z50.array(z50.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."),
|
|
2493
|
+
repoInit: z50.boolean().optional().describe("Set true when this whitelist is being seeded as part of the repo init flow, so onboarding is tracked correctly.")
|
|
2345
2494
|
})
|
|
2346
2495
|
},
|
|
2347
2496
|
async ({ graphId, emails, repoInit }) => {
|
|
@@ -2365,7 +2514,7 @@ function registerWhitelistMembers(server2, client2) {
|
|
|
2365
2514
|
}
|
|
2366
2515
|
|
|
2367
2516
|
// ../mcp-core/src/tools/resolve-admission.ts
|
|
2368
|
-
import { z as
|
|
2517
|
+
import { z as z51 } from "zod";
|
|
2369
2518
|
function registerResolveAdmission(server2, client2) {
|
|
2370
2519
|
server2.registerTool(
|
|
2371
2520
|
"naumu_resolve_admission",
|
|
@@ -2378,9 +2527,9 @@ function registerResolveAdmission(server2, client2) {
|
|
|
2378
2527
|
openWorldHint: false
|
|
2379
2528
|
},
|
|
2380
2529
|
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.",
|
|
2381
|
-
inputSchema:
|
|
2382
|
-
graphId:
|
|
2383
|
-
gitEmailHint:
|
|
2530
|
+
inputSchema: z51.object({
|
|
2531
|
+
graphId: z51.string().describe("The space (graph) ID referenced by the repo .naumu file that the user wants to join."),
|
|
2532
|
+
gitEmailHint: z51.string().optional().describe("The email from `git config user.email`, used to match whitelist entries and auto-join domains.")
|
|
2384
2533
|
})
|
|
2385
2534
|
},
|
|
2386
2535
|
async ({ graphId, gitEmailHint }) => {
|
|
@@ -2404,7 +2553,7 @@ function registerResolveAdmission(server2, client2) {
|
|
|
2404
2553
|
}
|
|
2405
2554
|
|
|
2406
2555
|
// ../mcp-core/src/tools/resolve-join-request.ts
|
|
2407
|
-
import { z as
|
|
2556
|
+
import { z as z52 } from "zod";
|
|
2408
2557
|
function registerResolveJoinRequest(server2, client2) {
|
|
2409
2558
|
server2.registerTool(
|
|
2410
2559
|
"naumu_resolve_join_request",
|
|
@@ -2417,10 +2566,10 @@ function registerResolveJoinRequest(server2, client2) {
|
|
|
2417
2566
|
openWorldHint: false
|
|
2418
2567
|
},
|
|
2419
2568
|
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.",
|
|
2420
|
-
inputSchema:
|
|
2421
|
-
graphId:
|
|
2422
|
-
requestId:
|
|
2423
|
-
action:
|
|
2569
|
+
inputSchema: z52.object({
|
|
2570
|
+
graphId: z52.string().describe("The space (graph) ID the request is for. You must be a member of this space."),
|
|
2571
|
+
requestId: z52.string().describe("The pending join request ID, taken from naumu_admission_status."),
|
|
2572
|
+
action: z52.enum(["approve", "deny"]).describe("approve adds the requester as a member; deny rejects the request.")
|
|
2424
2573
|
})
|
|
2425
2574
|
},
|
|
2426
2575
|
async ({ graphId, requestId, action }) => {
|
|
@@ -2443,6 +2592,29 @@ function registerResolveJoinRequest(server2, client2) {
|
|
|
2443
2592
|
);
|
|
2444
2593
|
}
|
|
2445
2594
|
|
|
2595
|
+
// ../mcp-core/src/tools/tool-attribution.ts
|
|
2596
|
+
function withToolAttribution(server2, client2) {
|
|
2597
|
+
return new Proxy(server2, {
|
|
2598
|
+
get(target, prop, receiver) {
|
|
2599
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2600
|
+
if (prop !== "registerTool" || typeof value !== "function") return value;
|
|
2601
|
+
return (...args) => {
|
|
2602
|
+
const toolName = typeof args[0] === "string" ? args[0] : void 0;
|
|
2603
|
+
const handlerIndex = args.length - 1;
|
|
2604
|
+
const handler = args[handlerIndex];
|
|
2605
|
+
if (toolName && typeof handler === "function") {
|
|
2606
|
+
const original = handler;
|
|
2607
|
+
args[handlerIndex] = (...handlerArgs) => {
|
|
2608
|
+
client2.setPendingToolName(toolName);
|
|
2609
|
+
return original(...handlerArgs);
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
return value.apply(target, args);
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2446
2618
|
// ../mcp-core/src/tools/index.ts
|
|
2447
2619
|
var TOOL_REGISTRARS = {
|
|
2448
2620
|
naumu_list_graphs: registerListGraphs,
|
|
@@ -2480,6 +2652,7 @@ var TOOL_REGISTRARS = {
|
|
|
2480
2652
|
naumu_create_thread: registerCreateThread,
|
|
2481
2653
|
naumu_request_attachment_upload: registerRequestAttachmentUpload,
|
|
2482
2654
|
naumu_persist_canvas_attachment: registerPersistCanvasAttachment,
|
|
2655
|
+
naumu_get_attachment: registerGetAttachment,
|
|
2483
2656
|
naumu_add_reaction: registerAddReaction,
|
|
2484
2657
|
naumu_remove_reaction: registerRemoveReaction,
|
|
2485
2658
|
naumu_typing: registerNaumuTyping,
|
|
@@ -2515,18 +2688,20 @@ var ALL_TOOL_NAMES = Object.keys(TOOL_REGISTRARS).filter(
|
|
|
2515
2688
|
(name) => !BOT_ONLY_TOOL_NAMES.has(name)
|
|
2516
2689
|
);
|
|
2517
2690
|
function registerAllTools(server2, client2) {
|
|
2691
|
+
const attributed = withToolAttribution(server2, client2);
|
|
2518
2692
|
for (const [name, registrar] of Object.entries(TOOL_REGISTRARS)) {
|
|
2519
2693
|
if (BOT_ONLY_TOOL_NAMES.has(name)) continue;
|
|
2520
|
-
registrar(
|
|
2694
|
+
registrar(attributed, client2);
|
|
2521
2695
|
}
|
|
2522
2696
|
}
|
|
2523
2697
|
function registerNamedTools(server2, client2, toolNames) {
|
|
2698
|
+
const attributed = withToolAttribution(server2, client2);
|
|
2524
2699
|
const registered = [];
|
|
2525
2700
|
const skipped = [];
|
|
2526
2701
|
for (const name of toolNames) {
|
|
2527
2702
|
const registrar = TOOL_REGISTRARS[name];
|
|
2528
2703
|
if (registrar) {
|
|
2529
|
-
registrar(
|
|
2704
|
+
registrar(attributed, client2);
|
|
2530
2705
|
registered.push(name);
|
|
2531
2706
|
} else {
|
|
2532
2707
|
skipped.push(name);
|
|
@@ -2549,7 +2724,7 @@ if (!apiKey) {
|
|
|
2549
2724
|
console.error("Missing required environment variable: NAUMU_API_KEY");
|
|
2550
2725
|
process.exit(1);
|
|
2551
2726
|
}
|
|
2552
|
-
var client = new NaumuClient(apiUrl, apiKey);
|
|
2727
|
+
var client = new NaumuClient(apiUrl, apiKey, { reportMcpClientHeaders: true });
|
|
2553
2728
|
var server = new McpServer(
|
|
2554
2729
|
{
|
|
2555
2730
|
name: "naumu",
|
|
@@ -2578,5 +2753,9 @@ if (isBotKey) {
|
|
|
2578
2753
|
`[mcp] connected to ${apiUrl} as user key (${process.env.NAUMU_IDENTITY_ID ?? "no identity id set"})`
|
|
2579
2754
|
);
|
|
2580
2755
|
}
|
|
2756
|
+
server.server.oninitialized = () => {
|
|
2757
|
+
const clientInfo = server.server.getClientVersion();
|
|
2758
|
+
client.setMcpClientInfo(clientInfo ? { name: clientInfo.name, version: clientInfo.version } : void 0);
|
|
2759
|
+
};
|
|
2581
2760
|
var transport = new StdioServerTransport();
|
|
2582
2761
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naumu/mcp",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "MCP server for Naumu – access your knowledge graph from Claude Code, Cursor, and other AI coding agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Naumu <hello@naumu.ai>",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"knowledge-management"
|
|
23
23
|
],
|
|
24
24
|
"bin": {
|
|
25
|
-
"naumu-mcp": "
|
|
25
|
+
"naumu-mcp": "dist/index.js"
|
|
26
26
|
},
|
|
27
27
|
"main": "dist/index.js",
|
|
28
28
|
"type": "module",
|