@orboto/mcp 0.192.0 → 0.194.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/dist/event-bridge.js +1 -47
- package/dist/event-bridge.test.js +0 -19
- package/dist/http-transport-binding.test.js +1 -4
- package/dist/http-transport.js +1 -213
- package/dist/http-transport.test.js +0 -56
- package/dist/index.js +1 -48
- package/dist/input-schema.js +2 -76
- package/dist/input-schema.test.js +1 -15
- package/dist/instructions-budget.test.js +0 -2
- package/dist/manifest-size.test.js +3 -72
- package/dist/oauth-bootstrap.js +0 -47
- package/dist/oauth-bootstrap.test.js +4 -16
- package/dist/orboto-client.js +0 -30
- package/dist/orboto-client.test.js +0 -12
- package/dist/prompts.js +0 -39
- package/dist/protect-text-usage.test.js +1 -9
- package/dist/required-rules-wrapper.test.js +0 -5
- package/dist/required-rules.js +0 -2
- package/dist/resources.js +0 -62
- package/dist/resources.test.js +0 -16
- package/dist/response-budget.js +2 -150
- package/dist/response-budget.test.js +1 -32
- package/dist/server.js +4 -180
- package/dist/session-nudge.js +0 -9
- package/dist/session-nudge.test.js +2 -10
- package/dist/session-nudge.transport.test.js +0 -23
- package/dist/tool-annotations.test.js +0 -44
- package/dist/tool-docs.js +1 -12
- package/dist/tool-docs.test.js +2 -8
- package/dist/tool-parameters.test.js +0 -19
- package/dist/tools/absence-writes.js +1 -17
- package/dist/tools/admin-translations.js +0 -11
- package/dist/tools/admin-writes.js +0 -28
- package/dist/tools/admin-writes.test.js +0 -1
- package/dist/tools/agent-coordination.js +2 -24
- package/dist/tools/agent-coordination.test.js +0 -2
- package/dist/tools/agent-instructions.js +0 -10
- package/dist/tools/agent-instructions.test.js +0 -2
- package/dist/tools/agent-messages.js +0 -1
- package/dist/tools/ai-status.js +0 -14
- package/dist/tools/ai-usage.js +0 -10
- package/dist/tools/analytics.js +0 -2
- package/dist/tools/api-call.js +0 -12
- package/dist/tools/api-call.test.js +1 -2
- package/dist/tools/api-search.js +1 -6
- package/dist/tools/approvals.js +0 -6
- package/dist/tools/attach.js +0 -25
- package/dist/tools/backup.js +0 -13
- package/dist/tools/backup.test.js +1 -2
- package/dist/tools/bulk-create.js +1 -25
- package/dist/tools/bulk-create.test.js +0 -4
- package/dist/tools/bulk-writes.js +69 -61
- package/dist/tools/bulk-writes.test.js +34 -4
- package/dist/tools/check-similar.js +0 -17
- package/dist/tools/checklist-writes.js +82 -27
- package/dist/tools/checklist-writes.test.js +69 -1
- package/dist/tools/claim.js +1 -71
- package/dist/tools/claim.test.js +0 -2
- package/dist/tools/critical-path.js +0 -5
- package/dist/tools/critical-path.test.js +0 -3
- package/dist/tools/cross-project-links.ee.js +1 -24
- package/dist/tools/doc-attachments.js +0 -28
- package/dist/tools/doc-attachments.test.js +1 -4
- package/dist/tools/doc-comments.js +1 -37
- package/dist/tools/doc-comments.test.js +1 -2
- package/dist/tools/doc-edits.js +1 -31
- package/dist/tools/doc-edits.test.js +0 -9
- package/dist/tools/doc-export.js +0 -24
- package/dist/tools/doc-export.test.js +0 -12
- package/dist/tools/doc-revisions.js +0 -23
- package/dist/tools/docs-ai.js +0 -34
- package/dist/tools/docs-ai.test.js +0 -11
- package/dist/tools/docs.js +1 -75
- package/dist/tools/docs.test.js +1 -3
- package/dist/tools/embedding-status.js +0 -13
- package/dist/tools/embedding-status.test.js +0 -1
- package/dist/tools/feedback.js +1 -15
- package/dist/tools/feedback.test.js +0 -1
- package/dist/tools/get-checklists.js +6 -14
- package/dist/tools/get-project-primer.js +1 -25
- package/dist/tools/get-project.js +0 -17
- package/dist/tools/get-ticket.js +1 -61
- package/dist/tools/get-timer.js +0 -11
- package/dist/tools/git-health-reasons.js +0 -1
- package/dist/tools/identity.js +0 -13
- package/dist/tools/list-projects.js +0 -20
- package/dist/tools/list-projects.test.js +0 -1
- package/dist/tools/list-tickets.js +10 -9
- package/dist/tools/milestones-crud.test.js +0 -4
- package/dist/tools/milestones.js +0 -43
- package/dist/tools/my-tickets.js +0 -7
- package/dist/tools/phase-b-tools.test.js +14 -21
- package/dist/tools/primer-facts.js +1 -49
- package/dist/tools/primer-facts.test.js +0 -20
- package/dist/tools/project-listings.js +0 -25
- package/dist/tools/query.js +1 -12
- package/dist/tools/query.test.js +0 -8
- package/dist/tools/raci.js +0 -11
- package/dist/tools/requirements-spec.js +1 -12
- package/dist/tools/response-expand.js +0 -19
- package/dist/tools/review-policy.js +0 -24
- package/dist/tools/review-policy.test.js +0 -2
- package/dist/tools/search.js +1 -13
- package/dist/tools/session-start.js +1 -136
- package/dist/tools/session-start.test.js +1 -44
- package/dist/tools/set-parent.js +1 -19
- package/dist/tools/shared.js +0 -41
- package/dist/tools/shared.test.js +0 -4
- package/dist/tools/similar-projection.js +0 -15
- package/dist/tools/ticket-attachments.js +1 -25
- package/dist/tools/ticket-schedules.js +0 -9
- package/dist/tools/ticket-schedules.test.js +0 -1
- package/dist/tools/ticket-writes.js +1 -110
- package/dist/tools/ticket-writes.test.js +1 -15
- package/dist/tools/time-writes.js +26 -46
- package/dist/tools/time-writes.test.js +18 -0
- package/dist/tools/update-project.js +1 -27
- package/dist/tools/update-project.test.js +1 -1
- package/dist/tools/wiki.js +0 -8
- package/dist/tools/work-next.js +0 -5
- package/dist/tools/work-sessions-start.test.js +0 -2
- package/dist/tools/work-sessions.js +0 -48
- package/dist/tools/work-sessions.test.js +0 -5
- package/dist/toolset.js +2 -81
- package/dist/toolset.test.js +0 -20
- package/dist/version-guard.test.js +0 -4
- package/dist/version.js +0 -12
- package/dist/with-metrics-output-schema.test.js +1 -14
- package/dist/with-metrics.js +2 -74
- package/dist/with-metrics.test.js +7 -29
- package/package.json +1 -1
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase B - `orboto_get_project`.
|
|
3
|
-
*
|
|
4
|
-
* Returns metadata + milestones + labels + members for a project.
|
|
5
|
-
* Accepts the human-readable project key (`ACME`), case-insensitive
|
|
6
|
-
* - the API's `GET /projects/by-key/:key` does the lookup, the rest
|
|
7
|
-
* of the hydration (milestones + labels + members) comes from the
|
|
8
|
-
* `/projects/:id/*` endpoints that take the resolved UUID.
|
|
9
|
-
*
|
|
10
|
-
* Members endpoint returns a nested `{user: {...}, role: {...}}`
|
|
11
|
-
* shape; we flatten here into a card-friendly line.
|
|
12
3
|
*/
|
|
13
4
|
import { z } from 'zod';
|
|
14
5
|
import { resolveProjectByKey } from './shared.js';
|
|
@@ -23,9 +14,6 @@ export const getProjectToolConfig = {
|
|
|
23
14
|
export function makeGetProjectHandler(client) {
|
|
24
15
|
return async ({ projectKey }) => {
|
|
25
16
|
const project = await resolveProjectByKey(client, projectKey);
|
|
26
|
-
// Parallel fan-out - the API doesn't have a single aggregate route
|
|
27
|
-
// for milestones+labels+members, and three parallel fetches beat a
|
|
28
|
-
// chain on the default Node HTTP pool.
|
|
29
17
|
const [milestones, labels, members] = await Promise.all([
|
|
30
18
|
client.get(`/projects/${project.id}/milestones`),
|
|
31
19
|
client.get(`/projects/${project.id}/labels`),
|
|
@@ -39,7 +27,6 @@ export function makeGetProjectHandler(client) {
|
|
|
39
27
|
...milestones.map((m) => ` - ${m.milestoneKey ? `${m.milestoneKey} · ` : ''}${m.name} [${m.status}]${m.endDate ? ` due ${m.endDate}` : ''}`),
|
|
40
28
|
'',
|
|
41
29
|
`Labels: ${labels.map((l) => l.name).join(', ') || '(none)'}`,
|
|
42
|
-
// ORB-1040 - only mention RACI when the project opted in.
|
|
43
30
|
project.raciEnabled ? 'RACI: enabled' : null,
|
|
44
31
|
'',
|
|
45
32
|
`Members (${members.length}):`,
|
|
@@ -52,15 +39,11 @@ export function makeGetProjectHandler(client) {
|
|
|
52
39
|
content: [{ type: 'text', text: lines.join('\n') }],
|
|
53
40
|
structuredContent: {
|
|
54
41
|
project: {
|
|
55
|
-
// ORB-1042 - surface the UUID for the rare APIs that still need it
|
|
56
|
-
// (most agent surfaces accept the key).
|
|
57
42
|
id: project.id,
|
|
58
43
|
key: project.key,
|
|
59
44
|
name: project.name,
|
|
60
45
|
status: project.status,
|
|
61
46
|
description: project.description,
|
|
62
|
-
// ORB-1040 - RACI is opt-in; agents must not raise/set RACI here
|
|
63
|
-
// unless this is true.
|
|
64
47
|
raciEnabled: project.raciEnabled ?? false,
|
|
65
48
|
},
|
|
66
49
|
milestones,
|
package/dist/tools/get-ticket.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase B - `orboto_get_ticket`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* labels, checklists, git activity - in a shape the model can
|
|
6
|
-
* reason about without follow-up calls. Comments and checklists are
|
|
7
|
-
* fetched in parallel with the ticket payload; git activity is
|
|
8
|
-
* skipped when the ticket's `gitActivityCount` is 0 so we don't waste
|
|
9
|
-
* a round-trip on the common case.
|
|
10
|
-
*
|
|
11
|
-
* ORB-272: `/tickets/:id/comments` is cursor-paginated. We pull the
|
|
12
|
-
* first page (50 by default) - if the ticket has more, a footer line
|
|
13
|
-
* nudges the user to open it in the UI. AI agents asking "give me
|
|
14
|
-
* the full history" beyond 50 is a rare enough case not to fan out.
|
|
4
|
+
* @see ORB-272
|
|
15
5
|
*/
|
|
16
6
|
import { z } from 'zod';
|
|
17
7
|
import { OrbotoApiError } from '../orboto-client.js';
|
|
@@ -33,29 +23,12 @@ export function makeGetTicketHandler(client) {
|
|
|
33
23
|
const ticket = await resolveTicketByKey(client, ticketKey);
|
|
34
24
|
const inc = new Set(include ?? []);
|
|
35
25
|
const parentId = ticket.parentTicketId ?? null;
|
|
36
|
-
// ORB-1698 - the default response is the decision CARD: identity +
|
|
37
|
-
// description + progress + counts. The heavy blocks (comment bodies,
|
|
38
|
-
// git rows, checklist items) are fetched only when asked for via
|
|
39
|
-
// `include` - measured, they were the bulk of a 1.02 Mtok carry cost.
|
|
40
|
-
// Children + attachments have no count on the enriched row, so their
|
|
41
|
-
// (cheap, metadata-only) fetches stay - the RESPONSE carries only the
|
|
42
|
-
// count unless included.
|
|
43
26
|
const [enriched, parent, childrenPage, attachments, commentsPage, checklists, gitActivity] = await Promise.all([
|
|
44
|
-
// ORB-1023 - `resolveTicketByKey` hits the by-key endpoint, which
|
|
45
|
-
// returns a BARE ticket row (no statusCategory, assignees, labels,
|
|
46
|
-
// milestoneName, counts). Re-fetch the enriched by-id shape; falls
|
|
47
|
-
// back to the bare row on a 404.
|
|
48
27
|
client.get(`/projects/${ticket.projectId}/tickets/${ticket.id}`).catch(swallow404(null)),
|
|
49
|
-
// Parent ticket - only fetched when set. Lets the model say
|
|
50
|
-
// "this is sub-ticket of [ACME-10]" without a second tool call.
|
|
51
28
|
parentId
|
|
52
29
|
? client.get(`/projects/${ticket.projectId}/tickets/${parentId}`).catch(swallow404(null))
|
|
53
30
|
: Promise.resolve(null),
|
|
54
|
-
// Children via the parentTicketId filter (API-side, O(children)).
|
|
55
|
-
// Cap at 50; anything bigger should use `orboto_list_tickets
|
|
56
|
-
// --parentTicketKey` and paginate explicitly.
|
|
57
31
|
client.get(`/projects/${ticket.projectId}/tickets?parentTicketId=${ticket.id}&limit=50`).catch(swallow404({ items: [], nextCursor: null })),
|
|
58
|
-
// ORB-1455 - attachments metadata so the agent knows files exist.
|
|
59
32
|
client.get(`/tickets/${ticket.id}/attachments`).catch(swallow404([])),
|
|
60
33
|
inc.has('comments')
|
|
61
34
|
? client.get(`/tickets/${ticket.id}/comments?limit=${COMMENT_PAGE_SIZE}`).catch(swallow404({ items: [], nextCursor: null }))
|
|
@@ -70,8 +43,6 @@ export function makeGetTicketHandler(client) {
|
|
|
70
43
|
const comments = commentsPage.items;
|
|
71
44
|
const hasMoreComments = !!commentsPage.nextCursor;
|
|
72
45
|
const children = childrenPage.items;
|
|
73
|
-
// ORB-1023 - prefer the enriched by-id row (statusCategory, assignees,
|
|
74
|
-
// labels, milestoneName); fall back to the bare resolver row.
|
|
75
46
|
const full = enriched ?? ticket;
|
|
76
47
|
const commentCount = full.commentCount ?? (inc.has('comments') ? comments.length : 0);
|
|
77
48
|
const gitCount = full.gitActivityCount ?? (inc.has('git') ? gitActivity.length : 0);
|
|
@@ -92,7 +63,6 @@ export function makeGetTicketHandler(client) {
|
|
|
92
63
|
return {
|
|
93
64
|
content: [{ type: 'text', text: formatTicket(full, inc, comments, hasMoreComments, checklists, gitActivity, parent, children, attachments, includeHint) }],
|
|
94
65
|
structuredContent: {
|
|
95
|
-
// ORB-1179 - surface the uuid alongside the key.
|
|
96
66
|
id: full.id,
|
|
97
67
|
key: full.ticketKey,
|
|
98
68
|
title: full.title,
|
|
@@ -103,22 +73,14 @@ export function makeGetTicketHandler(client) {
|
|
|
103
73
|
: null,
|
|
104
74
|
priority: full.priority,
|
|
105
75
|
type: full.type,
|
|
106
|
-
// ORB-1608 - role-aware commit policy. The API defaults unset
|
|
107
|
-
// rows to 'implementation'.
|
|
108
76
|
deliveryMode: full.deliveryMode ?? 'implementation',
|
|
109
77
|
dueDate: full.dueDate,
|
|
110
78
|
startDate: full.startDate,
|
|
111
79
|
isPrivate: full.isPrivate,
|
|
112
80
|
estimatedTimeMinutes: full.estimatedTimeMinutes,
|
|
113
81
|
loggedMinutes: full.loggedMinutes ?? 0,
|
|
114
|
-
// ORB-1605 - true when in_review, zero ingested git_activities,
|
|
115
|
-
// but the project HAS an active git connection: closing
|
|
116
|
-
// verification may be blocked on stalled ingestion.
|
|
117
82
|
waitingForGitIngestion: full.waitingForGitIngestion ?? false,
|
|
118
83
|
description: full.description ?? null,
|
|
119
|
-
// Hierarchy - null when no parent, array of summary rows for
|
|
120
|
-
// children (empty array when none). Sub-ticket consumers can
|
|
121
|
-
// decide to call orboto_get_ticket on each for the full detail.
|
|
122
84
|
parentTicket: parent ? {
|
|
123
85
|
key: parent.ticketKey,
|
|
124
86
|
title: parent.title,
|
|
@@ -135,14 +97,10 @@ export function makeGetTicketHandler(client) {
|
|
|
135
97
|
})),
|
|
136
98
|
} : {}),
|
|
137
99
|
assignees: full.assignees ?? [],
|
|
138
|
-
// ORB-1034 - RACI roster (R/A/C/I); opt-in via include: ["raci"]
|
|
139
|
-
// (assignees above stay the always-on Responsible+Accountable set).
|
|
140
100
|
...(inc.has('raci') ? {
|
|
141
101
|
raci: (full.raci ?? []).map((r) => ({ userId: r.userId, fullName: r.fullName, role: r.role })),
|
|
142
102
|
} : {}),
|
|
143
103
|
labels: (full.labels ?? []).map((l) => l.name),
|
|
144
|
-
// ORB-1698 - counts always; bodies opt-in. checklistProgress is the
|
|
145
|
-
// aggregate from the enriched row (items via include).
|
|
146
104
|
commentCount,
|
|
147
105
|
gitActivityCount: gitCount,
|
|
148
106
|
attachmentCount: attachments.length,
|
|
@@ -167,10 +125,6 @@ export function makeGetTicketHandler(client) {
|
|
|
167
125
|
items: cl.items.map((i) => ({
|
|
168
126
|
content: i.content,
|
|
169
127
|
done: i.effectiveCompleted,
|
|
170
|
-
// When the item links to another ticket, `effectiveCompleted`
|
|
171
|
-
// mirrors that ticket's status-category instead of this item's
|
|
172
|
-
// own checkbox. Surface the link so the model can explain
|
|
173
|
-
// why the item is/isn't done.
|
|
174
128
|
linkedTicket: i.linkedTicketKey ? {
|
|
175
129
|
key: i.linkedTicketKey,
|
|
176
130
|
title: i.linkedTicketTitle,
|
|
@@ -189,8 +143,6 @@ export function makeGetTicketHandler(client) {
|
|
|
189
143
|
createdAt: g.createdAt,
|
|
190
144
|
})),
|
|
191
145
|
} : {}),
|
|
192
|
-
// ORB-1455 - attachments (id + metadata). Feed an id to
|
|
193
|
-
// orboto_get_attachment to view an image or fetch the bytes.
|
|
194
146
|
...(inc.has('attachments') ? {
|
|
195
147
|
attachments: attachments.map((a) => ({
|
|
196
148
|
id: a.id,
|
|
@@ -215,18 +167,12 @@ function formatTicket(ticket, inc, comments, hasMoreComments, checklists, gitAct
|
|
|
215
167
|
const header = [
|
|
216
168
|
`[${ticket.ticketKey}] ${ticket.title}`,
|
|
217
169
|
`Status: ${ticket.statusName ?? ticket.status} Priority: ${ticket.priority} Type: ${ticket.type}`,
|
|
218
|
-
// ORB-1608 - only shown when it deviates from the 'implementation'
|
|
219
|
-
// default, so a fresh ticket's card stays uncluttered.
|
|
220
170
|
ticket.deliveryMode && ticket.deliveryMode !== 'implementation'
|
|
221
171
|
? `Delivery mode: ${ticket.deliveryMode}`
|
|
222
172
|
: null,
|
|
223
|
-
// ORB-1605 - surface the stalled-ingestion signal right in the
|
|
224
|
-
// header so an agent checking "is this really done?" sees it
|
|
225
|
-
// before reading the (currently empty) git-activity section.
|
|
226
173
|
ticket.waitingForGitIngestion
|
|
227
174
|
? '⏳ Waiting for Git ingestion - this project has an active git connection but no commits/PRs have landed for this ticket yet. Closing verification may be blocked on stalled ingestion, not on unfinished work - check manually before assuming it is unlinked.'
|
|
228
175
|
: null,
|
|
229
|
-
// ORB-1023 - show the milestone name (not the UUID) when set.
|
|
230
176
|
ticket.milestoneId ? `Milestone: ${ticket.milestoneName ?? '(unnamed)'}` : null,
|
|
231
177
|
ticket.dueDate ? `Due: ${ticket.dueDate}` : null,
|
|
232
178
|
parent ? `Parent: [${parent.ticketKey}] ${parent.title} (${parent.statusName ?? parent.status})` : null,
|
|
@@ -236,8 +182,6 @@ function formatTicket(ticket, inc, comments, hasMoreComments, checklists, gitAct
|
|
|
236
182
|
ticket.assignees && ticket.assignees.length > 0
|
|
237
183
|
? `Assignees: ${ticket.assignees.map((a) => a.fullName || a.email).join(', ')}`
|
|
238
184
|
: 'Assignees: (unassigned)',
|
|
239
|
-
// ORB-1034 - RACI summary, shown only when the project uses RACI and
|
|
240
|
-
// someone holds a non-Responsible role (Accountable / Consulted / Informed).
|
|
241
185
|
ticket.raci && ticket.raci.some((r) => r.role !== 'R')
|
|
242
186
|
? `RACI: ${['A', 'R', 'C', 'I']
|
|
243
187
|
.map((role) => {
|
|
@@ -270,8 +214,6 @@ function formatTicket(ticket, inc, comments, hasMoreComments, checklists, gitAct
|
|
|
270
214
|
const progressLabel = `${cl.progress.done}/${cl.progress.total}`;
|
|
271
215
|
checklistLines.push(`### ${cl.title} (${progressLabel})${cl.triggersDone ? ' · triggers done' : ''}`);
|
|
272
216
|
for (const i of cl.items) {
|
|
273
|
-
// Linked-ticket suffix on items that track another ticket -
|
|
274
|
-
// lets the model say "item X is done because [ACME-42] shipped".
|
|
275
217
|
const link = i.linkedTicketKey
|
|
276
218
|
? ` ↪ [${i.linkedTicketKey}] ${i.linkedTicketTitle ?? ''} (${i.linkedTicketStatusCategory ?? 'unknown'})`
|
|
277
219
|
: '';
|
|
@@ -302,8 +244,6 @@ function formatTicket(ticket, inc, comments, hasMoreComments, checklists, gitAct
|
|
|
302
244
|
gitLines.push(`- ${g.type} ${g.state ? `[${g.state}]` : ''} ${g.title}${g.url ? ` - ${g.url}` : ''}`);
|
|
303
245
|
}
|
|
304
246
|
}
|
|
305
|
-
// ORB-1455 - attachments so the agent knows files exist. Feed an id to
|
|
306
|
-
// orboto_get_attachment to view an image or fetch the bytes.
|
|
307
247
|
const attachmentLines = [];
|
|
308
248
|
if (!inc.has('attachments') && attachments.length > 0) {
|
|
309
249
|
attachmentLines.push('', `Attachments: ${attachments.length}`);
|
package/dist/tools/get-timer.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase B - `orboto_get_timer`.
|
|
3
|
-
*
|
|
4
|
-
* Returns the caller's active stopwatch (or `null` when none is
|
|
5
|
-
* running). Useful for the model to answer "am I still tracking
|
|
6
|
-
* time on that bug from earlier?" without guessing.
|
|
7
|
-
*
|
|
8
|
-
* API shape: `GET /time/timer` returns `ActiveTimer | null` directly
|
|
9
|
-
* (not wrapped). ActiveTimer carries `ticketTitle` and `projectId`
|
|
10
|
-
* as joined fields but no `ticketKey` - so the ticket identifier is
|
|
11
|
-
* resolved the cheap way: first 8 chars of the UUID when no title
|
|
12
|
-
* is available, just the title otherwise.
|
|
13
3
|
*/
|
|
14
4
|
import { z } from 'zod';
|
|
15
5
|
export const getTimerToolConfig = {
|
|
@@ -20,7 +10,6 @@ export const getTimerToolConfig = {
|
|
|
20
10
|
};
|
|
21
11
|
export function makeGetTimerHandler(client) {
|
|
22
12
|
return async () => {
|
|
23
|
-
// `/time/timer` returns the timer row directly, or `null`.
|
|
24
13
|
const timer = await client.get('/time/timer');
|
|
25
14
|
if (!timer) {
|
|
26
15
|
return {
|
package/dist/tools/identity.js
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-799 - identity / debug tools.
|
|
3
|
-
*
|
|
4
|
-
* `orboto_whoami` - return the authenticated principal as `{id, email,
|
|
5
|
-
* fullName, isBot}`. Mirrors `orboto.mjs whoami`. The wrapper hits the
|
|
6
|
-
* existing `/users/me` route directly; we do the same so a stale-token
|
|
7
|
-
* 401 surfaces the same way it does for every other tool.
|
|
8
|
-
*
|
|
9
|
-
* Useful for debugging "which API key is this session actually using?",
|
|
10
|
-
* which is the most common confusion when an agent has multiple MCP
|
|
11
|
-
* configs (`claude-desktop`, `cursor`, `gemini-cli`) wired to different
|
|
12
|
-
* `orb_*` keys.
|
|
13
3
|
*/
|
|
14
4
|
import { z } from 'zod';
|
|
15
5
|
export const whoamiToolConfig = {
|
|
@@ -22,7 +12,6 @@ export const whoamiToolConfig = {
|
|
|
22
12
|
fullName: z.string().nullable(),
|
|
23
13
|
isBot: z.boolean(),
|
|
24
14
|
workspaceLocale: z.string().nullable(),
|
|
25
|
-
// ORB-1671 - both identities of a delegated act-as connection.
|
|
26
15
|
actingAuthorizedBy: z.object({ id: z.string(), email: z.string(), fullName: z.string() }).nullable(),
|
|
27
16
|
}).shape,
|
|
28
17
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
@@ -40,8 +29,6 @@ export function makeWhoamiHandler(client) {
|
|
|
40
29
|
if (workspaceLocale) {
|
|
41
30
|
lines.push(` workspace language: ${workspaceLocale} (write tickets in this language)`);
|
|
42
31
|
}
|
|
43
|
-
// ORB-1671 - an act-as connection reports BOTH identities so the agent
|
|
44
|
-
// (and a human debugging it) can see who it acts as and who authorised it.
|
|
45
32
|
if (me.actingAuthorizedBy) {
|
|
46
33
|
lines.push(` acting as this bot, authorised by: ${me.actingAuthorizedBy.fullName} <${me.actingAuthorizedBy.email}>`);
|
|
47
34
|
}
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase A reference tool - `orboto_list_projects`.
|
|
3
|
-
*
|
|
4
|
-
* Maps to `GET /projects` in the orboto API, which already filters by
|
|
5
|
-
* the caller's visibility via the PBAC cascade. The MCP server is a
|
|
6
|
-
* transport adapter - it doesn't re-implement the ACL.
|
|
7
|
-
*
|
|
8
|
-
* Rest of the read-tool suite follows the same shape (see
|
|
9
|
-
* ticket ORB-244 Phase B). Keeping this first tool narrow on
|
|
10
|
-
* purpose - it's the one we wire + verify end-to-end before
|
|
11
|
-
* scaling out.
|
|
12
3
|
*/
|
|
13
4
|
import { z } from 'zod';
|
|
14
5
|
export const listProjectsToolConfig = {
|
|
@@ -17,19 +8,12 @@ export const listProjectsToolConfig = {
|
|
|
17
8
|
+ 'If you are after one project, pass `query` to filter by key/name instead of pulling the whole list - '
|
|
18
9
|
+ 'and note you can usually use a project key directly with other tools without listing at all. '
|
|
19
10
|
+ 'When the result says it is partial, refine with `query` rather than re-calling.',
|
|
20
|
-
// ORB-1109 - query + limit so a small-context agent (self-hosted /
|
|
21
|
-
// local 32k models over MCP) can narrow instead of ingesting every
|
|
22
|
-
// project.
|
|
23
11
|
inputSchema: z.object({
|
|
24
12
|
query: z.string().optional().describe('Substring matched against key or name.'),
|
|
25
13
|
limit: z.number().int().min(1).max(200).optional().describe('Max projects. Default 50.'),
|
|
26
14
|
}).shape,
|
|
27
|
-
// Well-formed output schema so MCP clients that honour it can
|
|
28
|
-
// surface structured data. Adding fields is safe; removing them is a
|
|
29
|
-
// breaking change for pinned client configs.
|
|
30
15
|
outputSchema: z.object({
|
|
31
16
|
projects: z.array(z.object({
|
|
32
|
-
// ORB-1179 - uuid alongside the key for downstream write tools.
|
|
33
17
|
id: z.string(),
|
|
34
18
|
key: z.string(),
|
|
35
19
|
name: z.string(),
|
|
@@ -52,10 +36,6 @@ export function makeListProjectsHandler(client) {
|
|
|
52
36
|
const cap = Math.min(limit ?? 50, 200);
|
|
53
37
|
const shown = matched.slice(0, cap);
|
|
54
38
|
const rows = shown.map((p) => ({ id: p.id, key: p.key, name: p.name, status: p.status, description: p.description }));
|
|
55
|
-
// Text content is what the model reads; structured content is what
|
|
56
|
-
// the client UI renders. The text stays description-free (compact)
|
|
57
|
-
// and ends with a count line so a small model knows whether it has
|
|
58
|
-
// the whole set.
|
|
59
39
|
const lines = rows.map((r) => `- ${r.key} - ${r.name} (${r.status})`);
|
|
60
40
|
const partial = shown.length < matched.length;
|
|
61
41
|
const footer = matched.length === 0
|
|
@@ -32,7 +32,6 @@ describe('tools/list-projects', () => {
|
|
|
32
32
|
});
|
|
33
33
|
expect(result.structuredContent).toEqual({
|
|
34
34
|
projects: [
|
|
35
|
-
// ORB-1179 - uuid surfaced alongside the key
|
|
36
35
|
{ id: 'p1', key: 'ACME', name: 'Acme', status: 'active', description: 'Customer portal' },
|
|
37
36
|
{ id: 'p2', key: 'TOOL', name: 'Internal Tools', status: 'draft', description: null },
|
|
38
37
|
],
|
|
@@ -12,7 +12,7 @@ import { resolveProjectByKey, resolveTicketByKey, ticketLine, agentTicketListRow
|
|
|
12
12
|
import { resolveMilestoneByNameOrId } from './milestones.js';
|
|
13
13
|
export const listTicketsToolConfig = {
|
|
14
14
|
title: 'List tickets',
|
|
15
|
-
description: 'List tickets in a project, optionally filtered by status category, milestone name, or assignee email. Returns up to 50 tickets per call.',
|
|
15
|
+
description: 'List tickets in a project, optionally filtered by status category, milestone name, or assignee email. Set unscheduled=true for the product backlog (everything not planned into a milestone yet). Returns up to 50 tickets per call.',
|
|
16
16
|
inputSchema: z.object({
|
|
17
17
|
projectKey: z.string().min(1).describe('Project key (e.g. "ACME").'),
|
|
18
18
|
statusCategory: z
|
|
@@ -23,6 +23,10 @@ export const listTicketsToolConfig = {
|
|
|
23
23
|
.string()
|
|
24
24
|
.optional()
|
|
25
25
|
.describe('Key (ORB-M3), name, or UUID. Omit for all, backlog included.'),
|
|
26
|
+
unscheduled: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('true = only the backlog: tickets with no milestone. Cannot be combined with `milestone`.'),
|
|
26
30
|
assigneeEmail: z
|
|
27
31
|
.string()
|
|
28
32
|
.optional()
|
|
@@ -43,18 +47,16 @@ export function makeListTicketsHandler(client) {
|
|
|
43
47
|
qs.set('limit', String(input.limit ?? 25));
|
|
44
48
|
if (input.statusCategory)
|
|
45
49
|
qs.set('statusCategory', input.statusCategory);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
if (input.unscheduled && input.milestone) {
|
|
51
|
+
throw new Error('Pass either `milestone` or `unscheduled: true`, not both.');
|
|
52
|
+
}
|
|
53
|
+
if (input.unscheduled)
|
|
54
|
+
qs.set('unscheduled', 'true');
|
|
49
55
|
if (input.milestone) {
|
|
50
|
-
// ORB-1696 - shared resolver: key (ORB-M3), name or UUID, ambiguous
|
|
51
|
-
// name -> explicit error. Matches create_ticket/set_milestone/OQL.
|
|
52
56
|
const m = await resolveMilestoneByNameOrId(client, project.id, input.milestone);
|
|
53
57
|
qs.set('milestoneId', m.id);
|
|
54
58
|
}
|
|
55
59
|
if (input.assigneeEmail) {
|
|
56
|
-
// Members endpoint returns `{userId, user: {email, ...}, role: {...}}`;
|
|
57
|
-
// we need to peek inside `user` to match by email.
|
|
58
60
|
const members = await client.get(`/projects/${project.id}/members`);
|
|
59
61
|
const member = members.find((x) => x.user.email.toLowerCase() === input.assigneeEmail.toLowerCase());
|
|
60
62
|
if (!member)
|
|
@@ -78,7 +80,6 @@ export function makeListTicketsHandler(client) {
|
|
|
78
80
|
project: { key: project.key },
|
|
79
81
|
count: page.items.length,
|
|
80
82
|
hasMore: !!page.nextCursor,
|
|
81
|
-
// ORB-1699 - shared lean row; verbose restores uuid/labels/minutes.
|
|
82
83
|
tickets: page.items.map((t) => agentTicketListRow(t, input.verbose ?? false)),
|
|
83
84
|
},
|
|
84
85
|
};
|
|
@@ -46,9 +46,6 @@ describe('orboto_create_milestone', () => {
|
|
|
46
46
|
const res = await makeCreateMilestoneHandler(client)({
|
|
47
47
|
projectKey: 'ACME', name: 'v2.0',
|
|
48
48
|
});
|
|
49
|
-
// ORB-1825 - startDate/endDate are nullable().optional() on the API's
|
|
50
|
-
// create body now; an absent key means the same thing as null, so the
|
|
51
|
-
// tool no longer forces `?? null` and the wire body simply omits them.
|
|
52
49
|
expect(calls[1]).toMatchObject({
|
|
53
50
|
method: 'POST',
|
|
54
51
|
url: 'https://orboto.example.com/projects/p1/milestones',
|
|
@@ -118,7 +115,6 @@ describe('orboto_close_milestone', () => {
|
|
|
118
115
|
await makeCloseMilestoneHandler(client)({
|
|
119
116
|
projectKey: 'ACME', milestone: '11111111-2222-3333-4444-555555555555',
|
|
120
117
|
});
|
|
121
|
-
// PATCH URL should hit the UUID, not the first row.
|
|
122
118
|
expect(calls[2].url).toContain('/milestones/11111111-2222-3333-4444-555555555555');
|
|
123
119
|
});
|
|
124
120
|
it('throws a clear error when milestone is not found', async () => {
|
package/dist/tools/milestones.js
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase B - milestone tools, expanded in ORB-799 with CRUD.
|
|
3
|
-
*
|
|
4
|
-
* `orboto_list_milestones` and `orboto_get_milestone` share this file
|
|
5
|
-
* because they're cheap neighbours (same API root, same resolution
|
|
6
|
-
* chain). The `get` tool also pulls the `/progress` endpoint so the
|
|
7
|
-
* model sees ticket-count breakdowns alongside the metadata.
|
|
8
|
-
*
|
|
9
|
-
* ORB-799 added the write half of the surface to close wrapper-parity:
|
|
10
|
-
*
|
|
11
|
-
* - orboto_create_milestone - mirrors `orboto.mjs create-milestone`.
|
|
12
|
-
* `startDate`/`endDate` are both optional; a milestone without dates
|
|
13
|
-
* is a legitimate object (analytics/Gantt/templates treat null dates
|
|
14
|
-
* as "no dates").
|
|
15
|
-
* - orboto_close_milestone - close + optional archive. Resolves the
|
|
16
|
-
* milestone by name OR UUID against the includeClosed=true list so
|
|
17
|
-
* re-closing an already-closed milestone is idempotent.
|
|
18
|
-
* - orboto_update_milestone - patch name / dates / private flag.
|
|
19
3
|
*/
|
|
20
4
|
import { z } from 'zod';
|
|
21
5
|
import { OrbotoApiError } from '../orboto-client.js';
|
|
@@ -24,9 +8,6 @@ const UUID_RE = /^[0-9a-f-]{36}$/i;
|
|
|
24
8
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
25
9
|
/** ORB-1059 - a milestone key looks like `ORB-M3`. */
|
|
26
10
|
const MILESTONE_KEY_RE = /^[A-Za-z0-9]+-M\d+$/i;
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// orboto_list_milestones
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
11
|
export const listMilestonesToolConfig = {
|
|
31
12
|
title: 'List milestones',
|
|
32
13
|
description: 'List milestones in a project, newest first.',
|
|
@@ -51,7 +32,6 @@ export function makeListMilestonesHandler(client) {
|
|
|
51
32
|
structuredContent: {
|
|
52
33
|
project: { key: project.key },
|
|
53
34
|
milestones: milestones.map((m) => ({
|
|
54
|
-
// ORB-1179 - surface the uuid alongside the milestone key.
|
|
55
35
|
id: m.id,
|
|
56
36
|
milestoneKey: m.milestoneKey ?? null,
|
|
57
37
|
name: m.name,
|
|
@@ -64,9 +44,6 @@ export function makeListMilestonesHandler(client) {
|
|
|
64
44
|
};
|
|
65
45
|
};
|
|
66
46
|
}
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// orboto_get_milestone
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
47
|
export const getMilestoneToolConfig = {
|
|
71
48
|
title: 'Get milestone details',
|
|
72
49
|
description: 'Return milestone metadata plus ticket-count breakdown by status (to do / in progress / done / …).',
|
|
@@ -79,12 +56,7 @@ export const getMilestoneToolConfig = {
|
|
|
79
56
|
export function makeGetMilestoneHandler(client) {
|
|
80
57
|
return async ({ projectKey, milestone }) => {
|
|
81
58
|
const project = await resolveProjectByKey(client, projectKey);
|
|
82
|
-
// ORB-1068 - resolve key / name / UUID through the shared resolver
|
|
83
|
-
// instead of an exact-name find, so `ORB-M19` works here too.
|
|
84
59
|
const m = await resolveMilestoneByNameOrId(client, project.id, milestone);
|
|
85
|
-
// Progress comes from a separate endpoint; tolerate 404 gracefully
|
|
86
|
-
// in case a future API rename drops it so the tool still returns
|
|
87
|
-
// the metadata half.
|
|
88
60
|
const progress = await client.get(`/projects/${project.id}/milestones/${m.id}/progress`).catch((err) => {
|
|
89
61
|
if (err instanceof OrbotoApiError && err.status === 404)
|
|
90
62
|
return null;
|
|
@@ -121,9 +93,6 @@ export function makeGetMilestoneHandler(client) {
|
|
|
121
93
|
};
|
|
122
94
|
};
|
|
123
95
|
}
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// orboto_create_milestone - ORB-799
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
96
|
export const createMilestoneToolConfig = {
|
|
128
97
|
title: 'Create a milestone',
|
|
129
98
|
description: 'Create a milestone in a project. `startDate` + `endDate` are optional - omit them (or pass null) for a milestone with no dates. The caller must have `milestone:create`.',
|
|
@@ -163,9 +132,6 @@ export function makeCreateMilestoneHandler(client) {
|
|
|
163
132
|
};
|
|
164
133
|
};
|
|
165
134
|
}
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
// orboto_close_milestone - ORB-799
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
135
|
/** Resolve a milestone by name OR UUID against the includeClosed list,
|
|
170
136
|
* so closing an already-completed milestone (re-close), archiving a
|
|
171
137
|
* completed milestone, and re-pointing a ticket onto any milestone all
|
|
@@ -183,7 +149,6 @@ export async function resolveMilestoneByNameOrId(client, projectId, nameOrId) {
|
|
|
183
149
|
}
|
|
184
150
|
return byId;
|
|
185
151
|
}
|
|
186
|
-
// ORB-1059 - accept the human-readable key (`ORB-M3`), case-insensitive.
|
|
187
152
|
if (MILESTONE_KEY_RE.test(nameOrId)) {
|
|
188
153
|
const byKey = all.find((x) => x.milestoneKey?.toLowerCase() === nameOrId.toLowerCase());
|
|
189
154
|
if (!byKey) {
|
|
@@ -191,11 +156,6 @@ export async function resolveMilestoneByNameOrId(client, projectId, nameOrId) {
|
|
|
191
156
|
}
|
|
192
157
|
return byKey;
|
|
193
158
|
}
|
|
194
|
-
// ORB-1826 - exact (raw) name match wins first; falls back to a unique
|
|
195
|
-
// normalised match (HTML-entity-decoded, trimmed, whitespace-collapsed,
|
|
196
|
-
// casefolded) so `"QA & Testing"` resolves against a milestone
|
|
197
|
-
// literally named `"QA & Testing"`. Ambiguous either way still errors,
|
|
198
|
-
// listing the candidates.
|
|
199
159
|
const { match, ambiguous } = resolveByName(all, nameOrId, (m) => m.name);
|
|
200
160
|
if (ambiguous) {
|
|
201
161
|
const list = ambiguous.map((m) => `"${m.name}" (${m.id})`).join(', ');
|
|
@@ -236,9 +196,6 @@ export function makeCloseMilestoneHandler(client) {
|
|
|
236
196
|
};
|
|
237
197
|
};
|
|
238
198
|
}
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
// orboto_update_milestone - ORB-799
|
|
241
|
-
// ---------------------------------------------------------------------------
|
|
242
199
|
export const updateMilestoneToolConfig = {
|
|
243
200
|
title: 'Update a milestone\'s fields',
|
|
244
201
|
description: 'Patch a milestone (`name`, `customerSummary`, `startDate`, `endDate`, `isPrivate`). At least one field must be set. `customerSummary` is the customer-facing text shown in the customer project report instead of any internal text. Use `orboto_close_milestone` to flip status to completed/archived - this tool intentionally does NOT touch the status field so closing remains a clear semantic operation.',
|
package/dist/tools/my-tickets.js
CHANGED
|
@@ -21,10 +21,6 @@ export const myTicketsToolConfig = {
|
|
|
21
21
|
}).shape,
|
|
22
22
|
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
23
23
|
};
|
|
24
|
-
// The API route's `statuses` param is the uppercase LEGACY status
|
|
25
|
-
// enum (TODO/IN_PROGRESS/IN_REVIEW/DONE/WONT_FIX). The MCP tool
|
|
26
|
-
// accepts the lower-case category enum the rest of the surface uses;
|
|
27
|
-
// map here.
|
|
28
24
|
const CATEGORY_TO_LEGACY = {
|
|
29
25
|
todo: 'TODO',
|
|
30
26
|
in_progress: 'IN_PROGRESS',
|
|
@@ -40,8 +36,6 @@ export function makeMyTicketsHandler(client) {
|
|
|
40
36
|
qs.set('statuses', CATEGORY_TO_LEGACY[input.statusCategory]);
|
|
41
37
|
}
|
|
42
38
|
else {
|
|
43
|
-
// Default: exclude done + wont_fix so "what am I working on?"
|
|
44
|
-
// returns open work rather than a lifetime history.
|
|
45
39
|
qs.set('statuses', 'TODO,IN_PROGRESS,IN_REVIEW');
|
|
46
40
|
}
|
|
47
41
|
const page = await client.get(`/users/me/assigned-tickets?${qs}`);
|
|
@@ -57,7 +51,6 @@ export function makeMyTicketsHandler(client) {
|
|
|
57
51
|
structuredContent: {
|
|
58
52
|
count: page.items.length,
|
|
59
53
|
hasMore: !!page.nextCursor,
|
|
60
|
-
// ORB-1699 - shared lean row; verbose restores uuid/labels/minutes.
|
|
61
54
|
tickets: page.items.map((t) => agentTicketListRow(t, input.verbose ?? false)),
|
|
62
55
|
},
|
|
63
56
|
};
|