@orboto/mcp 0.178.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/LICENSE.md +21 -0
- package/README.md +123 -0
- package/dist/event-bridge.js +250 -0
- package/dist/event-bridge.test.js +165 -0
- package/dist/http-transport-binding.test.js +151 -0
- package/dist/http-transport.js +608 -0
- package/dist/http-transport.test.js +408 -0
- package/dist/index.js +128 -0
- package/dist/input-schema.js +293 -0
- package/dist/input-schema.test.js +198 -0
- package/dist/instructions-budget.test.js +34 -0
- package/dist/manifest-size.test.js +192 -0
- package/dist/mcp-instrument.js +24 -0
- package/dist/oauth-bootstrap.js +413 -0
- package/dist/oauth-bootstrap.test.js +212 -0
- package/dist/orboto-client.js +250 -0
- package/dist/orboto-client.test.js +141 -0
- package/dist/prompts.js +219 -0
- package/dist/prompts.test.js +79 -0
- package/dist/protect-text-usage.test.js +38 -0
- package/dist/resources.js +260 -0
- package/dist/resources.test.js +191 -0
- package/dist/response-budget.js +515 -0
- package/dist/response-budget.test.js +343 -0
- package/dist/server.js +547 -0
- package/dist/session-nudge.js +81 -0
- package/dist/session-nudge.test.js +100 -0
- package/dist/session-nudge.transport.test.js +100 -0
- package/dist/tool-annotations.test.js +97 -0
- package/dist/tool-docs.js +64 -0
- package/dist/tool-docs.test.js +90 -0
- package/dist/tool-parameters.test.js +145 -0
- package/dist/tools/absence-writes.js +93 -0
- package/dist/tools/absence-writes.test.js +106 -0
- package/dist/tools/admin-translations.js +83 -0
- package/dist/tools/admin-writes.js +174 -0
- package/dist/tools/admin-writes.test.js +120 -0
- package/dist/tools/agent-coordination.js +160 -0
- package/dist/tools/agent-coordination.test.js +156 -0
- package/dist/tools/agent-drift.js +79 -0
- package/dist/tools/agent-drift.test.js +87 -0
- package/dist/tools/agent-instructions.js +172 -0
- package/dist/tools/agent-instructions.test.js +124 -0
- package/dist/tools/agent-messages.js +75 -0
- package/dist/tools/ai-status.js +55 -0
- package/dist/tools/ai-status.test.js +43 -0
- package/dist/tools/ai-usage.js +68 -0
- package/dist/tools/analytics.js +72 -0
- package/dist/tools/analytics.test.js +71 -0
- package/dist/tools/api-call.js +72 -0
- package/dist/tools/api-call.test.js +104 -0
- package/dist/tools/api-search.js +77 -0
- package/dist/tools/api-search.test.js +90 -0
- package/dist/tools/approvals.js +68 -0
- package/dist/tools/approvals.test.js +48 -0
- package/dist/tools/attach.js +108 -0
- package/dist/tools/attach.test.js +117 -0
- package/dist/tools/backup.js +93 -0
- package/dist/tools/backup.test.js +108 -0
- package/dist/tools/bulk-create.js +213 -0
- package/dist/tools/bulk-create.test.js +135 -0
- package/dist/tools/bulk-writes.js +339 -0
- package/dist/tools/bulk-writes.test.js +239 -0
- package/dist/tools/check-similar.js +84 -0
- package/dist/tools/check-similar.test.js +101 -0
- package/dist/tools/checklist-writes.js +202 -0
- package/dist/tools/checklist-writes.test.js +184 -0
- package/dist/tools/claim.js +236 -0
- package/dist/tools/claim.test.js +207 -0
- package/dist/tools/critical-path.js +83 -0
- package/dist/tools/critical-path.test.js +114 -0
- package/dist/tools/cross-project-links.ee.js +124 -0
- package/dist/tools/cross-project-links.ee.test.js +163 -0
- package/dist/tools/customer-report.js +88 -0
- package/dist/tools/customer-report.test.js +55 -0
- package/dist/tools/doc-attachments.js +184 -0
- package/dist/tools/doc-attachments.test.js +136 -0
- package/dist/tools/doc-comments.js +228 -0
- package/dist/tools/doc-comments.test.js +192 -0
- package/dist/tools/doc-edits.js +229 -0
- package/dist/tools/doc-edits.test.js +218 -0
- package/dist/tools/doc-export.js +87 -0
- package/dist/tools/doc-export.test.js +109 -0
- package/dist/tools/doc-revisions.js +133 -0
- package/dist/tools/doc-revisions.test.js +103 -0
- package/dist/tools/docs-ai.js +171 -0
- package/dist/tools/docs-ai.test.js +160 -0
- package/dist/tools/docs.js +557 -0
- package/dist/tools/docs.test.js +365 -0
- package/dist/tools/embedding-status.js +100 -0
- package/dist/tools/embedding-status.test.js +86 -0
- package/dist/tools/free-busy.js +49 -0
- package/dist/tools/free-busy.test.js +46 -0
- package/dist/tools/get-checklists.js +72 -0
- package/dist/tools/get-project-primer.js +58 -0
- package/dist/tools/get-project.js +77 -0
- package/dist/tools/get-ticket.js +321 -0
- package/dist/tools/get-timer.js +62 -0
- package/dist/tools/help.js +44 -0
- package/dist/tools/identity.js +60 -0
- package/dist/tools/identity.test.js +67 -0
- package/dist/tools/list-git-app-installations.js +45 -0
- package/dist/tools/list-projects.js +72 -0
- package/dist/tools/list-projects.test.js +67 -0
- package/dist/tools/list-tickets.js +86 -0
- package/dist/tools/milestones-crud.test.js +154 -0
- package/dist/tools/milestones.js +279 -0
- package/dist/tools/milestones.test.js +60 -0
- package/dist/tools/my-tickets.js +65 -0
- package/dist/tools/personal-facts.js +72 -0
- package/dist/tools/personal-facts.test.js +45 -0
- package/dist/tools/phase-b-tools.test.js +530 -0
- package/dist/tools/portfolio.js +56 -0
- package/dist/tools/primer-facts.js +346 -0
- package/dist/tools/primer-facts.test.js +353 -0
- package/dist/tools/project-listings.js +116 -0
- package/dist/tools/project-listings.test.js +106 -0
- package/dist/tools/query.js +65 -0
- package/dist/tools/query.test.js +128 -0
- package/dist/tools/raci.js +108 -0
- package/dist/tools/raci.test.js +70 -0
- package/dist/tools/requirements-spec.js +89 -0
- package/dist/tools/requirements-spec.test.js +62 -0
- package/dist/tools/response-expand.js +104 -0
- package/dist/tools/review-policy.js +105 -0
- package/dist/tools/review-policy.test.js +71 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/session-start.js +435 -0
- package/dist/tools/session-start.test.js +568 -0
- package/dist/tools/set-parent.js +76 -0
- package/dist/tools/set-parent.test.js +115 -0
- package/dist/tools/shared.js +226 -0
- package/dist/tools/shared.test.js +169 -0
- package/dist/tools/similar-projection.js +30 -0
- package/dist/tools/ticket-attachments.js +131 -0
- package/dist/tools/ticket-attachments.test.js +106 -0
- package/dist/tools/ticket-schedules.js +112 -0
- package/dist/tools/ticket-schedules.test.js +102 -0
- package/dist/tools/ticket-writes.js +778 -0
- package/dist/tools/ticket-writes.test.js +643 -0
- package/dist/tools/time-writes.js +238 -0
- package/dist/tools/time-writes.test.js +138 -0
- package/dist/tools/update-project.js +165 -0
- package/dist/tools/update-project.test.js +172 -0
- package/dist/tools/wiki.js +185 -0
- package/dist/tools/wiki.test.js +104 -0
- package/dist/tools/work-sessions-next.test.js +150 -0
- package/dist/tools/work-sessions-start.test.js +174 -0
- package/dist/tools/work-sessions.js +709 -0
- package/dist/tools/work-sessions.test.js +358 -0
- package/dist/toolset.js +149 -0
- package/dist/toolset.test.js +150 -0
- package/dist/version-guard.test.js +51 -0
- package/dist/version.js +28 -0
- package/dist/with-metrics-mail-nudge.test.js +41 -0
- package/dist/with-metrics-output-schema.test.js +91 -0
- package/dist/with-metrics.js +211 -0
- package/dist/with-metrics.test.js +230 -0
- package/package.json +66 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORB-244 Phase D - resources unit tests.
|
|
3
|
+
*
|
|
4
|
+
* The MCP SDK's resource-template handlers are normal functions
|
|
5
|
+
* once registered. Pulling them off the server's internal registry
|
|
6
|
+
* would couple us to SDK internals; instead we register against a
|
|
7
|
+
* real McpServer and read back via the protocol-shaped
|
|
8
|
+
* `_registeredResourceTemplates` accessor. If the SDK renames it,
|
|
9
|
+
* the test fails loudly, but that's the point - we want to know.
|
|
10
|
+
*
|
|
11
|
+
* Each test exercises the URI → handler-output path with the
|
|
12
|
+
* stubbed orboto REST client.
|
|
13
|
+
*/
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
15
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
16
|
+
import { OrbotoClient } from './orboto-client.js';
|
|
17
|
+
import { registerOrbotoResources } from './resources.js';
|
|
18
|
+
beforeEach(() => { vi.restoreAllMocks(); });
|
|
19
|
+
afterEach(() => { vi.restoreAllMocks(); });
|
|
20
|
+
function stub(responses) {
|
|
21
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
|
|
22
|
+
const r = responses.shift();
|
|
23
|
+
if (!r)
|
|
24
|
+
throw new Error('unexpected fetch');
|
|
25
|
+
return {
|
|
26
|
+
ok: r.ok ?? true,
|
|
27
|
+
status: r.status ?? 200,
|
|
28
|
+
statusText: 'OK',
|
|
29
|
+
json: async () => ('json' in r ? r.json : {}),
|
|
30
|
+
text: async () => '',
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const client = new OrbotoClient({ baseUrl: 'https://orboto.example.com', apiKey: 'orb_x' });
|
|
35
|
+
function buildServerWithResources() {
|
|
36
|
+
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
37
|
+
registerOrbotoResources(server, client);
|
|
38
|
+
return server;
|
|
39
|
+
}
|
|
40
|
+
/** Pull a registered resource-template handler off the McpServer. */
|
|
41
|
+
function getTemplateHandler(server, name) {
|
|
42
|
+
const templates = server._registeredResourceTemplates;
|
|
43
|
+
const entry = templates[name];
|
|
44
|
+
if (!entry)
|
|
45
|
+
throw new Error(`No registered resource template "${name}" - known: ${Object.keys(templates).join(', ')}`);
|
|
46
|
+
return entry.readCallback;
|
|
47
|
+
}
|
|
48
|
+
describe('orboto:// ticket resource', () => {
|
|
49
|
+
it('renders a ticket as Markdown with description + comments', async () => {
|
|
50
|
+
stub([
|
|
51
|
+
// resolveTicketByKey: project lookup
|
|
52
|
+
{ json: { id: 'p1', key: 'ACME', name: 'Acme', description: null, status: 'active' } },
|
|
53
|
+
// resolveTicketByKey: ticket
|
|
54
|
+
{
|
|
55
|
+
json: {
|
|
56
|
+
id: 't1', projectId: 'p1', ticketKey: 'ACME-7', title: 'Login bug',
|
|
57
|
+
description: 'Login fails on Safari.', type: 'bug', priority: 'high',
|
|
58
|
+
status: 'IN_PROGRESS', statusName: 'In Progress', dueDate: null,
|
|
59
|
+
isPrivate: false, estimatedTimeMinutes: 0,
|
|
60
|
+
assignees: [{ id: 'u1', email: 'ada@acme', fullName: 'Ada' }],
|
|
61
|
+
labels: [{ id: 'l1', name: 'bug' }],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
// comments page
|
|
65
|
+
{
|
|
66
|
+
json: {
|
|
67
|
+
items: [
|
|
68
|
+
{ content: 'looking into this', userName: 'Ada', createdAt: 'now', isInternal: false },
|
|
69
|
+
],
|
|
70
|
+
nextCursor: null,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
const server = buildServerWithResources();
|
|
75
|
+
const handler = getTemplateHandler(server, 'ticket');
|
|
76
|
+
const out = await handler(new URL('orboto://ticket/ACME-7'), { ticketKey: 'ACME-7' });
|
|
77
|
+
const text = out.contents[0]?.text;
|
|
78
|
+
expect(text).toContain('# [ACME-7] Login bug');
|
|
79
|
+
expect(text).toContain('Status: In Progress');
|
|
80
|
+
expect(text).toContain('Priority: high');
|
|
81
|
+
expect(text).toContain('Assignees: Ada');
|
|
82
|
+
expect(text).toContain('## Description');
|
|
83
|
+
expect(text).toContain('Login fails on Safari.');
|
|
84
|
+
expect(text).toContain('## Comments (1)');
|
|
85
|
+
expect(text).toContain('looking into this');
|
|
86
|
+
});
|
|
87
|
+
it('handles a ticket with no comments (404 from comments endpoint) gracefully', async () => {
|
|
88
|
+
stub([
|
|
89
|
+
{ json: { id: 'p1', key: 'ACME', name: 'Acme', description: null, status: 'active' } },
|
|
90
|
+
{
|
|
91
|
+
json: {
|
|
92
|
+
id: 't1', projectId: 'p1', ticketKey: 'ACME-8', title: 'Plain', type: 'task',
|
|
93
|
+
priority: 'normal', status: 'TODO', statusName: 'To Do', dueDate: null,
|
|
94
|
+
isPrivate: false, estimatedTimeMinutes: 0, description: null,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{ ok: false, status: 404, json: { error: 'not found' } },
|
|
98
|
+
]);
|
|
99
|
+
const server = buildServerWithResources();
|
|
100
|
+
const handler = getTemplateHandler(server, 'ticket');
|
|
101
|
+
const out = await handler(new URL('orboto://ticket/ACME-8'), { ticketKey: 'ACME-8' });
|
|
102
|
+
const text = out.contents[0]?.text;
|
|
103
|
+
expect(text).toContain('# [ACME-8] Plain');
|
|
104
|
+
expect(text).not.toContain('## Comments');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('orboto:// doc resource', () => {
|
|
108
|
+
it('renders the doc title + body as Markdown', async () => {
|
|
109
|
+
stub([{
|
|
110
|
+
json: {
|
|
111
|
+
id: 'd1', title: 'Runbook', content: '## Restart\n\n1. ssh in\n2. systemctl restart',
|
|
112
|
+
visibility: 'workspace', updatedAt: '2026-04-25T07:00:00Z',
|
|
113
|
+
},
|
|
114
|
+
}]);
|
|
115
|
+
const server = buildServerWithResources();
|
|
116
|
+
const handler = getTemplateHandler(server, 'doc');
|
|
117
|
+
const out = await handler(new URL('orboto://doc/d1'), { docId: 'd1' });
|
|
118
|
+
const text = out.contents[0]?.text;
|
|
119
|
+
expect(text).toContain('# Runbook');
|
|
120
|
+
expect(text).toContain('Visibility: workspace');
|
|
121
|
+
expect(text).toContain('## Restart');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('orboto:// project resource', () => {
|
|
125
|
+
it('aggregates project + milestones + members into Markdown', async () => {
|
|
126
|
+
stub([
|
|
127
|
+
// resolveProjectByKey
|
|
128
|
+
{ json: { id: 'p1', key: 'ACME', name: 'Acme Inc', description: 'CRM build', status: 'active' } },
|
|
129
|
+
// milestones
|
|
130
|
+
{ json: [{ id: 'm1', name: 'v1', status: 'active', startDate: '2026-04-01', endDate: '2026-05-01' }] },
|
|
131
|
+
// members
|
|
132
|
+
{ json: [{ user: { email: 'ada@acme', fullName: 'Ada' }, role: { name: 'developer' } }] },
|
|
133
|
+
]);
|
|
134
|
+
const server = buildServerWithResources();
|
|
135
|
+
const handler = getTemplateHandler(server, 'project');
|
|
136
|
+
const out = await handler(new URL('orboto://project/ACME'), { projectKey: 'ACME' });
|
|
137
|
+
const text = out.contents[0]?.text;
|
|
138
|
+
expect(text).toContain('# ACME - Acme Inc');
|
|
139
|
+
expect(text).toContain('## Milestones');
|
|
140
|
+
expect(text).toContain('**v1** [active]');
|
|
141
|
+
expect(text).toContain('## Members');
|
|
142
|
+
expect(text).toContain('Ada <ada@acme> - developer');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('orboto:// search resource', () => {
|
|
146
|
+
it('renders top hits when results are present', async () => {
|
|
147
|
+
stub([{
|
|
148
|
+
json: {
|
|
149
|
+
items: [
|
|
150
|
+
{ type: 'ticket', ticketKey: 'ACME-1', title: 'Login bug', excerpt: '…fails on Safari…', url: '/x', projectName: 'Acme' },
|
|
151
|
+
],
|
|
152
|
+
total: 1,
|
|
153
|
+
},
|
|
154
|
+
}]);
|
|
155
|
+
const server = buildServerWithResources();
|
|
156
|
+
const handler = getTemplateHandler(server, 'search');
|
|
157
|
+
const out = await handler(new URL('orboto://search/login'), { query: 'login' });
|
|
158
|
+
const text = out.contents[0]?.text;
|
|
159
|
+
expect(text).toContain('# Search: login');
|
|
160
|
+
expect(text).toContain('TICKET ACME-1');
|
|
161
|
+
expect(text).toContain('Login bug');
|
|
162
|
+
});
|
|
163
|
+
it('renders an explicit empty-state when no hits', async () => {
|
|
164
|
+
stub([{ json: { items: [], total: 0 } }]);
|
|
165
|
+
const server = buildServerWithResources();
|
|
166
|
+
const handler = getTemplateHandler(server, 'search');
|
|
167
|
+
const out = await handler(new URL('orboto://search/zzz'), { query: 'zzz' });
|
|
168
|
+
const text = out.contents[0]?.text;
|
|
169
|
+
expect(text).toContain('# Search: zzz');
|
|
170
|
+
expect(text).toContain('_No hits._');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('orboto:// rules resource (ORB-1177)', () => {
|
|
174
|
+
it('returns the complete assembled workspace rules', async () => {
|
|
175
|
+
stub([{ json: { instructions: 'RULE 1: ticket-first.\nRULE 2: one commit per ticket.' } }]);
|
|
176
|
+
const server = buildServerWithResources();
|
|
177
|
+
const handler = getTemplateHandler(server, 'rules');
|
|
178
|
+
const out = await handler(new URL('orboto://rules'), {});
|
|
179
|
+
const text = out.contents[0]?.text;
|
|
180
|
+
expect(text).toContain('# orboto workspace agent rules');
|
|
181
|
+
expect(text).toContain('RULE 1: ticket-first.');
|
|
182
|
+
expect(text).toContain('RULE 2: one commit per ticket.');
|
|
183
|
+
});
|
|
184
|
+
it('degrades to a placeholder when no rules are configured', async () => {
|
|
185
|
+
stub([{ json: { instructions: '' } }]);
|
|
186
|
+
const server = buildServerWithResources();
|
|
187
|
+
const handler = getTemplateHandler(server, 'rules');
|
|
188
|
+
const out = await handler(new URL('orboto://rules'), {});
|
|
189
|
+
expect(out.contents[0]?.text).toContain('(no workspace rules configured)');
|
|
190
|
+
});
|
|
191
|
+
});
|