@dotdrelle/wiki-manager 0.6.26 → 0.6.30
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 +27 -13
- package/package.json +1 -1
- package/src/agent/graph.js +2 -2
- package/src/commands/slash.js +6 -1
- package/src/core/mcp.js +14 -7
- package/src/core/mcp.test.js +103 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ and a *replaceable* toolbox of *external* MCP servers — to produce the **core
|
|
|
33
33
|
wiki** outputs, all driven by an agentic, multi-model orchestrator and grounded
|
|
34
34
|
in isolated workspaces.
|
|
35
35
|
|
|
36
|
-

|
|
36
|
+

|
|
37
37
|
|
|
38
38
|
## Quick start — your first wiki in ~5 minutes
|
|
39
39
|
|
|
@@ -56,7 +56,7 @@ mkdir -p ~/llm-wiki && cd ~/llm-wiki # all manager state lives here
|
|
|
56
56
|
**2 — Set the environment.**
|
|
57
57
|
Copy the template and keep the defaults — nothing is mandatory for the local
|
|
58
58
|
demo (tokens/credentials are only needed when you connect real sources, see
|
|
59
|
-
[docs/usage.md](docs/usage.md)). The `mcp.endpoints.json` file is created
|
|
59
|
+
[docs/usage.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/usage.md)). The `mcp.endpoints.json` file is created
|
|
60
60
|
automatically on the first command.
|
|
61
61
|
|
|
62
62
|
```bash
|
|
@@ -137,20 +137,10 @@ wiki-workspace wiki demo build # or, in the shell: /skills run pipeli
|
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
That's the whole loop. Next: the four ways to use it and how to configure the
|
|
140
|
-
external agents (CME & co.) live in [docs/usage.md](docs/usage.md); the detailed
|
|
140
|
+
external agents (CME & co.) live in [docs/usage.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/usage.md); the detailed
|
|
141
141
|
story is in [The journey](#the-journey-from-first-launch-to-first-result); and
|
|
142
142
|
installing from source is in [Initial Setup](#initial-setup).
|
|
143
143
|
|
|
144
|
-
## The 4 ways to use it & agent configuration
|
|
145
|
-
|
|
146
|
-
The same system has four faces — the **web interface** (explore with the mouse),
|
|
147
|
-
**scripting** (let it run on its own), the **`donna` shell** (talk in plain
|
|
148
|
-
language), and the **shared external agents** (the common toolbox). Each external
|
|
149
|
-
agent (Confluence export with **CME**, document conversion, mail) also needs a
|
|
150
|
-
little setup the first time.
|
|
151
|
-
|
|
152
|
-
Both are covered in **[docs/usage.md](docs/usage.md)**.
|
|
153
|
-
|
|
154
144
|
## The journey: from first launch to first result
|
|
155
145
|
|
|
156
146
|
Follow this little story in order. By the end you'll have seen it all: the
|
|
@@ -353,6 +343,30 @@ ingest -> build -> export -> polish
|
|
|
353
343
|
The legacy copy step is only for deployments that explicitly configure external
|
|
354
344
|
import mappings.
|
|
355
345
|
|
|
346
|
+
## Configuration overview
|
|
347
|
+
|
|
348
|
+
wikiLLM is configured by **four files** held together by two families of keys:
|
|
349
|
+
**MCP keys** (Bearer tokens that authenticate *who connects to whom*) and **LLM
|
|
350
|
+
keys** (`apiKey` + `baseUrl` that *reach a model*).
|
|
351
|
+
|
|
352
|
+

|
|
353
|
+
|
|
354
|
+
| File | Owner | Scope | Holds |
|
|
355
|
+
| --- | --- | --- | --- |
|
|
356
|
+
| `.env` | manager | global | shared secrets: agent MCP tokens, MailerSend, OCR LLM, port overrides |
|
|
357
|
+
| `mcp.endpoints.json` | manager | global | where each external agent lives + which `Bearer`/header to send |
|
|
358
|
+
| `workspaces/<name>/.env` | manager | per workspace | ports, workspace path, the wiki's own MCP tokens |
|
|
359
|
+
| `workspaces/<name>/.wikirc.yaml` (+ `.wikirc.yaml.<profile>`) | workspace | per workspace | LLM & vector keys (provider/model/apiKey/baseUrl/retrieval) |
|
|
360
|
+
|
|
361
|
+
Donna reaches the external agents and the internal wiki MCP through Bearer
|
|
362
|
+
tokens; the wiki then uses its `.wikirc.yaml` LLM keys to call models and
|
|
363
|
+
embeddings. Because every MCP server is an HTTP endpoint, remote MCP clients can
|
|
364
|
+
connect to the same surfaces with the same tokens. **MCP keys** are set in the
|
|
365
|
+
root `.env`; the wiki's **LLM keys** live in each workspace `.wikirc.yaml`.
|
|
366
|
+
|
|
367
|
+
See the full, field-by-field reference in
|
|
368
|
+
**[docs/configuration.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/configuration.md)**.
|
|
369
|
+
|
|
356
370
|
## Initial Setup
|
|
357
371
|
|
|
358
372
|
```bash
|
package/package.json
CHANGED
package/src/agent/graph.js
CHANGED
|
@@ -322,8 +322,8 @@ export function buildAgentSystemPrompt(state) {
|
|
|
322
322
|
skills,
|
|
323
323
|
'You can call MCP tools directly using the provided tool functions.',
|
|
324
324
|
'When the user asks for an action that can be performed with connected MCP tools or safe primitives, do not answer with future intent such as "I will call...", "I am going to run...", or "launching..." unless you also call the tool in the same turn. Either call the tool now, ask for the exact missing required arguments, or explain the concrete blocker.',
|
|
325
|
-
'For
|
|
326
|
-
'For workspace-scoped external MCP tools, the orchestrator enforces workspace injection.
|
|
325
|
+
'For connector configuration/setup/update requests, if a matching setup/configuration tool is connected and the required arguments are known, call it immediately. If the connector or tool is not connected, say which concrete capability is missing and recommend the exact service/status primitive to inspect it. Do not invent a pending connector action in plain text.',
|
|
326
|
+
'For workspace-scoped external MCP tools, the orchestrator enforces workspace injection. Use the active workspace for configuration, source, import, export, conversion, and generation tools unless a tool is explicitly job-scoped and only requires a job id.',
|
|
327
327
|
'You can call shell__run_command for safe manager slash commands such as /workspaces, /new <name> [path], /use <workspace>, /config, /status, /services, /skills, /skills show <name>, and /skills run <name>.',
|
|
328
328
|
'Skills are workflow instructions, not executable code. When a user asks to run a skill, inspect it, propose the concrete primitive/tool plan, and ask for confirmation before costly or mutating actions.',
|
|
329
329
|
[
|
package/src/commands/slash.js
CHANGED
|
@@ -658,7 +658,11 @@ export async function handleSlashCommand(line, context) {
|
|
|
658
658
|
context.session.workspacePath = workspace.workspacePath;
|
|
659
659
|
context.session.workspaceEnv = workspace.env;
|
|
660
660
|
context.session.workspaceEnvFile = workspace.envFile;
|
|
661
|
-
context.session.
|
|
661
|
+
context.session.wikirc = null;
|
|
662
|
+
context.session.wikircConfig = null;
|
|
663
|
+
context.session.language = null;
|
|
664
|
+
context.session.llm = null;
|
|
665
|
+
context.session.mcp = null;
|
|
662
666
|
context.session.systemPrompt = loadWorkspaceSystemPrompt(workspace.workspacePath);
|
|
663
667
|
try {
|
|
664
668
|
step(`Workspace: loading ${workspace.name} config…`);
|
|
@@ -702,6 +706,7 @@ export async function handleSlashCommand(line, context) {
|
|
|
702
706
|
}
|
|
703
707
|
try {
|
|
704
708
|
const summary = loadSessionWikirc(context.session, profileName);
|
|
709
|
+
await refreshMcpRuntimeStatus(context.session);
|
|
705
710
|
return {
|
|
706
711
|
output: [
|
|
707
712
|
'Active wikirc:',
|
package/src/core/mcp.js
CHANGED
|
@@ -2,10 +2,12 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { managerEnvFile, managerMcpEndpointsFile, readEnvFile } from './env.js';
|
|
3
3
|
|
|
4
4
|
function envValue(key) {
|
|
5
|
-
if (key in process.env) return process.env[key];
|
|
6
5
|
const filePath = managerEnvFile();
|
|
7
|
-
if (
|
|
8
|
-
|
|
6
|
+
if (existsSync(filePath)) {
|
|
7
|
+
const fileValue = readEnvFile(filePath)[key];
|
|
8
|
+
if (fileValue !== undefined) return fileValue;
|
|
9
|
+
}
|
|
10
|
+
return process.env[key];
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
function interpolateEnv(value) {
|
|
@@ -75,16 +77,20 @@ const MCP_SERVICE_MAP = {
|
|
|
75
77
|
|
|
76
78
|
export function buildMcpStatus(session) {
|
|
77
79
|
const workspaceEnv = session.workspaceEnv ?? {};
|
|
80
|
+
const wikiMcpToken = session.wikircConfig?.mcp?.accessKey;
|
|
81
|
+
const wikiMcpDetail = workspaceEnv.WIKI_MCP_PORT
|
|
82
|
+
? (wikiMcpToken ? `:${workspaceEnv.WIKI_MCP_PORT}` : `:${workspaceEnv.WIKI_MCP_PORT} (mcp.accessKey missing in active wikirc)`)
|
|
83
|
+
: '';
|
|
78
84
|
const external = readExternalMcpEndpoints();
|
|
79
85
|
|
|
80
86
|
return {
|
|
81
87
|
wiki: {
|
|
82
88
|
...endpointStatus(
|
|
83
|
-
workspaceEnv.WIKI_MCP_PORT &&
|
|
84
|
-
|
|
89
|
+
workspaceEnv.WIKI_MCP_PORT && wikiMcpToken,
|
|
90
|
+
wikiMcpDetail,
|
|
85
91
|
),
|
|
86
92
|
url: workspaceEnv.WIKI_MCP_PORT ? `http://127.0.0.1:${workspaceEnv.WIKI_MCP_PORT}/mcp` : null,
|
|
87
|
-
token:
|
|
93
|
+
token: wikiMcpToken || null,
|
|
88
94
|
},
|
|
89
95
|
production: {
|
|
90
96
|
...endpointStatus(
|
|
@@ -205,7 +211,7 @@ async function mcpRequest(endpoint, method, params, signal, options = {}) {
|
|
|
205
211
|
params: {
|
|
206
212
|
protocolVersion: '2025-06-18',
|
|
207
213
|
capabilities: {},
|
|
208
|
-
clientInfo: { name: 'wiki-manager', version: '0.6.
|
|
214
|
+
clientInfo: { name: 'wiki-manager', version: '0.6.30' },
|
|
209
215
|
},
|
|
210
216
|
}),
|
|
211
217
|
});
|
|
@@ -295,6 +301,7 @@ export async function discoverMcpTools(mcpStatus) {
|
|
|
295
301
|
const message = err instanceof Error ? err.message : String(err);
|
|
296
302
|
next[name] = {
|
|
297
303
|
...value,
|
|
304
|
+
status: value.status === 'connected' ? 'configured' : value.status,
|
|
298
305
|
tools: [],
|
|
299
306
|
toolError: message,
|
|
300
307
|
};
|
package/src/core/mcp.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { mkdtemp, writeFile } from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { buildMcpStatus, callMcpTool } from './mcp.js';
|
|
6
|
+
import { buildMcpStatus, callMcpTool, discoverMcpTools } from './mcp.js';
|
|
7
7
|
|
|
8
8
|
test('buildMcpStatus reads external MCP endpoints from mcp.endpoints.json', async () => {
|
|
9
9
|
const originalCwd = process.cwd();
|
|
@@ -87,6 +87,82 @@ test('buildMcpStatus interpolates external endpoints from manager .env', async (
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
test('buildMcpStatus reloads external endpoint keys changed in manager .env', async () => {
|
|
91
|
+
const originalCwd = process.cwd();
|
|
92
|
+
const originalToken = process.env.TEST_EXTERNAL_TOKEN;
|
|
93
|
+
const root = await mkdtemp(path.join(os.tmpdir(), 'wiki-manager-mcp-env-reload-'));
|
|
94
|
+
await writeFile(
|
|
95
|
+
path.join(root, '.env'),
|
|
96
|
+
'TEST_EXTERNAL_TOKEN=first-token\n',
|
|
97
|
+
'utf8',
|
|
98
|
+
);
|
|
99
|
+
await writeFile(
|
|
100
|
+
path.join(root, 'mcp.endpoints.json'),
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
mcpServers: {
|
|
103
|
+
external: {
|
|
104
|
+
url: 'http://127.0.0.1:9999/mcp/',
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: 'Bearer ${TEST_EXTERNAL_TOKEN}',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
'utf8',
|
|
112
|
+
);
|
|
113
|
+
process.env.TEST_EXTERNAL_TOKEN = 'first-token';
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
process.chdir(root);
|
|
117
|
+
await writeFile(
|
|
118
|
+
path.join(root, '.env'),
|
|
119
|
+
'TEST_EXTERNAL_TOKEN=second-token\n',
|
|
120
|
+
'utf8',
|
|
121
|
+
);
|
|
122
|
+
const status = buildMcpStatus({ workspaceEnv: {} });
|
|
123
|
+
assert.deepEqual(status.external.headers, {
|
|
124
|
+
authorization: 'Bearer second-token',
|
|
125
|
+
});
|
|
126
|
+
} finally {
|
|
127
|
+
process.chdir(originalCwd);
|
|
128
|
+
if (originalToken === undefined) delete process.env.TEST_EXTERNAL_TOKEN;
|
|
129
|
+
else process.env.TEST_EXTERNAL_TOKEN = originalToken;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('buildMcpStatus does not use workspace env token for wiki MCP without active wikirc accessKey', () => {
|
|
134
|
+
const status = buildMcpStatus({
|
|
135
|
+
workspaceEnv: {
|
|
136
|
+
WIKI_MCP_PORT: '3101',
|
|
137
|
+
WIKI_MCP_AUTH_TOKEN: 'wiki-token-2',
|
|
138
|
+
PRODUCTION_MCP_PORT: '3102',
|
|
139
|
+
PRODUCTION_MCP_AUTH_TOKEN: 'production-token-2',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
assert.equal(status.wiki.status, 'missing');
|
|
144
|
+
assert.equal(status.wiki.token, null);
|
|
145
|
+
assert.match(status.wiki.detail, /mcp\.accessKey missing/);
|
|
146
|
+
assert.equal(status.production.token, 'production-token-2');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('buildMcpStatus uses active wikirc mcp.accessKey for wiki MCP', () => {
|
|
150
|
+
const status = buildMcpStatus({
|
|
151
|
+
workspaceEnv: {
|
|
152
|
+
WIKI_MCP_PORT: '3101',
|
|
153
|
+
WIKI_MCP_AUTH_TOKEN: 'env-wiki-token',
|
|
154
|
+
},
|
|
155
|
+
wikircConfig: {
|
|
156
|
+
mcp: {
|
|
157
|
+
accessKey: 'wikirc-wiki-token',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
assert.equal(status.wiki.status, 'configured');
|
|
163
|
+
assert.equal(status.wiki.token, 'wikirc-wiki-token');
|
|
164
|
+
});
|
|
165
|
+
|
|
90
166
|
test('callMcpTool injects active configPath for production_start_job', async () => {
|
|
91
167
|
const originalFetch = globalThis.fetch;
|
|
92
168
|
let requestBody = null;
|
|
@@ -193,6 +269,32 @@ test('callMcpTool sends configured endpoint headers', async () => {
|
|
|
193
269
|
}
|
|
194
270
|
});
|
|
195
271
|
|
|
272
|
+
test('discoverMcpTools downgrades connected endpoint when tool discovery fails', async () => {
|
|
273
|
+
const originalFetch = globalThis.fetch;
|
|
274
|
+
globalThis.fetch = async () => ({
|
|
275
|
+
ok: false,
|
|
276
|
+
status: 401,
|
|
277
|
+
headers: { get: () => null },
|
|
278
|
+
text: async () => '{"error":"invalid or missing bearer token"}',
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const status = await discoverMcpTools({
|
|
283
|
+
wiki: {
|
|
284
|
+
status: 'connected',
|
|
285
|
+
url: 'http://127.0.0.1:3201/mcp',
|
|
286
|
+
token: 'token',
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
assert.equal(status.wiki.status, 'configured');
|
|
291
|
+
assert.equal(status.wiki.tools.length, 0);
|
|
292
|
+
assert.match(status.wiki.toolError, /401/);
|
|
293
|
+
} finally {
|
|
294
|
+
globalThis.fetch = originalFetch;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
196
298
|
test('callMcpTool parses SSE responses after keepalive comments', async () => {
|
|
197
299
|
const originalFetch = globalThis.fetch;
|
|
198
300
|
globalThis.fetch = async () => ({
|