@orboto/mcp 0.209.1 → 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/server.js +2 -0
- package/dist/tool-docs.js +11 -3
- package/dist/tools/feedback-reply.js +23 -0
- package/dist/tools/feedback-reply.test.js +27 -0
- 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/server.js
CHANGED
|
@@ -74,6 +74,7 @@ import { primerFactListToolConfig, makePrimerFactListHandler, primerFactAddToolC
|
|
|
74
74
|
import { whoamiToolConfig, makeWhoamiHandler } from './tools/identity.js';
|
|
75
75
|
import { claimToolConfig, makeClaimHandler, unclaimToolConfig, makeUnclaimHandler, } from './tools/claim.js';
|
|
76
76
|
import { reportFeedbackToolConfig, makeReportFeedbackHandler } from './tools/feedback.js';
|
|
77
|
+
import { feedbackReplyToolConfig, makeFeedbackReplyHandler } from './tools/feedback-reply.js';
|
|
77
78
|
import { listApprovalsToolConfig, makeListApprovalsHandler, approvalDecideToolConfig, makeApprovalDecideHandler, } from './tools/approvals.js';
|
|
78
79
|
import { reviewFingerprintToolConfig, makeReviewFingerprintHandler, reviewPolicyCheckToolConfig, makeReviewPolicyCheckHandler, reviewApprovalRecordToolConfig, makeReviewApprovalRecordHandler, } from './tools/review-policy.js';
|
|
79
80
|
import { createMilestoneToolConfig, makeCreateMilestoneHandler, closeMilestoneToolConfig, makeCloseMilestoneHandler, updateMilestoneToolConfig, makeUpdateMilestoneHandler, } from './tools/milestones.js';
|
|
@@ -339,6 +340,7 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
339
340
|
reg('orboto_claim', claimToolConfig, makeClaimHandler(client));
|
|
340
341
|
reg('orboto_unclaim', unclaimToolConfig, makeUnclaimHandler(client));
|
|
341
342
|
reg('orboto_report_feedback', reportFeedbackToolConfig, makeReportFeedbackHandler(client));
|
|
343
|
+
reg('orboto_feedback_reply', feedbackReplyToolConfig, makeFeedbackReplyHandler(client));
|
|
342
344
|
reg('orboto_list_approvals', listApprovalsToolConfig, makeListApprovalsHandler(client));
|
|
343
345
|
reg('orboto_approval_decide', approvalDecideToolConfig, makeApprovalDecideHandler(client));
|
|
344
346
|
reg('orboto_review_fingerprint', reviewFingerprintToolConfig, makeReviewFingerprintHandler(client));
|
package/dist/tool-docs.js
CHANGED
|
@@ -12,12 +12,20 @@ export const SUMMARY_MAX_CHARS = 220;
|
|
|
12
12
|
* name the key inputs.
|
|
13
13
|
*/
|
|
14
14
|
const SUMMARY_OVERRIDES = {
|
|
15
|
-
orboto_create_full_backup: 'Start a full-workspace backup job
|
|
15
|
+
orboto_create_full_backup: 'Start a full-workspace backup job; returns the job id to poll via orboto_list_backups.',
|
|
16
16
|
orboto_get_ticket: 'Fetch one ticket by key or UUID with description, status, assignees, checklists and comments.',
|
|
17
|
-
orboto_requirements_spec: 'Generate a structured requirements spec for a project or milestone from its tickets
|
|
17
|
+
orboto_requirements_spec: 'Generate a structured requirements spec for a project or milestone from its tickets.',
|
|
18
18
|
orboto_update_doc_space: 'Update a doc space\'s name, description, icon, project binding or access mode (open/restricted) by space id.',
|
|
19
19
|
orboto_search_docs: 'Full-text search over doc/wiki pages (query, optional space or project filter); returns matching pages with snippets.',
|
|
20
|
-
orboto_critical_path: 'Compute the
|
|
20
|
+
orboto_critical_path: 'Compute the critical path (blocking dependency chain) for a project or milestone.',
|
|
21
|
+
/** ORB-2237 - shortened summaries that pay for `orboto_feedback_reply`; each tool's full text stays in `getToolDoc`. */
|
|
22
|
+
orboto_agent_broadcast: 'Fan-out a message to every agent in a workspace, project or topic scope.',
|
|
23
|
+
orboto_api_search: 'Discover REST API endpoints on demand - the escape hatch for any orboto endpoint.',
|
|
24
|
+
orboto_get_doc: 'Return a doc\'s content (Markdown) plus its backlinks.',
|
|
25
|
+
orboto_primer_fact_list: 'List structured project facts feeding the AI primer.',
|
|
26
|
+
orboto_bulk_move_tickets: 'Move every matching ticket to the same status category.',
|
|
27
|
+
orboto_resolve_doc_smart_links: 'Resolve doc/ticket/milestone/project smart-link references to their title + URL.',
|
|
28
|
+
orboto_admin_translation_revert: 'Restore the pre-translation title + description and clear the translation marker.',
|
|
21
29
|
};
|
|
22
30
|
/** Runtime registry: tool name -> full guidance text, captured at
|
|
23
31
|
* registration. Module-global on purpose - the HTTP transport builds one
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORB-2237 - `orboto_feedback_reply`: the operator's public reply to a
|
|
3
|
+
* tenant report, one step (POST /tickets/:id/feedback-reply).
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export const feedbackReplyToolConfig = {
|
|
7
|
+
title: 'Reply to feedback report',
|
|
8
|
+
description: 'Post the operator\'s public reply on a feedback ticket. Synced to the tenant. Refuses secrets.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
ticketKey: z.string().min(1).max(64),
|
|
11
|
+
content: z.string().min(1).max(20_000),
|
|
12
|
+
}).shape,
|
|
13
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
14
|
+
};
|
|
15
|
+
export function makeFeedbackReplyHandler(client) {
|
|
16
|
+
return async (args) => {
|
|
17
|
+
const res = await client.post(`/tickets/${encodeURIComponent(args.ticketKey)}/feedback-reply`, { content: args.content });
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: 'text', text: `Reply posted on ${args.ticketKey} (comment ${res.commentId}, ${res.repliedAt}).` }],
|
|
20
|
+
structuredContent: res,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORB-2237 - `orboto_feedback_reply` unit test.
|
|
3
|
+
*/
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { OrbotoClient } from '../orboto-client.js';
|
|
6
|
+
import { makeFeedbackReplyHandler } from './feedback-reply.js';
|
|
7
|
+
beforeEach(() => { vi.restoreAllMocks(); });
|
|
8
|
+
afterEach(() => { vi.restoreAllMocks(); });
|
|
9
|
+
const client = new OrbotoClient({ baseUrl: 'http://api.test', apiKey: 'orb_test' });
|
|
10
|
+
describe('orboto_feedback_reply', () => {
|
|
11
|
+
it('posts the reply to the feedback-reply route and reports the comment id', async () => {
|
|
12
|
+
const calls = [];
|
|
13
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (url, init) => {
|
|
14
|
+
calls.push({ url: url.toString(), body: init?.body ? JSON.parse(init.body) : undefined });
|
|
15
|
+
return {
|
|
16
|
+
ok: true, status: 200, statusText: 'OK',
|
|
17
|
+
json: async () => ({ commentId: 'c-1', repliedAt: '2026-09-28T10:00:00.000Z' }),
|
|
18
|
+
text: async () => '',
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
const result = await makeFeedbackReplyHandler(client)({ ticketKey: 'FBK-1', content: 'Fixed in the next release.' });
|
|
22
|
+
expect(calls[0].url).toContain('/tickets/FBK-1/feedback-reply');
|
|
23
|
+
expect(calls[0].body).toEqual({ content: 'Fixed in the next release.' });
|
|
24
|
+
expect(result.structuredContent).toEqual({ commentId: 'c-1', repliedAt: '2026-09-28T10:00:00.000Z' });
|
|
25
|
+
expect(result.content[0]).toMatchObject({ type: 'text' });
|
|
26
|
+
});
|
|
27
|
+
});
|
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.
|
|
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",
|