@naumu/mcp 0.11.1 → 0.12.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 +176 -34
- package/dist/index.js +254 -15
- package/package.json +7 -6
- package/server.json +58 -0
package/README.md
CHANGED
|
@@ -1,14 +1,59 @@
|
|
|
1
1
|
# @naumu/mcp
|
|
2
2
|
|
|
3
|
-
MCP server for [Naumu](https://naumu.ai)
|
|
3
|
+
MCP server for [Naumu](https://naumu.ai) - access your knowledge graph from Claude Code, Cursor, and other AI agents.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Naumu speaks MCP over two transports:
|
|
6
|
+
|
|
7
|
+
1. **Remote, over OAuth** at `https://naumu.ai/api/mcp` - the recommended path. You sign in through your browser, and there is no API key to create, paste, or keep in sync. This package is not involved.
|
|
8
|
+
2. **Local stdio**, this package, launched on demand with `npx` and authenticated with an API key. Use it for clients that cannot do browser OAuth: CI jobs, bot and external-agent identities, and older stdio-only harnesses.
|
|
9
|
+
|
|
10
|
+
## Recommended setup: remote MCP over OAuth
|
|
11
|
+
|
|
12
|
+
Register the server once. The first time a session uses a Naumu tool, your browser opens to sign in and approve access. After that the tools are available in every session.
|
|
13
|
+
|
|
14
|
+
### Claude Code
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
claude mcp add --transport http --scope user naumu https://naumu.ai/api/mcp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`--scope user` registers the server for your whole machine rather than the current project, so you do not have to repeat it per repository.
|
|
21
|
+
|
|
22
|
+
### Cursor
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"naumu": {
|
|
28
|
+
"url": "https://naumu.ai/api/mcp"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Leave any OAuth client ID and secret fields blank. Naumu registers the client for you automatically during sign-in, using Dynamic Client Registration (DCR).
|
|
35
|
+
|
|
36
|
+
Other MCP-capable clients that support remote servers follow the same shape: point them at `https://naumu.ai/api/mcp` and let the browser handle sign-in. Use the front-end origin (`naumu.ai`), not a backend address - sign-in and consent rely on your Naumu session, which lives on the front-end origin.
|
|
37
|
+
|
|
38
|
+
You can revoke a connected tool's access at any time in Naumu under **Settings > Your agents**, in the **Active access** list.
|
|
39
|
+
|
|
40
|
+
See [Local MCP](https://naumu.ai/docs/local-mcp) for the full walkthrough.
|
|
41
|
+
|
|
42
|
+
## Fallback setup: local stdio server with an API key
|
|
43
|
+
|
|
44
|
+
Use this package when your client cannot do browser OAuth - CI, bots and external agent identities, or a harness that only speaks stdio.
|
|
6
45
|
|
|
7
46
|
### 1. Get an API key
|
|
8
47
|
|
|
9
|
-
|
|
48
|
+
1. Open **Settings > Your agents** in Naumu.
|
|
49
|
+
2. Select **Create an API key** and give it a recognizable name (such as the tool it is for).
|
|
50
|
+
3. Copy the key right away. It is shown only once and cannot be retrieved later. Keys start with `nmu_`.
|
|
51
|
+
|
|
52
|
+
Use a separate key per tool, so a leak only costs you that one key.
|
|
10
53
|
|
|
11
|
-
### 2. Configure your
|
|
54
|
+
### 2. Configure your client
|
|
55
|
+
|
|
56
|
+
You do not install this package globally; your client launches it on demand with `npx`.
|
|
12
57
|
|
|
13
58
|
#### Claude Code
|
|
14
59
|
|
|
@@ -20,8 +65,6 @@ claude mcp add naumu -s user \
|
|
|
20
65
|
|
|
21
66
|
#### Cursor
|
|
22
67
|
|
|
23
|
-
Add to your Cursor MCP settings:
|
|
24
|
-
|
|
25
68
|
```json
|
|
26
69
|
{
|
|
27
70
|
"mcpServers": {
|
|
@@ -36,39 +79,138 @@ Add to your Cursor MCP settings:
|
|
|
36
79
|
}
|
|
37
80
|
```
|
|
38
81
|
|
|
82
|
+
### Environment variables
|
|
83
|
+
|
|
84
|
+
| Variable | Required | Default | Description |
|
|
85
|
+
|----------|----------|---------|-------------|
|
|
86
|
+
| `NAUMU_API_KEY` | Yes | - | Your API key (starts with `nmu_`) |
|
|
87
|
+
| `NAUMU_API_URL` | No | `https://naumu.ai` | Naumu API base URL. You do not need to set this |
|
|
88
|
+
|
|
39
89
|
## Available tools
|
|
40
90
|
|
|
91
|
+
Both transports expose the same tool surface. Tools marked **bot** are only registered for bot identity keys (`nmu_bot_*`), which additionally receive a filtered subset based on the identity's permissions.
|
|
92
|
+
|
|
93
|
+
### Spaces and members
|
|
94
|
+
|
|
41
95
|
| Tool | Description |
|
|
42
96
|
|------|-------------|
|
|
43
|
-
| `naumu_list_graphs` | List
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `naumu_filter` | Filter nodes by type and attributes with deterministic results |
|
|
48
|
-
| `naumu_get_node` | Get a node with all its properties and connections |
|
|
49
|
-
| `naumu_add_node` | Create a new node |
|
|
50
|
-
| `naumu_update_node` | Update an existing node |
|
|
51
|
-
| `naumu_add_edge` | Create a relationship between two nodes |
|
|
52
|
-
| `naumu_remove_node` | Delete a node and its connections |
|
|
53
|
-
| `naumu_remove_edge` | Delete a relationship |
|
|
54
|
-
| `naumu_create_topic` | Create a topic (filing destination) in a space; admin-only, name must be a lowercase slug, returns an `id` for `topicIds` params |
|
|
55
|
-
| `naumu_ask` | Ask @Naumu a question and get a synthesised answer back (answer + sources + confidence + threadId + status); leaves a visible thread in the space |
|
|
56
|
-
| `naumu_delegate` | Hand @Naumu a task to carry out asynchronously (add knowledge, make changes); returns a `threadId` immediately, then poll with `naumu_read_thread` |
|
|
57
|
-
| `naumu_read_thread` | Read a thread's messages; each message carries a `status` of `processing` or `complete` |
|
|
58
|
-
| `naumu_whoami` | (Bot keys) Self-introspection — id, graphId, name, instructions, allowed tools |
|
|
59
|
-
| `naumu_list_threads` | (Bot keys) List threads in your graph with `isParticipant` flag |
|
|
60
|
-
| `naumu_get_thread` | (Bot keys) Fetch a thread plus its human + bot participant rosters |
|
|
61
|
-
| `naumu_create_thread` | (Bot keys only) Start a new conversation, attach participants, post initial message, optionally file into topics via `topicIds` |
|
|
62
|
-
| `naumu_request_attachment_upload` | (Bot keys) Get a presigned S3 URL to upload a file; pair with `naumu_post_message`'s `attachmentIds` |
|
|
63
|
-
| `naumu_add_reaction` | (Bot keys) Add an emoji reaction to a message in a thread you participate in (idempotent) |
|
|
64
|
-
| `naumu_remove_reaction` | (Bot keys) Remove your own reaction from a message |
|
|
65
|
-
|
|
66
|
-
## Environment variables
|
|
97
|
+
| `naumu_list_graphs` | List every space (graph) the key can access, with ids, names, and roles |
|
|
98
|
+
| `naumu_create_graph` | Create a new, empty space owned by the authenticated user |
|
|
99
|
+
| `naumu_list_members` | List a space's members with their roles |
|
|
100
|
+
| `naumu_whoami` | Return who the calling key is (user, or bot identity with its graph and allowed tools) |
|
|
67
101
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
102
|
+
### Schema
|
|
103
|
+
|
|
104
|
+
| Tool | Description |
|
|
105
|
+
|------|-------------|
|
|
106
|
+
| `naumu_get_schema` | Get a space's schema (node types, attributes, connections) |
|
|
107
|
+
| `naumu_update_schema` | Replace the schema with a full new definition |
|
|
108
|
+
| `naumu_add_node_type` | Add one node type without resending the whole schema |
|
|
109
|
+
| `naumu_add_connection` | Add one connection from an existing node type to another |
|
|
110
|
+
| `naumu_add_attribute` | Add or extend an attribute on an existing node type |
|
|
111
|
+
| `naumu_list_schema_violations` | Audit a space against its schema and report violations |
|
|
112
|
+
|
|
113
|
+
### Reading the graph
|
|
114
|
+
|
|
115
|
+
| Tool | Description |
|
|
116
|
+
|------|-------------|
|
|
117
|
+
| `naumu_search` | Hybrid (meaning plus text) search over graph nodes |
|
|
118
|
+
| `naumu_filter` | Filter nodes by type and attributes, with deterministic and complete results |
|
|
119
|
+
| `naumu_get_node` | Get one node with its properties, edges, notes, threads, and generated context |
|
|
120
|
+
| `naumu_list_node_connections` | List a single node's connections |
|
|
121
|
+
| `naumu_list_dense_nodes` | Find over-connected hub nodes by child count |
|
|
122
|
+
| `naumu_ask` | Ask @Naumu a question and get a synthesised, node-grounded answer with sources and confidence |
|
|
123
|
+
|
|
124
|
+
### Writing to the graph
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
|------|-------------|
|
|
128
|
+
| `naumu_add_node` | Create 1-25 nodes in one call |
|
|
129
|
+
| `naumu_update_node` | Overwrite properties of an existing node |
|
|
130
|
+
| `naumu_remove_node` | Delete a node and all of its connections (destructive) |
|
|
131
|
+
| `naumu_add_edge` | Create 1-25 edges between existing nodes |
|
|
132
|
+
| `naumu_remove_edge` | Delete one edge identified by (source, target, label) |
|
|
133
|
+
| `naumu_remove_edges_bulk` | Delete 1-100 edges in one atomic call |
|
|
134
|
+
| `naumu_reparent` | Atomically swap a node's parent edge |
|
|
135
|
+
| `naumu_batch_reparent` | Reparent 1-25 nodes onto a shared new parent |
|
|
136
|
+
| `naumu_delegate` | Hand @Naumu a task to carry out asynchronously; returns a `threadId` to poll |
|
|
137
|
+
|
|
138
|
+
### Threads and topics
|
|
139
|
+
|
|
140
|
+
| Tool | Description |
|
|
141
|
+
|------|-------------|
|
|
142
|
+
| `naumu_list_threads` | List threads by last activity, with an `isParticipant` flag |
|
|
143
|
+
| `naumu_get_thread` | Fetch one thread plus its human and bot participant rosters |
|
|
144
|
+
| `naumu_read_thread` | Read a thread's messages; each carries `processing` or `complete` status |
|
|
145
|
+
| `naumu_post_message` | Post a markdown message in a thread you participate in |
|
|
146
|
+
| `naumu_create_thread` | **bot** Start a new conversation, attach participants, post a first message, optionally file into topics |
|
|
147
|
+
| `naumu_add_reaction` | Add an emoji reaction to a message (idempotent) |
|
|
148
|
+
| `naumu_remove_reaction` | Remove your own reaction from a message |
|
|
149
|
+
| `naumu_typing` | Show or hide your "is typing" indicator in a thread |
|
|
150
|
+
| `naumu_list_topics` | List a space's topics (filing destinations) for use in `topicIds` |
|
|
151
|
+
| `naumu_create_topic` | Create a topic; admin-only, name must be a lowercase slug |
|
|
152
|
+
|
|
153
|
+
### Notes
|
|
154
|
+
|
|
155
|
+
| Tool | Description |
|
|
156
|
+
|------|-------------|
|
|
157
|
+
| `naumu_create_note` | Create a note, optionally with its full markdown body in the same call |
|
|
158
|
+
| `naumu_note_read` | Read a note's current markdown plus the nodes it is tied to |
|
|
159
|
+
| `naumu_note_append` | Append markdown blocks to the end of a note |
|
|
160
|
+
| `naumu_note_insert` | Insert markdown immediately after a named heading |
|
|
161
|
+
| `naumu_note_replace_section` | Replace the body under a named heading |
|
|
162
|
+
| `naumu_note_delete_section` | Delete a heading and its body (destructive) |
|
|
163
|
+
| `naumu_note_replace` | Replace an entire note's content (destructive) |
|
|
164
|
+
| `naumu_note_find_replace` | Literal find and replace within a note |
|
|
165
|
+
|
|
166
|
+
### Attachments
|
|
167
|
+
|
|
168
|
+
| Tool | Description |
|
|
169
|
+
|------|-------------|
|
|
170
|
+
| `naumu_request_attachment_upload` | Get a presigned S3 URL to upload a file, then reference it via `attachmentIds` |
|
|
171
|
+
| `naumu_get_attachment` | Read a chat attachment by `attachmentId` |
|
|
172
|
+
| `naumu_persist_canvas_attachment` | Promote a canvas-bound pending attachment to a persisted one |
|
|
173
|
+
|
|
174
|
+
### Space admission
|
|
175
|
+
|
|
176
|
+
| Tool | Description |
|
|
177
|
+
|------|-------------|
|
|
178
|
+
| `naumu_admission_status` | Show who can auto-join a space and who is waiting for approval |
|
|
179
|
+
| `naumu_whitelist_members` | Whitelist emails so those people auto-join on sign-in |
|
|
180
|
+
| `naumu_resolve_admission` | Join (or request to join) the space a repo's `.naumu` file references |
|
|
181
|
+
| `naumu_resolve_join_request` | Approve or deny a pending join request |
|
|
182
|
+
|
|
183
|
+
### ChatGPT compatibility
|
|
184
|
+
|
|
185
|
+
The OpenAI MCP surface requires tools literally named `search` and `fetch`. These wrap cross-space search and node reads in the response shapes that client expects.
|
|
186
|
+
|
|
187
|
+
| Tool | Description |
|
|
188
|
+
|------|-------------|
|
|
189
|
+
| `search` | Search across every accessible space; returns `{ results: [{ id, title, url }] }` |
|
|
190
|
+
| `fetch` | Fetch a node returned by `search`; returns `{ id, title, text, url }` |
|
|
191
|
+
|
|
192
|
+
## Troubleshooting
|
|
193
|
+
|
|
194
|
+
When the stdio server is misconfigured it exits before the MCP handshake, and most clients simply show no server at all, with no error. Run the built-in self-check to find out why:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npx -y -p @naumu/mcp naumu-mcp doctor
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
It checks, in order, that `NAUMU_API_KEY` is set (the key itself is never printed), that the API is reachable and accepts the key, and that this build matches the latest published version. It prints one line per check plus a verdict, and exits non-zero if anything failed:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
naumu-mcp doctor (v0.11.1, API https://naumu.ai)
|
|
204
|
+
|
|
205
|
+
[fail] API key: NAUMU_API_KEY is missing from the environment
|
|
206
|
+
[warn] API: reachable (https://naumu.ai answered HTTP 403), but there is no NAUMU_API_KEY to verify
|
|
207
|
+
[ok] Version: running 0.11.1 (npm latest)
|
|
208
|
+
|
|
209
|
+
Verdict: 1 check failed - the MCP server will not work until this is fixed.
|
|
210
|
+
Most likely fix: Set NAUMU_API_KEY - create a key at https://naumu.ai/settings (Settings > Your agents > Create an API key)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Run it with the same environment your MCP client uses, otherwise the key check reports on your shell rather than on the client's configuration.
|
|
72
214
|
|
|
73
215
|
## License
|
|
74
216
|
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ function safeErrorMessage(status, upstream) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// ../mcp-core/src/version.ts
|
|
33
|
-
var NAUMU_MCP_VERSION = "0.
|
|
33
|
+
var NAUMU_MCP_VERSION = "0.12.0";
|
|
34
34
|
|
|
35
35
|
// ../mcp-core/src/client.ts
|
|
36
36
|
var HEADER_VALUE_MAX_LENGTH = 100;
|
|
@@ -677,7 +677,7 @@ function registerGetNode(server2, client2) {
|
|
|
677
677
|
{
|
|
678
678
|
title: "Get Node",
|
|
679
679
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
680
|
-
description: "Get a single node with all its properties
|
|
680
|
+
description: "Get a single node with all its properties, connections (incoming and outgoing edges with neighbor labels), and tied context: attached notes, conversations (threads), scheduled tasks, and the generated summary when one exists.",
|
|
681
681
|
inputSchema: z11.object({
|
|
682
682
|
graphId: z11.string().describe("The graph ID"),
|
|
683
683
|
nodeId: z11.string().describe("The node ID")
|
|
@@ -1283,15 +1283,31 @@ function registerListThreads(server2, client2) {
|
|
|
1283
1283
|
{
|
|
1284
1284
|
title: "List Threads",
|
|
1285
1285
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
1286
|
-
description: "List threads sorted by last activity (newest first), for self-discovery before deciding which to engage. With a user API key, pass `graphId` to list threads you can see in that space (resolve it via naumu_list_graphs). With a bot identity key, omit `graphId` to list threads in your own graph \u2014 each row carries an `isParticipant` flag (TRUE means you were explicitly invited and your replies fan out via webhook). Page back with `cursor` set to the oldest `lastActivityAt` from the previous page.",
|
|
1286
|
+
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.",
|
|
1287
1287
|
inputSchema: z23.object({
|
|
1288
1288
|
graphId: z23.string().optional().describe("Graph (space) ID. Required for user API keys; omit for bot identity keys (defaults to your own graph)."),
|
|
1289
|
+
nodeId: z23.string().optional().describe("Scope the listing to conversations tied to this node. Requires `graphId`."),
|
|
1289
1290
|
cursor: z23.number().int().optional().describe("Unix timestamp ms \u2014 returns threads with `lastActivityAt` strictly older than this. Omit for the newest page."),
|
|
1290
1291
|
limit: z23.number().int().min(1).max(200).optional().describe("Page size, default 50, max 200.")
|
|
1291
1292
|
})
|
|
1292
1293
|
},
|
|
1293
|
-
async ({ graphId, cursor, limit }) => {
|
|
1294
|
+
async ({ graphId, nodeId, cursor, limit }) => {
|
|
1294
1295
|
try {
|
|
1296
|
+
if (nodeId) {
|
|
1297
|
+
if (!graphId) {
|
|
1298
|
+
return {
|
|
1299
|
+
content: [{ type: "text", text: "Error: `nodeId` requires `graphId`." }],
|
|
1300
|
+
isError: true
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
const data2 = await client2.get(
|
|
1304
|
+
`/api/threads?graphId=${encodeURIComponent(graphId)}&nodeId=${encodeURIComponent(nodeId)}`
|
|
1305
|
+
);
|
|
1306
|
+
const clean2 = Array.isArray(data2) ? data2.map(sanitizeThreadParticipants) : data2;
|
|
1307
|
+
return {
|
|
1308
|
+
content: [{ type: "text", text: JSON.stringify(clean2, null, 2) }]
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1295
1311
|
const params = new URLSearchParams();
|
|
1296
1312
|
if (cursor !== void 0) params.set("cursor", String(cursor));
|
|
1297
1313
|
if (limit !== void 0) params.set("limit", String(limit));
|
|
@@ -1841,7 +1857,7 @@ function registerNoteRead(server2, client2) {
|
|
|
1841
1857
|
{
|
|
1842
1858
|
title: "Read Note",
|
|
1843
1859
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
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.",
|
|
1860
|
+
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.",
|
|
1845
1861
|
inputSchema: z34.object({
|
|
1846
1862
|
noteId: z34.string().describe("The note (Thought) ID")
|
|
1847
1863
|
})
|
|
@@ -2345,7 +2361,8 @@ var NON_ATTRIBUTE_PROPS = /* @__PURE__ */ new Set([
|
|
|
2345
2361
|
"updatedAt",
|
|
2346
2362
|
"heatConvSignal",
|
|
2347
2363
|
"heatConvComputedAt",
|
|
2348
|
-
"heatComputedAt"
|
|
2364
|
+
"heatComputedAt",
|
|
2365
|
+
"context"
|
|
2349
2366
|
]);
|
|
2350
2367
|
function parseResourceId(id) {
|
|
2351
2368
|
const sep = id.indexOf(":");
|
|
@@ -2374,14 +2391,42 @@ ${attrLines.join("\n")}`);
|
|
|
2374
2391
|
const edgeLines = [];
|
|
2375
2392
|
for (const e of connections?.outgoing ?? []) {
|
|
2376
2393
|
if (!e?.target) continue;
|
|
2377
|
-
|
|
2394
|
+
const target = e.targetLabel ? `${e.targetLabel} (${e.target})` : e.target;
|
|
2395
|
+
edgeLines.push(`- ${e.label ?? "related"} \u2192 ${target}${e.isParent ? " (parent)" : ""}`);
|
|
2378
2396
|
}
|
|
2379
2397
|
for (const e of connections?.incoming ?? []) {
|
|
2380
2398
|
if (!e?.source) continue;
|
|
2381
|
-
|
|
2399
|
+
const source = e.sourceLabel ? `${e.sourceLabel} (${e.source})` : e.source;
|
|
2400
|
+
edgeLines.push(`- ${source} \u2192 ${e.label ?? "related"} (incoming)`);
|
|
2382
2401
|
}
|
|
2383
2402
|
if (edgeLines.length) parts.push(`Connections:
|
|
2384
2403
|
${edgeLines.join("\n")}`);
|
|
2404
|
+
const ctx = node.context ?? {};
|
|
2405
|
+
if (ctx.notes?.length) {
|
|
2406
|
+
const more = (ctx.notesTotal ?? ctx.notes.length) - ctx.notes.length;
|
|
2407
|
+
const lines = ctx.notes.map((n) => `- ${n.title || "Untitled"} (${n.id})`);
|
|
2408
|
+
if (more > 0) lines.push(`- \u2026and ${more} more`);
|
|
2409
|
+
parts.push(`Notes:
|
|
2410
|
+
${lines.join("\n")}`);
|
|
2411
|
+
}
|
|
2412
|
+
if (ctx.threads?.length) {
|
|
2413
|
+
const more = (ctx.threadsTotal ?? ctx.threads.length) - ctx.threads.length;
|
|
2414
|
+
const lines = ctx.threads.map(
|
|
2415
|
+
(t) => `- ${t.title || "Untitled"} (${t.id}${t.relations?.length ? ` \xB7 ${t.relations.join(", ")}` : ""})`
|
|
2416
|
+
);
|
|
2417
|
+
if (more > 0) lines.push(`- \u2026and ${more} more`);
|
|
2418
|
+
parts.push(`Conversations:
|
|
2419
|
+
${lines.join("\n")}`);
|
|
2420
|
+
}
|
|
2421
|
+
if (ctx.scheduledTasks?.length) {
|
|
2422
|
+
const lines = ctx.scheduledTasks.map(
|
|
2423
|
+
(t) => `- ${t.title} (${t.id}${t.cron ? ` \xB7 cron ${t.cron}` : ""})`
|
|
2424
|
+
);
|
|
2425
|
+
parts.push(`Scheduled tasks:
|
|
2426
|
+
${lines.join("\n")}`);
|
|
2427
|
+
}
|
|
2428
|
+
if (ctx.summary?.content) parts.push(`Summary:
|
|
2429
|
+
${ctx.summary.content}`);
|
|
2385
2430
|
return parts.join("\n\n");
|
|
2386
2431
|
}
|
|
2387
2432
|
function registerChatgptFetch(server2, client2) {
|
|
@@ -2642,11 +2687,12 @@ var TOOL_REGISTRARS = {
|
|
|
2642
2687
|
naumu_whoami: registerWhoami,
|
|
2643
2688
|
naumu_list_threads: registerListThreads,
|
|
2644
2689
|
naumu_list_topics: registerListTopics,
|
|
2645
|
-
// User-surface only.
|
|
2646
|
-
//
|
|
2647
|
-
//
|
|
2648
|
-
//
|
|
2649
|
-
//
|
|
2690
|
+
// User-surface only. Omitted from BOT_ONLY_TOOL_NAMES and from the backend
|
|
2691
|
+
// PERMISSION_TO_MCP_TOOLS map (same treatment as the admission tools
|
|
2692
|
+
// below), so bot manifests never include it. Note the backend gate alone
|
|
2693
|
+
// no longer keeps bots out: `space:manage-topics` is editor+ since
|
|
2694
|
+
// 2026-08-11 and bot identities resolve to the editor role — the manifest
|
|
2695
|
+
// omission is what keeps this tool off the bot surface.
|
|
2650
2696
|
naumu_create_topic: registerCreateTopic,
|
|
2651
2697
|
naumu_get_thread: registerGetThread,
|
|
2652
2698
|
naumu_create_thread: registerCreateThread,
|
|
@@ -2715,11 +2761,204 @@ function registerNamedTools(server2, client2, toolNames) {
|
|
|
2715
2761
|
console.error(`[mcp] registered ${registered.length} tool(s) for bot key`);
|
|
2716
2762
|
}
|
|
2717
2763
|
|
|
2764
|
+
// src/doctor.ts
|
|
2765
|
+
var MARKERS = {
|
|
2766
|
+
pass: "[ok] ",
|
|
2767
|
+
fail: "[fail]",
|
|
2768
|
+
warn: "[warn]"
|
|
2769
|
+
};
|
|
2770
|
+
var BOT_KEY_PREFIX = "nmu_bot_";
|
|
2771
|
+
var SETTINGS_URL = "https://naumu.ai/settings";
|
|
2772
|
+
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/@naumu/mcp/latest";
|
|
2773
|
+
var API_TIMEOUT_MS = 1e4;
|
|
2774
|
+
var REGISTRY_TIMEOUT_MS = 4e3;
|
|
2775
|
+
var errorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
2776
|
+
var timeoutSignal = (ms) => typeof AbortSignal?.timeout === "function" ? AbortSignal.timeout(ms) : void 0;
|
|
2777
|
+
var formatDoctorCheck = (check) => `${MARKERS[check.status]} ${check.label}: ${check.detail}`;
|
|
2778
|
+
var checkApiKey = (apiKey2) => {
|
|
2779
|
+
if (!apiKey2) {
|
|
2780
|
+
return {
|
|
2781
|
+
label: "API key",
|
|
2782
|
+
status: "fail",
|
|
2783
|
+
detail: "NAUMU_API_KEY is missing from the environment",
|
|
2784
|
+
fix: `Set NAUMU_API_KEY - create a key at ${SETTINGS_URL} (Settings > Your agents > Create an API key)`
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
const kind = apiKey2.startsWith(BOT_KEY_PREFIX) ? "bot key" : "user key";
|
|
2788
|
+
return {
|
|
2789
|
+
label: "API key",
|
|
2790
|
+
status: "pass",
|
|
2791
|
+
detail: `NAUMU_API_KEY is set (${kind}; value not printed)`
|
|
2792
|
+
};
|
|
2793
|
+
};
|
|
2794
|
+
var describeWhoami = (data) => {
|
|
2795
|
+
const row = data ?? {};
|
|
2796
|
+
if (row.kind === "user") {
|
|
2797
|
+
const email = row.email ? ` <${row.email}>` : "";
|
|
2798
|
+
return `authenticated as ${row.name ?? "unknown user"}${email}`;
|
|
2799
|
+
}
|
|
2800
|
+
if (row.id) {
|
|
2801
|
+
return `authenticated as bot identity ${row.name ?? row.id} (graph ${row.graphId ?? "unknown"})`;
|
|
2802
|
+
}
|
|
2803
|
+
return "authenticated, but the identity payload was not recognized";
|
|
2804
|
+
};
|
|
2805
|
+
var checkApiReachability = async (options) => {
|
|
2806
|
+
const label = "API";
|
|
2807
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
2808
|
+
const headers = { Accept: "application/json" };
|
|
2809
|
+
if (options.apiKey) headers.Authorization = `Bearer ${options.apiKey}`;
|
|
2810
|
+
let res;
|
|
2811
|
+
try {
|
|
2812
|
+
res = await doFetch(`${options.apiUrl}/api/identities/me/whoami`, {
|
|
2813
|
+
headers,
|
|
2814
|
+
signal: timeoutSignal(options.timeoutMs ?? API_TIMEOUT_MS)
|
|
2815
|
+
});
|
|
2816
|
+
} catch (err) {
|
|
2817
|
+
return {
|
|
2818
|
+
label,
|
|
2819
|
+
status: "fail",
|
|
2820
|
+
detail: `unreachable - no response from ${options.apiUrl} (${errorMessage(err)})`,
|
|
2821
|
+
fix: `Check your network and NAUMU_API_URL - nothing answered at ${options.apiUrl}`
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
if (res.status === 401 || res.status === 403) {
|
|
2825
|
+
if (!options.apiKey) {
|
|
2826
|
+
return {
|
|
2827
|
+
label,
|
|
2828
|
+
status: "warn",
|
|
2829
|
+
detail: `reachable (${options.apiUrl} answered HTTP ${res.status}), but there is no NAUMU_API_KEY to verify`
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2832
|
+
return {
|
|
2833
|
+
label,
|
|
2834
|
+
status: "fail",
|
|
2835
|
+
detail: `reachable, but ${options.apiUrl} rejected the key (HTTP ${res.status})`,
|
|
2836
|
+
fix: `NAUMU_API_KEY is invalid or revoked - create a new key at ${SETTINGS_URL} and update your MCP config`
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
if (!res.ok) {
|
|
2840
|
+
return {
|
|
2841
|
+
label,
|
|
2842
|
+
status: "fail",
|
|
2843
|
+
detail: `reachable, but ${options.apiUrl} answered HTTP ${res.status}`,
|
|
2844
|
+
fix: "The Naumu API returned an unexpected status - retry shortly, or check NAUMU_API_URL points at the front-end origin"
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
try {
|
|
2848
|
+
const data = await res.json();
|
|
2849
|
+
return { label, status: "pass", detail: `reachable at ${options.apiUrl}; ${describeWhoami(data)}` };
|
|
2850
|
+
} catch (err) {
|
|
2851
|
+
return {
|
|
2852
|
+
label,
|
|
2853
|
+
status: "fail",
|
|
2854
|
+
detail: `reachable, but the response from ${options.apiUrl} was not JSON (${errorMessage(err)})`,
|
|
2855
|
+
fix: "NAUMU_API_URL does not look like a Naumu API - point it at https://naumu.ai"
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
var compareVersions = (a, b) => {
|
|
2860
|
+
const parse = (value) => value.split(".").slice(0, 3).map((part) => Number.parseInt(part, 10) || 0);
|
|
2861
|
+
const left = parse(a);
|
|
2862
|
+
const right = parse(b);
|
|
2863
|
+
for (let i = 0; i < 3; i += 1) {
|
|
2864
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
2865
|
+
if (diff !== 0) return diff < 0 ? -1 : 1;
|
|
2866
|
+
}
|
|
2867
|
+
return 0;
|
|
2868
|
+
};
|
|
2869
|
+
var checkVersionFreshness = async (options = {}) => {
|
|
2870
|
+
const label = "Version";
|
|
2871
|
+
const current = options.currentVersion ?? NAUMU_MCP_VERSION;
|
|
2872
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
2873
|
+
let latest;
|
|
2874
|
+
try {
|
|
2875
|
+
const res = await doFetch(REGISTRY_LATEST_URL, {
|
|
2876
|
+
headers: { Accept: "application/json" },
|
|
2877
|
+
signal: timeoutSignal(options.timeoutMs ?? REGISTRY_TIMEOUT_MS)
|
|
2878
|
+
});
|
|
2879
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2880
|
+
const body = await res.json();
|
|
2881
|
+
latest = body?.version;
|
|
2882
|
+
} catch (err) {
|
|
2883
|
+
return {
|
|
2884
|
+
label,
|
|
2885
|
+
status: "warn",
|
|
2886
|
+
detail: `running ${current}; could not check the npm registry for the latest version (${errorMessage(err)})`
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
if (!latest) {
|
|
2890
|
+
return {
|
|
2891
|
+
label,
|
|
2892
|
+
status: "warn",
|
|
2893
|
+
detail: `running ${current}; could not check the npm registry for the latest version (no version field in the response)`
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
const comparison = compareVersions(current, latest);
|
|
2897
|
+
if (comparison < 0) {
|
|
2898
|
+
return {
|
|
2899
|
+
label,
|
|
2900
|
+
status: "fail",
|
|
2901
|
+
detail: `running ${current}, but npm latest is ${latest}`,
|
|
2902
|
+
fix: 'Update @naumu/mcp - run "npm cache clean --force" and reinstall (globally installed: "npm i -g @naumu/mcp@latest"; npx users: pin "@naumu/mcp@latest")'
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
if (comparison > 0) {
|
|
2906
|
+
return { label, status: "pass", detail: `running ${current}, ahead of npm latest ${latest} (unpublished build)` };
|
|
2907
|
+
}
|
|
2908
|
+
return { label, status: "pass", detail: `running ${current} (npm latest)` };
|
|
2909
|
+
};
|
|
2910
|
+
var runDoctorChecks = async (options) => {
|
|
2911
|
+
const checks = [];
|
|
2912
|
+
checks.push(checkApiKey(options.apiKey));
|
|
2913
|
+
checks.push(
|
|
2914
|
+
await checkApiReachability({
|
|
2915
|
+
apiUrl: options.apiUrl,
|
|
2916
|
+
apiKey: options.apiKey,
|
|
2917
|
+
fetchImpl: options.fetchImpl
|
|
2918
|
+
})
|
|
2919
|
+
);
|
|
2920
|
+
checks.push(
|
|
2921
|
+
await checkVersionFreshness({
|
|
2922
|
+
currentVersion: options.currentVersion,
|
|
2923
|
+
fetchImpl: options.fetchImpl
|
|
2924
|
+
})
|
|
2925
|
+
);
|
|
2926
|
+
return checks;
|
|
2927
|
+
};
|
|
2928
|
+
var renderDoctorReport = (checks, context) => {
|
|
2929
|
+
const version = context.version ?? NAUMU_MCP_VERSION;
|
|
2930
|
+
const lines = [
|
|
2931
|
+
`naumu-mcp doctor (v${version}, API ${context.apiUrl})`,
|
|
2932
|
+
"",
|
|
2933
|
+
...checks.map(formatDoctorCheck),
|
|
2934
|
+
""
|
|
2935
|
+
];
|
|
2936
|
+
const failures = checks.filter((check) => check.status === "fail");
|
|
2937
|
+
if (failures.length === 0) {
|
|
2938
|
+
lines.push("Verdict: all checks passed - the MCP server should start normally.");
|
|
2939
|
+
return { text: lines.join("\n"), exitCode: 0 };
|
|
2940
|
+
}
|
|
2941
|
+
const plural = failures.length === 1 ? "check" : "checks";
|
|
2942
|
+
lines.push(`Verdict: ${failures.length} ${plural} failed - the MCP server will not work until this is fixed.`);
|
|
2943
|
+
const fix = failures.find((check) => check.fix)?.fix;
|
|
2944
|
+
if (fix) lines.push(`Most likely fix: ${fix}`);
|
|
2945
|
+
return { text: lines.join("\n"), exitCode: 1 };
|
|
2946
|
+
};
|
|
2947
|
+
var runDoctorCli = async (options) => {
|
|
2948
|
+
const checks = await runDoctorChecks({ apiUrl: options.apiUrl, apiKey: options.apiKey });
|
|
2949
|
+
const report = renderDoctorReport(checks, { apiUrl: options.apiUrl });
|
|
2950
|
+
console.log(report.text);
|
|
2951
|
+
return report.exitCode;
|
|
2952
|
+
};
|
|
2953
|
+
|
|
2718
2954
|
// src/index.ts
|
|
2719
2955
|
var DEFAULT_API_URL = "https://naumu.ai";
|
|
2720
|
-
var
|
|
2956
|
+
var BOT_KEY_PREFIX2 = "nmu_bot_";
|
|
2721
2957
|
var apiUrl = (process.env.NAUMU_API_URL || DEFAULT_API_URL).replace(/\/api\/?$/, "");
|
|
2722
2958
|
var apiKey = process.env.NAUMU_API_KEY;
|
|
2959
|
+
if (process.argv[2] === "doctor") {
|
|
2960
|
+
process.exit(await runDoctorCli({ apiUrl, apiKey }));
|
|
2961
|
+
}
|
|
2723
2962
|
if (!apiKey) {
|
|
2724
2963
|
console.error("Missing required environment variable: NAUMU_API_KEY");
|
|
2725
2964
|
process.exit(1);
|
|
@@ -2734,7 +2973,7 @@ var server = new McpServer(
|
|
|
2734
2973
|
instructions: NAUMU_INSTRUCTIONS
|
|
2735
2974
|
}
|
|
2736
2975
|
);
|
|
2737
|
-
var isBotKey = apiKey.startsWith(
|
|
2976
|
+
var isBotKey = apiKey.startsWith(BOT_KEY_PREFIX2);
|
|
2738
2977
|
if (isBotKey) {
|
|
2739
2978
|
try {
|
|
2740
2979
|
const manifest = await client.get("/api/identities/me/mcp-tool-manifest");
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naumu/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Naumu
|
|
3
|
+
"version": "0.12.0",
|
|
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>",
|
|
7
|
+
"mcpName": "ai.naumu/mcp",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/naumu-
|
|
10
|
-
"directory": "packages/mcp"
|
|
10
|
+
"url": "https://github.com/naumu-ai/mcp"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://naumu.ai",
|
|
13
|
-
"bugs": "https://github.com/naumu-
|
|
13
|
+
"bugs": "https://github.com/naumu-ai/mcp/issues",
|
|
14
14
|
"keywords": [
|
|
15
15
|
"mcp",
|
|
16
16
|
"model-context-protocol",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"type": "module",
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
|
-
"README.md"
|
|
31
|
+
"README.md",
|
|
32
|
+
"server.json"
|
|
32
33
|
],
|
|
33
34
|
"engines": {
|
|
34
35
|
"node": ">=18"
|
package/server.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "ai.naumu/mcp",
|
|
4
|
+
"title": "Naumu",
|
|
5
|
+
"description": "Search, extend, and act on your team's Naumu knowledge graph: notes, threads, and nodes.",
|
|
6
|
+
"version": "0.12.0",
|
|
7
|
+
"websiteUrl": "https://naumu.ai",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/naumu-ai/mcp",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"icons": [
|
|
13
|
+
{
|
|
14
|
+
"src": "https://cdn.naumu.ai/brand/icon-256.png",
|
|
15
|
+
"mimeType": "image/png",
|
|
16
|
+
"sizes": ["256x256"]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"remotes": [
|
|
20
|
+
{
|
|
21
|
+
"type": "streamable-http",
|
|
22
|
+
"url": "https://naumu.ai/api/mcp"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"packages": [
|
|
26
|
+
{
|
|
27
|
+
"registryType": "npm",
|
|
28
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
29
|
+
"identifier": "@naumu/mcp",
|
|
30
|
+
"version": "0.12.0",
|
|
31
|
+
"runtimeHint": "npx",
|
|
32
|
+
"transport": { "type": "stdio" },
|
|
33
|
+
"environmentVariables": [
|
|
34
|
+
{
|
|
35
|
+
"name": "NAUMU_API_KEY",
|
|
36
|
+
"description": "Naumu API key (starts with nmu_), created in Naumu under Settings > Your agents.",
|
|
37
|
+
"isRequired": true,
|
|
38
|
+
"isSecret": true,
|
|
39
|
+
"format": "string"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "NAUMU_API_URL",
|
|
43
|
+
"description": "Override the Naumu API base URL. Defaults to https://naumu.ai.",
|
|
44
|
+
"isRequired": false,
|
|
45
|
+
"isSecret": false,
|
|
46
|
+
"format": "string"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "NAUMU_IDENTITY_ID",
|
|
50
|
+
"description": "Optional bot identity id to act as when the key covers several identities.",
|
|
51
|
+
"isRequired": false,
|
|
52
|
+
"isSecret": false,
|
|
53
|
+
"format": "string"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|