@orboto/mcp 0.210.0 → 0.210.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/tools/knowledge.js +2 -1
- package/dist/tools/knowledge.test.js +13 -0
- package/dist/tools/wiki.js +12 -4
- package/dist/tools/wiki.test.js +7 -0
- package/package.json +1 -1
package/dist/tools/knowledge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** ORB-2225 - the wiki as the agents' knowledge base: ask it first, file durable facts in it. */
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { mcpInstanceToken } from './shared.js';
|
|
4
|
+
import { droppedLines } from './wiki.js';
|
|
4
5
|
export const knowledgeAskToolConfig = {
|
|
5
6
|
title: 'Ask the wiki',
|
|
6
7
|
description: 'Ask your session\'s project wikis and the Operations wiki before asking a person; cite docs by key.',
|
|
@@ -30,6 +31,6 @@ export const knowledgeAddToolConfig = {
|
|
|
30
31
|
export function makeKnowledgeAddHandler(client) {
|
|
31
32
|
return async (input, extra) => {
|
|
32
33
|
const res = await client.post('/knowledge/add', input, { instanceToken: mcpInstanceToken(undefined, extra) });
|
|
33
|
-
return { content: [{ type: 'text', text: res.text }], structuredContent: res };
|
|
34
|
+
return { content: [{ type: 'text', text: `${res.text}${droppedLines(res.droppedOps ?? [])}` }], structuredContent: res };
|
|
34
35
|
};
|
|
35
36
|
}
|
|
@@ -26,6 +26,14 @@ describe('ORB-2225 orboto_knowledge_ask and orboto_knowledge_add', () => {
|
|
|
26
26
|
expect(textOf(result)).toContain('ACME-D7');
|
|
27
27
|
expect(result.structuredContent).toMatchObject({ action: 'recorded' });
|
|
28
28
|
});
|
|
29
|
+
it('files a fact and lists the ops the wiki agent dropped, as wiki record does', async () => {
|
|
30
|
+
const droppedOps = [{ title: 'Runner', target: 'ACME-D99', reason: 'unknown_target' }];
|
|
31
|
+
const post = vi.fn().mockResolvedValue({ action: 'recorded', text: 'Recorded in the ACME-S1 wiki: ACME-D7 "Build host". Cite it by key.', pages: [], source: null, droppedOps });
|
|
32
|
+
const result = await makeKnowledgeAddHandler({ post })({ title: 'Build host', content: 'build.example.org', projectKey: 'ACME' });
|
|
33
|
+
expect(textOf(result)).toContain('Ops dropped: 1');
|
|
34
|
+
expect(textOf(result)).toContain('- Runner (target ACME-D99): unknown_target');
|
|
35
|
+
expect(result.structuredContent).toMatchObject({ droppedOps });
|
|
36
|
+
});
|
|
29
37
|
it('both tools carry a title and annotations; ask is read-only, add is not', () => {
|
|
30
38
|
expect(knowledgeAskToolConfig.title).toBeTruthy();
|
|
31
39
|
expect(knowledgeAskToolConfig.annotations.readOnlyHint).toBe(true);
|
|
@@ -71,4 +79,9 @@ describe('ORB-2225 orboto_wiki_runs', () => {
|
|
|
71
79
|
expect(textOf(await handler({ spaceId: 'ACME-S1', action: 'retry', runId: '11111111-0000-4000-8000-000000000000' }))).toContain('ran again: applied');
|
|
72
80
|
expect(post).toHaveBeenCalledWith('/spaces/ACME-S1/llm-wiki/ingest-runs/11111111-0000-4000-8000-000000000000/retry', {});
|
|
73
81
|
});
|
|
82
|
+
it('ORB-2226 - a run that left planner ops out says how many', async () => {
|
|
83
|
+
const get = vi.fn().mockResolvedValue({ runs: [{ id: 'r2', sourceKey: 'ACME-D10', sourceTitle: 'Deploy notes', status: 'applied', error: null, droppedOps: [{ title: 'Deploy', target: 'ACME-D99', reason: 'unknown_target' }], createdAt: '2026-09-27T09:00:00Z' }] });
|
|
84
|
+
const handler = makeWikiRunsHandler({ get, post: vi.fn() });
|
|
85
|
+
expect(textOf(await handler({ spaceId: 'ACME-S1' }))).toContain('r2 applied ACME-D10 "Deploy notes" 2026-09-27T09:00:00Z (ops dropped: 1)');
|
|
86
|
+
});
|
|
74
87
|
});
|
package/dist/tools/wiki.js
CHANGED
|
@@ -69,9 +69,15 @@ export function makeWikiLintHandler(client) {
|
|
|
69
69
|
return text(`Lint complete: ${res.issues.length} open issue(s), ${res.resolved} auto-resolved.\n${lines.join('\n')}`, { issues: res.issues, detected: res.detected, resolved: res.resolved });
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
/** ORB-2226 - planner ops whose target page matched no docId, doc key or title. */
|
|
73
|
+
export function droppedLines(dropped) {
|
|
74
|
+
if (dropped.length === 0)
|
|
75
|
+
return '';
|
|
76
|
+
return `\nOps dropped: ${dropped.length}\n${dropped.map((d) => `- ${d.title}${d.target ? ` (target ${d.target})` : ''}: ${d.reason}`).join('\n')}`;
|
|
77
|
+
}
|
|
72
78
|
export const wikiPlanUpdateToolConfig = {
|
|
73
79
|
title: 'Plan a wiki edit (dry-run, no writes)',
|
|
74
|
-
description: 'Turn a natural-language instruction into page operations (create/patch/append) WITHOUT writing anything. Returns a planId valid for 15 minutes plus the proposed ops. Review the ops, then call orboto_wiki_apply_plan to commit. Wraps POST /spaces/:id/docs/plan-update.',
|
|
80
|
+
description: 'Turn a natural-language instruction into page operations (create/patch/append) WITHOUT writing anything. Returns a planId valid for 15 minutes plus the proposed ops, and droppedOps: ops whose target page matched no docId, doc key or title. Review the ops, then call orboto_wiki_apply_plan to commit. Wraps POST /spaces/:id/docs/plan-update.',
|
|
75
81
|
inputSchema: z.object({
|
|
76
82
|
spaceId: z.string().uuid().describe('The wiki space to edit.'),
|
|
77
83
|
instruction: z.string().min(1).max(4000).describe('What to change, in plain language.'),
|
|
@@ -86,7 +92,8 @@ export function makeWikiPlanUpdateHandler(client) {
|
|
|
86
92
|
body.sourceDocId = input.sourceDocId;
|
|
87
93
|
const res = await client.post(`/spaces/${input.spaceId}/docs/plan-update`, body);
|
|
88
94
|
const ops = res.ops.map((o, i) => `${i + 1}. ${o.op} ${o.title ?? ''} - ${o.summary}`).join('\n');
|
|
89
|
-
|
|
95
|
+
const droppedOps = res.droppedOps ?? [];
|
|
96
|
+
return text(`Plan ${res.planId} (expires ${res.expiresAt}):\n${ops}${droppedLines(droppedOps)}\n\nApply with orboto_wiki_apply_plan(planId).`, { planId: res.planId, ops: res.ops, droppedOps, expiresAt: res.expiresAt });
|
|
90
97
|
};
|
|
91
98
|
}
|
|
92
99
|
export const wikiApplyPlanToolConfig = {
|
|
@@ -121,7 +128,8 @@ export function makeWikiRecordHandler(client) {
|
|
|
121
128
|
planBody.sourceDocId = input.sourceDocId;
|
|
122
129
|
const plan = await client.post(`/spaces/${input.spaceId}/docs/plan-update`, planBody);
|
|
123
130
|
const applied = await client.post(`/spaces/${input.spaceId}/docs/apply-plan`, { planId: plan.planId });
|
|
124
|
-
|
|
131
|
+
const droppedOps = plan.droppedOps ?? [];
|
|
132
|
+
return text(`Recorded: ${applied.touchedDocs.length} page(s) updated.${droppedLines(droppedOps)}`, { planId: plan.planId, touchedDocs: applied.touchedDocs, droppedOps });
|
|
125
133
|
};
|
|
126
134
|
}
|
|
127
135
|
export const wikiAppendSectionToolConfig = {
|
|
@@ -196,7 +204,7 @@ export function makeWikiRunsHandler(client) {
|
|
|
196
204
|
}
|
|
197
205
|
const qs = input.status ? `?status=${input.status}` : '';
|
|
198
206
|
const res = await client.get(`/spaces/${encodeURIComponent(input.spaceId)}/llm-wiki/ingest-runs${qs}`);
|
|
199
|
-
const lines = res.runs.map((r) => `- ${r.id} ${r.status} ${r.sourceKey ?? ''} "${r.sourceTitle}" ${r.createdAt}${r.error ? ` - ${r.error}` : ''}`);
|
|
207
|
+
const lines = res.runs.map((r) => `- ${r.id} ${r.status} ${r.sourceKey ?? ''} "${r.sourceTitle}" ${r.createdAt}${r.droppedOps?.length ? ` (ops dropped: ${r.droppedOps.length})` : ''}${r.error ? ` - ${r.error}` : ''}`);
|
|
200
208
|
return text(lines.length ? lines.join('\n') : 'No ingest runs.', res);
|
|
201
209
|
};
|
|
202
210
|
}
|
package/dist/tools/wiki.test.js
CHANGED
|
@@ -57,6 +57,13 @@ describe('orboto_wiki_plan_update + apply_plan', () => {
|
|
|
57
57
|
expect(calls[0].url).toBe(`https://orboto.example.com/spaces/${SPACE}/docs/plan-update`);
|
|
58
58
|
expect(res.content[0].text).toContain('Plan p1');
|
|
59
59
|
});
|
|
60
|
+
it('ORB-2226 - plan-update names the ops the planner target could not be resolved for', async () => {
|
|
61
|
+
stubJSON([{ json: { planId: 'p1', ops: [], droppedOps: [{ title: 'Runbook', target: 'ghost page', reason: 'unknown_target' }], expiresAt: '2026-01-01T00:15:00Z' } }]);
|
|
62
|
+
const res = await makeWikiPlanUpdateHandler(client)({ spaceId: SPACE, instruction: 'update the runbook' });
|
|
63
|
+
const out = res.content[0].text;
|
|
64
|
+
expect(out).toContain('Ops dropped: 1');
|
|
65
|
+
expect(out).toContain('- Runbook (target ghost page): unknown_target');
|
|
66
|
+
});
|
|
60
67
|
it('apply-plan reports touched docs', async () => {
|
|
61
68
|
stubJSON([{ json: { touchedDocs: [DOC] } }]);
|
|
62
69
|
const res = await makeWikiApplyPlanHandler(client)({ spaceId: SPACE, planId: 'p1' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orboto/mcp",
|
|
3
|
-
"version": "0.210.
|
|
3
|
+
"version": "0.210.1",
|
|
4
4
|
"description": "Model Context Protocol server for orboto - exposes tickets, projects, milestones, time, docs, primer-facts, alerts and absences as MCP tools for Claude Desktop / Cursor / Copilot Chat / any MCP-aware AI client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|