@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
package/dist/server.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-244 Phase A/B - MCP server factory.
|
|
3
|
-
*
|
|
4
|
-
* Builds an `McpServer` with the registered tool set + a handle to
|
|
5
|
-
* the orboto REST client. Transport is picked by the process entry
|
|
6
|
-
* point (`index.ts`) based on `ORBOTO_MCP_TRANSPORT=stdio|http`.
|
|
7
|
-
*
|
|
8
|
-
* Keeping the server factory transport-agnostic means `server.ts` is
|
|
9
|
-
* the same object whether we ship stdio for Local-Proxy (Phase G
|
|
10
|
-
* `@orboto/mcp-cli`) or HTTP-SSE for Self-Hosted-inline.
|
|
11
|
-
*
|
|
12
|
-
* Tool registrations stay in this file so adding a new tool in
|
|
13
|
-
* Phase C is one diff here + one new file in `tools/`. Phase D will
|
|
14
|
-
* add `registerResource` / `registerPrompt` calls here too.
|
|
15
3
|
*/
|
|
16
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
17
5
|
import { VERSION } from './version.js';
|
|
@@ -68,12 +56,11 @@ import { listGitAppInstallationsToolConfig, makeListGitAppInstallationsHandler }
|
|
|
68
56
|
import { createTicketToolConfig, makeCreateTicketHandler, updateTicketToolConfig, makeUpdateTicketHandler, moveTicketToolConfig, makeMoveTicketHandler, closeTicketToolConfig, makeCloseTicketHandler, deleteTicketToolConfig, makeDeleteTicketHandler, commentToolConfig, makeCommentHandler, updateCommentToolConfig, makeUpdateCommentHandler, deleteCommentToolConfig, makeDeleteCommentHandler, assignToolConfig, makeAssignHandler, unassignToolConfig, makeUnassignHandler, labelTicketToolConfig, makeLabelTicketHandler, unlabelTicketToolConfig, makeUnlabelTicketHandler, setMilestoneToolConfig, makeSetMilestoneHandler, addTicketDependencyToolConfig, makeAddTicketDependencyHandler, removeTicketDependencyToolConfig, makeRemoveTicketDependencyHandler, listTicketDependenciesToolConfig, makeListTicketDependenciesHandler, } from './tools/ticket-writes.js';
|
|
69
57
|
import { timerStartToolConfig, makeTimerStartHandler, timerStopToolConfig, makeTimerStopHandler, logTimeToolConfig, makeLogTimeHandler, listTimeEntriesToolConfig, makeListTimeEntriesHandler, editTimeEntryToolConfig, makeEditTimeEntryHandler, deleteTimeEntryToolConfig, makeDeleteTimeEntryHandler, } from './tools/time-writes.js';
|
|
70
58
|
import { listTicketSchedulesToolConfig, makeListTicketSchedulesHandler, scheduleTicketSessionToolConfig, makeScheduleTicketSessionHandler, cancelTicketSessionToolConfig, makeCancelTicketSessionHandler, } from './tools/ticket-schedules.js';
|
|
71
|
-
import { checkToolConfig, makeCheckHandler, uncheckToolConfig, makeUncheckHandler, addCheckToolConfig, makeAddCheckHandler, removeCheckToolConfig, makeRemoveCheckHandler, newChecklistToolConfig, makeNewChecklistHandler, } from './tools/checklist-writes.js';
|
|
59
|
+
import { checkToolConfig, makeCheckHandler, uncheckToolConfig, makeUncheckHandler, addCheckToolConfig, makeAddCheckHandler, removeCheckToolConfig, makeRemoveCheckHandler, updateCheckToolConfig, makeUpdateCheckHandler, newChecklistToolConfig, makeNewChecklistHandler, } from './tools/checklist-writes.js';
|
|
72
60
|
import { listUsersToolConfig, makeListUsersHandler, getAuditLogToolConfig, makeGetAuditLogHandler, triggerBackupToolConfig, makeTriggerBackupHandler, } from './tools/admin-writes.js';
|
|
73
61
|
import { freeBusyToolConfig, makeFreeBusyHandler } from './tools/free-busy.js';
|
|
74
62
|
import { createFullBackupToolConfig, makeCreateFullBackupHandler, listBackupsToolConfig, makeListBackupsHandler, downloadBackupToolConfig, makeDownloadBackupHandler, } from './tools/backup.js';
|
|
75
63
|
import { primerFactListToolConfig, makePrimerFactListHandler, primerFactAddToolConfig, makePrimerFactAddHandler, primerFactUpdateToolConfig, makePrimerFactUpdateHandler, primerFactSupersedeToolConfig, makePrimerFactSupersedeHandler, primerFactVerifyToolConfig, makePrimerFactVerifyHandler, primerFactDeleteToolConfig, makePrimerFactDeleteHandler, } from './tools/primer-facts.js';
|
|
76
|
-
// ORB-799 - wrapper-feature-parity gap-close.
|
|
77
64
|
import { whoamiToolConfig, makeWhoamiHandler } from './tools/identity.js';
|
|
78
65
|
import { claimToolConfig, makeClaimHandler, unclaimToolConfig, makeUnclaimHandler, } from './tools/claim.js';
|
|
79
66
|
import { reportFeedbackToolConfig, makeReportFeedbackHandler } from './tools/feedback.js';
|
|
@@ -103,18 +90,7 @@ const FALLBACK_WORKING_RULES = [
|
|
|
103
90
|
'When you write a git commit that touches a ticket, put the ticket key (e.g. `ORB-42`) in parentheses at the END of the subject line - `feat(auth): add token rotation (ORB-42)`. The orboto git-activity parser links the commit by that key.',
|
|
104
91
|
'Use sub-tickets for steps large enough to need their own commit / time tracking / review, and checklists for one-liners inside a single ticket\'s scope. Big features (Epic + 3+ phase tickets): create a milestone FIRST, then the Epic, then phase tickets as children on that milestone.',
|
|
105
92
|
].join(' ');
|
|
106
|
-
// Static MCP operational hints - tool/resource/prompt usage that does
|
|
107
|
-
// not change per workspace. The configurable working-rules are appended
|
|
108
|
-
// live below. ORB-1520 - assembled per toolset: the curated manifest
|
|
109
|
-
// gets the escape-hatch pointer, the full manifest keeps the
|
|
110
|
-
// get_checklists hint (that tool only exists there).
|
|
111
93
|
function staticMcpHints(toolset) {
|
|
112
|
-
// ORB-1805 - the minimal tier serves 8k/16k local models, where the
|
|
113
|
-
// instructions block competes with the user's own turn for the same
|
|
114
|
-
// few thousand tokens. Everything that is merely NICE to know
|
|
115
|
-
// (resources, prompts, checklist semantics, primer) is dropped here:
|
|
116
|
-
// `orboto_session_start` delivers the authoritative rules on the first
|
|
117
|
-
// call anyway, and `orboto_api_search` reaches the rest.
|
|
118
94
|
if (toolset === 'minimal') {
|
|
119
95
|
return [
|
|
120
96
|
'orboto is a ticket + project management system. Ticket keys look like `PROJ-123`; the first segment is the project key.',
|
|
@@ -139,14 +115,6 @@ function staticMcpHints(toolset) {
|
|
|
139
115
|
'All writes respect the caller\'s project-level permissions - a 403 means the API rejected the write, not the MCP server.',
|
|
140
116
|
].filter((s) => s.length > 0).join(' ');
|
|
141
117
|
}
|
|
142
|
-
// ORB-1177 - MCP clients cap how much of the `instructions` block they
|
|
143
|
-
// inject and silently truncate the overflow (ORB-1168 saw a 5k+ string
|
|
144
|
-
// cut mid-rule). Enforce our own budget so the high-priority head (hints
|
|
145
|
-
// + non-negotiables + the orboto_session_start / orboto://rules pointers)
|
|
146
|
-
// always survives, and the workspace rules are cut at a WHOLE-LINE
|
|
147
|
-
// boundary with an explicit pointer to the cap-independent full set,
|
|
148
|
-
// rather than the client slicing mid-sentence. The complete rules are
|
|
149
|
-
// always available via orboto_session_start + the orboto://rules resource.
|
|
150
118
|
const INSTRUCTIONS_BUDGET = 4000;
|
|
151
119
|
const RULES_HEADING = 'Working rules for this workspace:\n';
|
|
152
120
|
const TRUNCATION_MARKER = '\n\n[... rules truncated to fit the client cap - read the COMPLETE rules via the orboto_session_start tool or the orboto://rules resource ...]';
|
|
@@ -154,7 +122,7 @@ export function assembleInstructions(head, workingRules, budget = INSTRUCTIONS_B
|
|
|
154
122
|
const full = `${head}\n\n${RULES_HEADING}${workingRules}`;
|
|
155
123
|
if (full.length <= budget)
|
|
156
124
|
return full;
|
|
157
|
-
const room = budget - head.length - RULES_HEADING.length - TRUNCATION_MARKER.length - 2;
|
|
125
|
+
const room = budget - head.length - RULES_HEADING.length - TRUNCATION_MARKER.length - 2;
|
|
158
126
|
let kept = '';
|
|
159
127
|
if (room > 0) {
|
|
160
128
|
for (const line of workingRules.split('\n')) {
|
|
@@ -167,23 +135,10 @@ export function assembleInstructions(head, workingRules, budget = INSTRUCTIONS_B
|
|
|
167
135
|
}
|
|
168
136
|
export async function buildOrbotoMcpServer(opts) {
|
|
169
137
|
const client = new OrbotoClient(opts);
|
|
170
|
-
// ORB-1520 - the manifest mode. Explicit option (per-connection) wins,
|
|
171
|
-
// then the process-wide env default, then `curated`.
|
|
172
138
|
const toolset = resolveToolset(opts.toolset, process.env.ORBOTO_MCP_TOOLSET);
|
|
173
|
-
// ORB-1090 - fetch the workspace's configurable working-rules at
|
|
174
|
-
// connect so admin edits propagate to every new MCP session. The
|
|
175
|
-
// instructions block is the one place every MCP client reliably sees
|
|
176
|
-
// the rules (clients don't read the repo's CLAUDE.md). If unavailable,
|
|
177
|
-
// keep the connection usable for recovery but gate all other tools.
|
|
178
139
|
let workingRules = FALLBACK_WORKING_RULES;
|
|
179
|
-
// ORB-1471 - the same connect-time fetch also reports whether this
|
|
180
|
-
// workspace requires `orboto_session_start` before any other tool call
|
|
181
|
-
// (the hard gate). Unknown policy must never silently disable the gate.
|
|
182
140
|
let requireSessionStart = true;
|
|
183
141
|
try {
|
|
184
|
-
// ORB-1753 - stdio deployments can declare their profile via
|
|
185
|
-
// ORBOTO_AGENT_KIND / ORBOTO_MODEL_TIER; api-key standing profiles
|
|
186
|
-
// resolve server-side either way.
|
|
187
142
|
const connectParams = new URLSearchParams();
|
|
188
143
|
applyAgentProfile(connectParams);
|
|
189
144
|
const connectQs = connectParams.toString();
|
|
@@ -196,33 +151,9 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
196
151
|
workingRules = 'Workspace rules are unavailable. Built-in hints are not a substitute. Call orboto_session_start with forceRules and retry after connectivity or authentication recovers.';
|
|
197
152
|
}
|
|
198
153
|
const server = new McpServer({ name: 'orboto', version: VERSION }, {
|
|
199
|
-
// ORB-940 - advertise resources/subscribe so MCP-aware clients
|
|
200
|
-
// (Claude Desktop, Cursor) wire up live updates instead of
|
|
201
|
-
// polling. Even when no bridge is hooked up (stdio sessions)
|
|
202
|
-
// the capability stays on; subscriptions just stay quiet.
|
|
203
154
|
capabilities: {
|
|
204
155
|
resources: { subscribe: true, listChanged: true },
|
|
205
156
|
},
|
|
206
|
-
// `instructions` appears in the system-prompt-style block some
|
|
207
|
-
// MCP clients inject before the user's first message. Static
|
|
208
|
-
// operational hints + the live, workspace-configurable working
|
|
209
|
-
// rules (ORB-1086).
|
|
210
|
-
//
|
|
211
|
-
// ORB-1168 - MCP clients cap how much of this block they inject; a
|
|
212
|
-
// 5k+ string was being silently truncated mid-rule, so an MCP-only
|
|
213
|
-
// agent (no skill / no repo CLAUDE.md) got incomplete rules. Put
|
|
214
|
-
// the orientation, a pointer to the full rule set (the
|
|
215
|
-
// orboto_session_start tool), and the core non-negotiables
|
|
216
|
-
// FIRST so they survive any truncation; the full workspace rules
|
|
217
|
-
// follow and only their tail is at risk.
|
|
218
|
-
// ORB-1805 - the minimal tier ships the HEAD ONLY: no workspace
|
|
219
|
-
// rules block at all. On an 8k window every rule line here is a
|
|
220
|
-
// line the user's own turn does not get, and the rules are not
|
|
221
|
-
// lost - `orboto_session_start` (which the head demands as the
|
|
222
|
-
// first action) returns the complete, authoritative set, and its
|
|
223
|
-
// length is then the caller's budget to spend, not a fixed tax on
|
|
224
|
-
// every connect. It also makes this block a CONSTANT: a workspace
|
|
225
|
-
// that grows its rules cannot grow the minimal manifest.
|
|
226
157
|
instructions: toolset === 'minimal'
|
|
227
158
|
? [
|
|
228
159
|
staticMcpHints(toolset),
|
|
@@ -233,71 +164,34 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
233
164
|
'FIRST ACTION this session: call the `orboto_session_start` tool - it returns the complete, authoritative binding rules you must follow (plus your in-progress work). Re-run it after any context compaction. If the rules below look cut off, `orboto_session_start` and the `orboto://rules` resource always have the full set. (Do NOT use `orboto_list_agent_instructions` to read the rules - that manages rule blocks for admins.) Core non-negotiables: ticket-first (claim or create a ticket before touching code), one commit per ticket with the ticket key in the subject line, push after each commit, and never mark work done that is not actually done.',
|
|
234
165
|
].join('\n\n'), workingRules),
|
|
235
166
|
});
|
|
236
|
-
// ORB-1331 - one-time session-start nudge. The state object lives for
|
|
237
|
-
// the lifetime of THIS server instance: the HTTP transport builds one
|
|
238
|
-
// server per session and the stdio transport one per process, so a
|
|
239
|
-
// single flag object is per-session (HTTP) / process-local (stdio)
|
|
240
|
-
// without any session-lifecycle plumbing. Shared by reference into
|
|
241
|
-
// every tool's dispatch wrapper below.
|
|
242
167
|
const nudgeState = createNudgeState(requireSessionStart);
|
|
243
|
-
// ORB-311 - every tool dispatch posts one row to /admin/mcp/instrument
|
|
244
|
-
// via the withMetrics wrapper. `reg` is a one-line shim around
|
|
245
|
-
// `server.registerTool` that adds the metrics layer at registration
|
|
246
|
-
// time; per-tool files stay metrics-unaware.
|
|
247
|
-
// ORB-1520 - the toolset gate. Wraps the metrics-registering shim so a
|
|
248
|
-
// tail tool in curated mode simply never registers; the reg() CALL
|
|
249
|
-
// SITES below stay untouched (the chat-tools drift guard parses them
|
|
250
|
-
// from this file's source and must keep seeing the full set).
|
|
251
168
|
const regAll = registerWithMetrics(server, client, opts.userAgentSuffix, nudgeState);
|
|
252
169
|
const reg = (toolName, config, handler) => {
|
|
253
170
|
if (!toolInToolset(toolName, toolset))
|
|
254
171
|
return;
|
|
255
|
-
// ORB-1805 - the minimal tier drops the human-readable `title`
|
|
256
|
-
// (measured 580 characters across its 12 tools). `name` is already
|
|
257
|
-
// self-describing (orboto_create_ticket) and a client without a
|
|
258
|
-
// title falls back to it, so the display loss is small where the
|
|
259
|
-
// token pressure is largest. Curated/full keep their titles.
|
|
260
172
|
const cfg = toolset === 'minimal' && config && 'title' in config
|
|
261
173
|
? (() => { const { title: _title, ...rest } = config; return rest; })()
|
|
262
174
|
: config;
|
|
263
175
|
regAll(toolName, cfg, handler);
|
|
264
176
|
};
|
|
265
|
-
// Tools - alphabetical-ish by concept. Each tool file owns its
|
|
266
|
-
// input/output schema; the server just glues names to handlers.
|
|
267
177
|
reg('orboto_ai_status', aiStatusToolConfig, makeAiStatusHandler(client));
|
|
268
|
-
reg('orboto_draft_customer_reply', draftCustomerReplyToolConfig, makeDraftCustomerReplyHandler(client));
|
|
178
|
+
reg('orboto_draft_customer_reply', draftCustomerReplyToolConfig, makeDraftCustomerReplyHandler(client));
|
|
269
179
|
reg('orboto_embedding_status', embeddingStatusToolConfig, makeEmbeddingStatusHandler(client));
|
|
270
180
|
reg('orboto_ai_usage', aiUsageToolConfig, makeAiUsageHandler(client));
|
|
271
|
-
// ORB-1093 - session-start / post-compact re-orientation digest.
|
|
272
181
|
reg('orboto_session_start', sessionStartToolConfig, makeSessionStartHandler(client));
|
|
273
|
-
// ORB-1697 - the way back from a budget-truncated response. Serves the
|
|
274
|
-
// omitted remainder from the in-process store, so capping a response
|
|
275
|
-
// never loses content.
|
|
276
182
|
reg('orboto_response_expand', responseExpandToolConfig, makeResponseExpandHandler());
|
|
277
|
-
// ORB-1741 - lazy full guidance: manifest descriptions are one-line
|
|
278
|
-
// summaries (tool-docs.ts); this serves the complete text on demand.
|
|
279
183
|
reg('orboto_help', helpToolConfig, makeHelpHandler());
|
|
280
|
-
// ORB-1518 - Code-Mode escape hatch, discovery half: search the live
|
|
281
|
-
// OpenAPI spec / fetch one endpoint's schema on demand. Keeps the whole
|
|
282
|
-
// REST surface reachable without a named tool per endpoint.
|
|
283
184
|
reg('orboto_api_search', apiSearchToolConfig, makeApiSearchHandler(client));
|
|
284
|
-
// ORB-1519 - the execute half: structured REST proxy through the
|
|
285
|
-
// API's full auth + permission chain (POST /system/api-proxy).
|
|
286
185
|
reg('orboto_api_call', apiCallToolConfig, makeApiCallHandler(client));
|
|
287
|
-
// ORB-1089 - manage the configurable coding-agent rule blocks (admin:ai:write).
|
|
288
186
|
reg('orboto_list_agent_instructions', listAgentInstructionsToolConfig, makeListAgentInstructionsHandler(client));
|
|
289
187
|
reg('orboto_create_agent_instruction', createAgentInstructionToolConfig, makeCreateAgentInstructionHandler(client));
|
|
290
188
|
reg('orboto_update_agent_instruction', updateAgentInstructionToolConfig, makeUpdateAgentInstructionHandler(client));
|
|
291
189
|
reg('orboto_reset_agent_instruction', resetAgentInstructionToolConfig, makeResetAgentInstructionHandler(client));
|
|
292
190
|
reg('orboto_delete_agent_instruction', deleteAgentInstructionToolConfig, makeDeleteAgentInstructionHandler(client));
|
|
293
|
-
// ORB-705 - Multi-Agent Coordination tools (heartbeat, presence,
|
|
294
|
-
// directed notify). Layered on the ORB-704 REST surface.
|
|
295
191
|
reg('orboto_agent_heartbeat', agentHeartbeatToolConfig, makeAgentHeartbeatHandler(client));
|
|
296
192
|
reg('orboto_agent_presence', agentPresenceToolConfig, makeAgentPresenceHandler(client));
|
|
297
193
|
reg('orboto_agent_notify', agentNotifyToolConfig, makeAgentNotifyHandler(client));
|
|
298
|
-
// ORB-1727 - the receive half of the store-and-forward inbox.
|
|
299
194
|
reg('orboto_messages', agentMessagesToolConfig, makeAgentMessagesHandler(client));
|
|
300
|
-
// ORB-964 - scoped fan-out for multi-agent coordination.
|
|
301
195
|
reg('orboto_agent_broadcast', agentBroadcastToolConfig, makeAgentBroadcastHandler(client));
|
|
302
196
|
reg('orboto_list_projects', listProjectsToolConfig, makeListProjectsHandler(client));
|
|
303
197
|
reg('orboto_get_project', getProjectToolConfig, makeGetProjectHandler(client));
|
|
@@ -317,42 +211,27 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
317
211
|
reg('orboto_requirements_spec', requirementsSpecToolConfig, makeRequirementsSpecHandler(client));
|
|
318
212
|
reg('orboto_list_doc_spaces', listDocSpacesToolConfig, makeListDocSpacesHandler(client));
|
|
319
213
|
reg('orboto_get_doc', getDocToolConfig, makeGetDocHandler(client));
|
|
320
|
-
// ORB-1342 (epic ORB-1339) - context-efficient doc snippet search +
|
|
321
|
-
// targeted edits. Wrap GET /docs/search (ORB-1340) and POST
|
|
322
|
-
// /docs/:id/edits (ORB-1341). Prefer these over get_doc + update_doc for
|
|
323
|
-
// small changes to a large doc: find a passage by snippet, change it by
|
|
324
|
-
// diff, never transfer the full body in either direction.
|
|
325
214
|
reg('orboto_search_docs', searchDocsToolConfig, makeSearchDocsHandler(client));
|
|
326
215
|
reg('orboto_edit_doc', editDocToolConfig, makeEditDocHandler(client));
|
|
327
216
|
reg('orboto_edit_doc_section', editDocSectionToolConfig, makeEditDocSectionHandler(client));
|
|
328
|
-
// ORB-912 - doc-spaces CRUD + list docs in a space. Closes the
|
|
329
|
-
// first slice of the MCP doc-surface parity gap (epic ORB-911).
|
|
330
217
|
reg('orboto_create_doc_space', createDocSpaceToolConfig, makeCreateDocSpaceHandler(client));
|
|
331
218
|
reg('orboto_update_doc_space', updateDocSpaceToolConfig, makeUpdateDocSpaceHandler(client));
|
|
332
219
|
reg('orboto_delete_doc_space', deleteDocSpaceToolConfig, makeDeleteDocSpaceHandler(client));
|
|
333
220
|
reg('orboto_list_docs_in_space', listDocsInSpaceToolConfig, makeListDocsInSpaceHandler(client));
|
|
334
|
-
// ORB-913 - doc-page CRUD (plain create + update + delete + move).
|
|
335
221
|
reg('orboto_create_doc', createDocToolConfig, makeCreateDocHandler(client));
|
|
336
222
|
reg('orboto_update_doc', updateDocToolConfig, makeUpdateDocHandler(client));
|
|
337
223
|
reg('orboto_delete_doc', deleteDocToolConfig, makeDeleteDocHandler(client));
|
|
338
224
|
reg('orboto_move_doc', moveDocToolConfig, makeMoveDocHandler(client));
|
|
339
|
-
// ORB-914 - doc-attachments (upload + list + delete).
|
|
340
225
|
reg('orboto_upload_doc_attachment', uploadDocAttachmentToolConfig, makeUploadDocAttachmentHandler(client));
|
|
341
226
|
reg('orboto_list_doc_attachments', listDocAttachmentsToolConfig, makeListDocAttachmentsHandler(client));
|
|
342
227
|
reg('orboto_delete_doc_attachment', deleteDocAttachmentToolConfig, makeDeleteDocAttachmentHandler(client));
|
|
343
|
-
// ORB-1455 - ticket-attachment read + view (list metadata, fetch bytes /
|
|
344
|
-
// image content block). Complements orboto_attach_to_ticket (write-only).
|
|
345
228
|
reg('orboto_list_ticket_attachments', listTicketAttachmentsToolConfig, makeListTicketAttachmentsHandler(client));
|
|
346
229
|
reg('orboto_get_attachment', getAttachmentToolConfig, makeGetAttachmentHandler(client));
|
|
347
|
-
// ORB-915 - doc export (Markdown + PDF).
|
|
348
230
|
reg('orboto_export_doc_md', exportDocMdToolConfig, makeExportDocMdHandler(client));
|
|
349
231
|
reg('orboto_export_doc_pdf', exportDocPdfToolConfig, makeExportDocPdfHandler(client));
|
|
350
|
-
// ORB-916 - doc revision history (list + get + restore). Closes
|
|
351
|
-
// the last slice of the doc-surface parity gap (epic ORB-911).
|
|
352
232
|
reg('orboto_list_doc_revisions', listDocRevisionsToolConfig, makeListDocRevisionsHandler(client));
|
|
353
233
|
reg('orboto_get_doc_revision', getDocRevisionToolConfig, makeGetDocRevisionHandler(client));
|
|
354
234
|
reg('orboto_restore_doc_revision', restoreDocRevisionToolConfig, makeRestoreDocRevisionHandler(client));
|
|
355
|
-
// ORB-917 - doc comments (list + post + resolve + delete).
|
|
356
235
|
reg('orboto_list_doc_comments', listDocCommentsToolConfig, makeListDocCommentsHandler(client));
|
|
357
236
|
reg('orboto_post_doc_comment', postDocCommentToolConfig, makePostDocCommentHandler(client));
|
|
358
237
|
reg('orboto_resolve_doc_comment', resolveDocCommentToolConfig, makeResolveDocCommentHandler(client));
|
|
@@ -365,13 +244,8 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
365
244
|
reg('orboto_add_cross_project_link', addCrossProjectLinkToolConfig, makeAddCrossProjectLinkHandler(client));
|
|
366
245
|
reg('orboto_update_cross_project_link', updateCrossProjectLinkToolConfig, makeUpdateCrossProjectLinkHandler(client));
|
|
367
246
|
reg('orboto_remove_cross_project_link', removeCrossProjectLinkToolConfig, makeRemoveCrossProjectLinkHandler(client));
|
|
368
|
-
// ORB-918 - duplicate-space + resolve-links. Closes the doc-surface
|
|
369
|
-
// parity epic (ORB-911) - every doc-API endpoint now has an MCP
|
|
370
|
-
// pendant.
|
|
371
247
|
reg('orboto_duplicate_doc_space', duplicateDocSpaceToolConfig, makeDuplicateDocSpaceHandler(client));
|
|
372
248
|
reg('orboto_resolve_doc_smart_links', resolveDocSmartLinksToolConfig, makeResolveDocSmartLinksHandler(client));
|
|
373
|
-
// ORB-855 - LLM-Wiki tools (ingest / ask / lint / plan-apply / record /
|
|
374
|
-
// append-section / flag-stale). Thin wrappers over the Phase B/C/D routes.
|
|
375
249
|
reg('orboto_wiki_ingest_url', wikiIngestUrlToolConfig, makeWikiIngestUrlHandler(client));
|
|
376
250
|
reg('orboto_wiki_ask', wikiAskToolConfig, makeWikiAskHandler(client));
|
|
377
251
|
reg('orboto_wiki_lint', wikiLintToolConfig, makeWikiLintHandler(client));
|
|
@@ -381,16 +255,12 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
381
255
|
reg('orboto_wiki_append_section', wikiAppendSectionToolConfig, makeWikiAppendSectionHandler(client));
|
|
382
256
|
reg('orboto_wiki_flag_stale', wikiFlagStaleToolConfig, makeWikiFlagStaleHandler(client));
|
|
383
257
|
reg('orboto_wiki_save_answer', wikiSaveAnswerToolConfig, makeWikiSaveAnswerHandler(client));
|
|
384
|
-
// ORB-862 - personal AI-preference facts (owner-scoped).
|
|
385
258
|
reg('orboto_personal_fact_list', personalFactListToolConfig, makePersonalFactListHandler(client));
|
|
386
259
|
reg('orboto_personal_fact_add', personalFactAddToolConfig, makePersonalFactAddHandler(client));
|
|
387
260
|
reg('orboto_personal_fact_update', personalFactUpdateToolConfig, makePersonalFactUpdateHandler(client));
|
|
388
261
|
reg('orboto_personal_fact_delete', personalFactDeleteToolConfig, makePersonalFactDeleteHandler(client));
|
|
389
262
|
reg('orboto_get_timer', getTimerToolConfig, makeGetTimerHandler(client));
|
|
390
263
|
reg('orboto_list_git_app_installations', listGitAppInstallationsToolConfig, makeListGitAppInstallationsHandler(client));
|
|
391
|
-
// ORB-309 Phase C - write tools (Group 1: ticket mutations).
|
|
392
|
-
// Each respects the API's PBAC cascade - a 403 surfaces as
|
|
393
|
-
// OrbotoApiError → MCP throws → client sees an isError response.
|
|
394
264
|
reg('orboto_create_ticket', createTicketToolConfig, makeCreateTicketHandler(client));
|
|
395
265
|
reg('orboto_update_ticket', updateTicketToolConfig, makeUpdateTicketHandler(client));
|
|
396
266
|
reg('orboto_move_ticket', moveTicketToolConfig, makeMoveTicketHandler(client));
|
|
@@ -404,73 +274,45 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
404
274
|
reg('orboto_label_ticket', labelTicketToolConfig, makeLabelTicketHandler(client));
|
|
405
275
|
reg('orboto_unlabel_ticket', unlabelTicketToolConfig, makeUnlabelTicketHandler(client));
|
|
406
276
|
reg('orboto_set_milestone', setMilestoneToolConfig, makeSetMilestoneHandler(client));
|
|
407
|
-
// ORB-1037 - RACI agent surfaces: read the matrix + set a person's role.
|
|
408
277
|
reg('orboto_raci', raciToolConfig, makeRaciHandler(client));
|
|
409
278
|
reg('orboto_set_raci', setRaciToolConfig, makeSetRaciHandler(client));
|
|
410
|
-
// ORB-453 - ticket-dependency tools (3-way-sync gap filed after the
|
|
411
|
-
// skill wrapper landed in ORB-452). Same idempotent-on-409/404
|
|
412
|
-
// semantics as orboto_assign / orboto_unassign.
|
|
413
279
|
reg('orboto_add_ticket_dependency', addTicketDependencyToolConfig, makeAddTicketDependencyHandler(client));
|
|
414
280
|
reg('orboto_remove_ticket_dependency', removeTicketDependencyToolConfig, makeRemoveTicketDependencyHandler(client));
|
|
415
281
|
reg('orboto_list_ticket_dependencies', listTicketDependenciesToolConfig, makeListTicketDependenciesHandler(client));
|
|
416
|
-
// ORB-309 Phase C - Group 2: time tools.
|
|
417
282
|
reg('orboto_timer_start', timerStartToolConfig, makeTimerStartHandler(client));
|
|
418
283
|
reg('orboto_timer_stop', timerStopToolConfig, makeTimerStopHandler(client));
|
|
419
284
|
reg('orboto_log_time', logTimeToolConfig, makeLogTimeHandler(client));
|
|
420
285
|
reg('orboto_list_time_entries', listTimeEntriesToolConfig, makeListTimeEntriesHandler(client));
|
|
421
286
|
reg('orboto_edit_time_entry', editTimeEntryToolConfig, makeEditTimeEntryHandler(client));
|
|
422
287
|
reg('orboto_delete_time_entry', deleteTimeEntryToolConfig, makeDeleteTimeEntryHandler(client));
|
|
423
|
-
// ORB-626 - ticket meeting schedules (working sessions + iCal invites).
|
|
424
288
|
reg('orboto_list_ticket_schedules', listTicketSchedulesToolConfig, makeListTicketSchedulesHandler(client));
|
|
425
289
|
reg('orboto_schedule_ticket_session', scheduleTicketSessionToolConfig, makeScheduleTicketSessionHandler(client));
|
|
426
290
|
reg('orboto_cancel_ticket_session', cancelTicketSessionToolConfig, makeCancelTicketSessionHandler(client));
|
|
427
|
-
// ORB-309 Phase C - Group 3: checklist writes (ORB-234 surface).
|
|
428
291
|
reg('orboto_check', checkToolConfig, makeCheckHandler(client));
|
|
429
292
|
reg('orboto_uncheck', uncheckToolConfig, makeUncheckHandler(client));
|
|
430
293
|
reg('orboto_add_check', addCheckToolConfig, makeAddCheckHandler(client));
|
|
431
294
|
reg('orboto_remove_check', removeCheckToolConfig, makeRemoveCheckHandler(client));
|
|
295
|
+
reg('orboto_update_check', updateCheckToolConfig, makeUpdateCheckHandler(client));
|
|
432
296
|
reg('orboto_new_checklist', newChecklistToolConfig, makeNewChecklistHandler(client));
|
|
433
|
-
// ORB-309 Phase C - Group 4: admin-only tools. Each call hits a
|
|
434
|
-
// route gated on super-admin / admin:* permissions; a non-admin
|
|
435
|
-
// caller's 403 surfaces as a readable error from rewrite403().
|
|
436
297
|
reg('orboto_list_users', listUsersToolConfig, makeListUsersHandler(client));
|
|
437
|
-
// ORB-625 - free/busy availability lookup for assignee selection (read).
|
|
438
298
|
reg('orboto_free_busy', freeBusyToolConfig, makeFreeBusyHandler(client));
|
|
439
299
|
reg('orboto_get_audit_log', getAuditLogToolConfig, makeGetAuditLogHandler(client));
|
|
440
300
|
reg('orboto_trigger_backup', triggerBackupToolConfig, makeTriggerBackupHandler(client));
|
|
441
301
|
reg('orboto_create_full_backup', createFullBackupToolConfig, makeCreateFullBackupHandler(client));
|
|
442
302
|
reg('orboto_list_backups', listBackupsToolConfig, makeListBackupsHandler(client));
|
|
443
303
|
reg('orboto_download_backup', downloadBackupToolConfig, makeDownloadBackupHandler(client));
|
|
444
|
-
// ORB-510 / ORB-513 - primer-fact tools. Wraps the ORB-511 REST
|
|
445
|
-
// surface so agents can record structured project facts that the
|
|
446
|
-
// primer renderer (ORB-512) surfaces at session start. The skill
|
|
447
|
-
// rule (ORB-514) tells agents *when* to record; these are the *how*.
|
|
448
304
|
reg('orboto_primer_fact_list', primerFactListToolConfig, makePrimerFactListHandler(client));
|
|
449
305
|
reg('orboto_primer_fact_add', primerFactAddToolConfig, makePrimerFactAddHandler(client));
|
|
450
306
|
reg('orboto_primer_fact_update', primerFactUpdateToolConfig, makePrimerFactUpdateHandler(client));
|
|
451
307
|
reg('orboto_primer_fact_supersede', primerFactSupersedeToolConfig, makePrimerFactSupersedeHandler(client));
|
|
452
308
|
reg('orboto_primer_fact_verify', primerFactVerifyToolConfig, makePrimerFactVerifyHandler(client));
|
|
453
309
|
reg('orboto_primer_fact_delete', primerFactDeleteToolConfig, makePrimerFactDeleteHandler(client));
|
|
454
|
-
// ORB-799 - wrapper-feature-parity gap-close. Eight clusters:
|
|
455
|
-
// 1. Identity (whoami)
|
|
456
|
-
// 2. Composite (claim, unclaim)
|
|
457
|
-
// 3. Milestone CRUD (create, close, update)
|
|
458
|
-
// 4. Project listings (statuses, labels)
|
|
459
|
-
// 5. Bulk writes (patch, move, close, comment, assign, unassign)
|
|
460
|
-
// 6. Docs-AI (ask, ingest-url, ingest-file)
|
|
461
|
-
// 7. Attachments (attach-to-ticket)
|
|
462
|
-
// 8. Re-parenting (set-parent - symmetric to set_milestone)
|
|
463
310
|
reg('orboto_whoami', whoamiToolConfig, makeWhoamiHandler(client));
|
|
464
311
|
reg('orboto_claim', claimToolConfig, makeClaimHandler(client));
|
|
465
312
|
reg('orboto_unclaim', unclaimToolConfig, makeUnclaimHandler(client));
|
|
466
|
-
// ORB-1910 - feedback / bug report to the operator through the tenant relay.
|
|
467
313
|
reg('orboto_report_feedback', reportFeedbackToolConfig, makeReportFeedbackHandler(client));
|
|
468
|
-
// ORB-1223 - approval / sign-off gates.
|
|
469
314
|
reg('orboto_list_approvals', listApprovalsToolConfig, makeListApprovalsHandler(client));
|
|
470
315
|
reg('orboto_approval_decide', approvalDecideToolConfig, makeApprovalDecideHandler(client));
|
|
471
|
-
// ORB-1615 - structural review policy: fingerprint, consult-before-invoking
|
|
472
|
-
// policy check, and recording a review decision. Rule CONFIGURATION stays
|
|
473
|
-
// out of MCP (project-settings admin, same as approval-policy CRUD above).
|
|
474
316
|
reg('orboto_review_fingerprint', reviewFingerprintToolConfig, makeReviewFingerprintHandler(client));
|
|
475
317
|
reg('orboto_review_policy_check', reviewPolicyCheckToolConfig, makeReviewPolicyCheckHandler(client));
|
|
476
318
|
reg('orboto_review_approval_record', reviewApprovalRecordToolConfig, makeReviewApprovalRecordHandler(client));
|
|
@@ -483,8 +325,6 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
483
325
|
reg('orboto_bulk_patch_tickets', bulkPatchTicketsToolConfig, makeBulkPatchTicketsHandler(client));
|
|
484
326
|
reg('orboto_bulk_move_tickets', bulkMoveTicketsToolConfig, makeBulkMoveTicketsHandler(client));
|
|
485
327
|
reg('orboto_bulk_close_tickets', bulkCloseTicketsToolConfig, makeBulkCloseTicketsHandler(client));
|
|
486
|
-
// ORB-1694 - the two longest measured consecutive-call runs (create 49x,
|
|
487
|
-
// add_dependency 27x) collapse into one call each.
|
|
488
328
|
reg('orboto_bulk_create_tickets', bulkCreateTicketsToolConfig, makeBulkCreateTicketsHandler(client));
|
|
489
329
|
reg('orboto_bulk_add_ticket_dependencies', bulkAddTicketDependenciesToolConfig, makeBulkAddTicketDependenciesHandler(client));
|
|
490
330
|
reg('orboto_bulk_comment_tickets', bulkCommentTicketsToolConfig, makeBulkCommentTicketsHandler(client));
|
|
@@ -499,17 +339,12 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
499
339
|
reg('orboto_create_project', createProjectToolConfig, makeCreateProjectHandler(client));
|
|
500
340
|
reg('orboto_archive_project', archiveProjectToolConfig, makeArchiveProjectHandler(client));
|
|
501
341
|
reg('orboto_check_similar', checkSimilarToolConfig, makeCheckSimilarHandler(client));
|
|
502
|
-
// ORB-1609 - work sessions: the lease/timer/presence/evidence record.
|
|
503
342
|
reg('orboto_work_session_start', workSessionStartToolConfig, makeWorkSessionStartHandler(client));
|
|
504
|
-
// ORB-1611 - the one-call bundled pickup: acquire + full context bundle.
|
|
505
343
|
reg('orboto_work_start', workStartToolConfig, makeWorkStartHandler(client));
|
|
506
344
|
reg('orboto_work_session_finish', workSessionFinishToolConfig, makeWorkSessionFinishHandler(client));
|
|
507
|
-
// ORB-1612 - the one-call bundled exit: finish + ticket transition + note.
|
|
508
345
|
reg('orboto_work_finish', workFinishToolConfig, makeWorkFinishHandler(client));
|
|
509
|
-
// ORB-1613 - collision-aware dispatch pull for worker pools.
|
|
510
346
|
reg('orboto_work_next', workNextToolConfig, makeWorkNextHandler(client));
|
|
511
347
|
reg('orboto_work_sessions', workSessionsToolConfig, makeWorkSessionsHandler(client));
|
|
512
|
-
// ORB-1610 - resource claims: path globs + named exclusive resources.
|
|
513
348
|
reg('orboto_work_session_claims_add', workSessionClaimsAddToolConfig, makeWorkSessionClaimsAddHandler(client));
|
|
514
349
|
reg('orboto_work_session_claims_release', workSessionClaimsReleaseToolConfig, makeWorkSessionClaimsReleaseHandler(client));
|
|
515
350
|
reg('orboto_admin_translation_list', listAdminTranslationsToolConfig, makeListAdminTranslationsHandler(client));
|
|
@@ -517,23 +352,12 @@ export async function buildOrbotoMcpServer(opts) {
|
|
|
517
352
|
reg('orboto_admin_translation_revert', revertTranslationToolConfig, makeAdminRevertTranslationHandler(client));
|
|
518
353
|
reg('orboto_admin_agent_drift_list', listAgentDriftToolConfig, makeListAgentDriftHandler(client));
|
|
519
354
|
reg('orboto_admin_agent_drift_resolve', resolveAgentDriftToolConfig, makeResolveAgentDriftHandler(client));
|
|
520
|
-
// ORB-310 Phase D - read-only `orboto://…` URI resources +
|
|
521
|
-
// task-shaped Prompt templates the MCP client offers in its UI.
|
|
522
355
|
registerOrbotoResources(server, client);
|
|
523
356
|
registerOrbotoPrompts(server);
|
|
524
|
-
// ORB-940 - resources/subscribe + resources/unsubscribe. The Set
|
|
525
|
-
// is shared by reference with the transport's event-bridge, which
|
|
526
|
-
// is the actual sender of `notifications/resources/updated`. We
|
|
527
|
-
// resolve immediately with an empty result either way - the spec
|
|
528
|
-
// allows that, and the bridge does the real work.
|
|
529
357
|
if (opts.subscriptions) {
|
|
530
358
|
const subs = opts.subscriptions;
|
|
531
359
|
server.server.setRequestHandler(SubscribeRequestSchema, async (req) => {
|
|
532
360
|
subs.add(req.params.uri);
|
|
533
|
-
// ORB-940 follow-up - stderr-log every subscribe/unsubscribe so
|
|
534
|
-
// operators can see in the MCP container log whether the AI
|
|
535
|
-
// client actually opts into live updates (vs. just reading the
|
|
536
|
-
// resource once). Diagnostic only - never throw from here.
|
|
537
361
|
try {
|
|
538
362
|
process.stderr.write(`[orboto-mcp] subscribe → ${req.params.uri} (total subs: ${subs.size})\n`);
|
|
539
363
|
}
|
package/dist/session-nudge.js
CHANGED
|
@@ -52,15 +52,6 @@ export const SESSION_START_GATE_MESSAGE = 'This workspace requires you to load i
|
|
|
52
52
|
* Advance the gate state for one dispatch and report whether this
|
|
53
53
|
* dispatch must be REFUSED (returned an instructive error without running the
|
|
54
54
|
* handler).
|
|
55
|
-
*
|
|
56
|
-
* Semantics when the gate is enabled: every tool call other than
|
|
57
|
-
* `orboto_session_start` is refused until `orboto_session_start` has run once
|
|
58
|
-
* this session. A rule refresh locks the session until its result succeeds
|
|
59
|
-
* (and is itself never gated). When the gate is disabled, nothing is ever
|
|
60
|
-
* refused (returns false) - default behaviour is unchanged.
|
|
61
|
-
*
|
|
62
|
-
* The metrics wrapper records successful completion separately; dispatch
|
|
63
|
-
* alone is not evidence of rule delivery.
|
|
64
55
|
*/
|
|
65
56
|
export function shouldGate(state, toolName) {
|
|
66
57
|
if (toolName === SESSION_START_TOOL) {
|
|
@@ -11,7 +11,6 @@ describe('shouldNudge (ORB-1331)', () => {
|
|
|
11
11
|
it('nudges on the first tool call when it is not session_start, then never again', () => {
|
|
12
12
|
const state = createNudgeState();
|
|
13
13
|
expect(shouldNudge(state, 'orboto_list_projects')).toBe(true);
|
|
14
|
-
// Every subsequent call is clean regardless of tool.
|
|
15
14
|
expect(shouldNudge(state, 'orboto_get_ticket')).toBe(false);
|
|
16
15
|
expect(shouldNudge(state, 'orboto_list_projects')).toBe(false);
|
|
17
16
|
expect(shouldNudge(state, SESSION_START_TOOL)).toBe(false);
|
|
@@ -19,14 +18,12 @@ describe('shouldNudge (ORB-1331)', () => {
|
|
|
19
18
|
it('never nudges when the first call IS session_start', () => {
|
|
20
19
|
const state = createNudgeState();
|
|
21
20
|
expect(shouldNudge(state, SESSION_START_TOOL)).toBe(false);
|
|
22
|
-
// and does not resurface on later calls either.
|
|
23
21
|
expect(shouldNudge(state, 'orboto_list_projects')).toBe(false);
|
|
24
22
|
});
|
|
25
23
|
it('isolates state between sessions/processes (each fresh state nudges once)', () => {
|
|
26
24
|
const a = createNudgeState();
|
|
27
25
|
const b = createNudgeState();
|
|
28
26
|
expect(shouldNudge(a, 'orboto_list_projects')).toBe(true);
|
|
29
|
-
// b is untouched by a - its own first call still nudges.
|
|
30
27
|
expect(shouldNudge(b, 'orboto_list_projects')).toBe(true);
|
|
31
28
|
expect(shouldNudge(a, 'orboto_list_projects')).toBe(false);
|
|
32
29
|
expect(shouldNudge(b, 'orboto_list_projects')).toBe(false);
|
|
@@ -34,21 +31,18 @@ describe('shouldNudge (ORB-1331)', () => {
|
|
|
34
31
|
});
|
|
35
32
|
describe('shouldGate (ORB-1471)', () => {
|
|
36
33
|
it('never gates when the gate is disabled (default), whatever the tool', () => {
|
|
37
|
-
const state = createNudgeState();
|
|
34
|
+
const state = createNudgeState();
|
|
38
35
|
expect(shouldGate(state, 'orboto_list_projects')).toBe(false);
|
|
39
36
|
expect(shouldGate(state, 'orboto_create_ticket')).toBe(false);
|
|
40
37
|
expect(shouldGate(state, SESSION_START_TOOL)).toBe(false);
|
|
41
38
|
});
|
|
42
39
|
it('when enabled, refuses every non-session-start tool until session_start runs', () => {
|
|
43
40
|
const state = createNudgeState(true);
|
|
44
|
-
// Pre session_start: every other tool is gated.
|
|
45
41
|
expect(shouldGate(state, 'orboto_list_projects')).toBe(true);
|
|
46
42
|
expect(shouldGate(state, 'orboto_create_ticket')).toBe(true);
|
|
47
|
-
// Dispatch is not proof of delivery; only successful completion unlocks.
|
|
48
43
|
expect(shouldGate(state, SESSION_START_TOOL)).toBe(false);
|
|
49
44
|
expect(shouldGate(state, 'orboto_list_projects')).toBe(true);
|
|
50
45
|
recordSessionStartResult(state, SESSION_START_TOOL, true);
|
|
51
|
-
// Post session_start: everything passes.
|
|
52
46
|
expect(shouldGate(state, 'orboto_list_projects')).toBe(false);
|
|
53
47
|
expect(shouldGate(state, 'orboto_create_ticket')).toBe(false);
|
|
54
48
|
});
|
|
@@ -76,7 +70,7 @@ describe('SESSION_START_NUDGE text (ORB-1331)', () => {
|
|
|
76
70
|
it('is English, ASCII-only, and free of em/en-dashes', () => {
|
|
77
71
|
// eslint-disable-next-line no-control-regex
|
|
78
72
|
expect(/^[\x00-\x7F]*$/.test(SESSION_START_NUDGE)).toBe(true);
|
|
79
|
-
expect(SESSION_START_NUDGE).not.toMatch(/[\u2013\u2014]/);
|
|
73
|
+
expect(SESSION_START_NUDGE).not.toMatch(/[\u2013\u2014]/);
|
|
80
74
|
expect(SESSION_START_NUDGE).toContain('orboto_session_start');
|
|
81
75
|
});
|
|
82
76
|
});
|
|
@@ -90,9 +84,7 @@ describe('prependNudge (ORB-1331)', () => {
|
|
|
90
84
|
expect(out.content).toHaveLength(2);
|
|
91
85
|
expect(out.content[0]).toEqual({ type: 'text', text: SESSION_START_NUDGE });
|
|
92
86
|
expect(out.content[1]).toEqual({ type: 'text', text: 'tool output' });
|
|
93
|
-
// structuredContent consumers are unaffected - same reference-equal payload.
|
|
94
87
|
expect(out.structuredContent).toBe(original.structuredContent);
|
|
95
|
-
// input is not mutated.
|
|
96
88
|
expect(original.content).toHaveLength(1);
|
|
97
89
|
});
|
|
98
90
|
it('preserves isError on an error result', () => {
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-1331 - end-to-end transport proof of the session-start nudge.
|
|
3
|
-
*
|
|
4
|
-
* Drives the REAL server (buildOrbotoMcpServer → registerWithMetrics →
|
|
5
|
-
* withMetrics → prependNudge) over an in-memory MCP transport with a
|
|
6
|
-
* genuine MCP Client, so the whole JSON-RPC dispatch path is exercised,
|
|
7
|
-
* not just the helper in isolation.
|
|
8
|
-
*
|
|
9
|
-
* Transport semantics both reduce to the lifetime of the per-build nudge
|
|
10
|
-
* state:
|
|
11
|
-
* - HTTP: the transport calls buildOrbotoMcpServer once PER SESSION, so
|
|
12
|
-
* each session is an independent server instance with its own state.
|
|
13
|
-
* - stdio: buildOrbotoMcpServer is called once PER PROCESS, so a single
|
|
14
|
-
* server instance's state is process-local.
|
|
15
|
-
* We model a "session"/"process" as one built server + one connected
|
|
16
|
-
* client, and assert the nudge fires exactly once on the first non-
|
|
17
|
-
* session_start call and never when the first call is session_start.
|
|
18
3
|
*/
|
|
19
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
20
5
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
21
6
|
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
22
7
|
import { buildOrbotoMcpServer } from './server.js';
|
|
23
8
|
import { SESSION_START_NUDGE } from './session-nudge.js';
|
|
24
|
-
// Every API call the tools make resolves to a benign empty payload so
|
|
25
|
-
// the handlers succeed without a live backend. list_projects → [] →
|
|
26
|
-
// "no projects" text + valid structuredContent; session_start's sub-
|
|
27
|
-
// rules read is required and carries an explicit valid empty ruleset.
|
|
28
9
|
function mockApi() {
|
|
29
10
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (url) => ({
|
|
30
11
|
ok: true,
|
|
@@ -76,7 +57,6 @@ describe('ORB-1331 - HTTP transport (per-session nudge)', () => {
|
|
|
76
57
|
const client = await connectSession();
|
|
77
58
|
const first = await client.callTool({ name: 'orboto_list_projects', arguments: {} });
|
|
78
59
|
expect(firstText(first)).toContain(SESSION_START_NUDGE);
|
|
79
|
-
// structuredContent must be untouched by the text-block prepend.
|
|
80
60
|
expect(first.structuredContent).toMatchObject({ projects: [], total: 0 });
|
|
81
61
|
const second = await client.callTool({ name: 'orboto_list_projects', arguments: {} });
|
|
82
62
|
expect(firstText(second)).not.toContain(SESSION_START_NUDGE);
|
|
@@ -93,15 +73,12 @@ describe('ORB-1331 - HTTP transport (per-session nudge)', () => {
|
|
|
93
73
|
const b = await connectSession();
|
|
94
74
|
const aFirst = await a.callTool({ name: 'orboto_list_projects', arguments: {} });
|
|
95
75
|
expect(firstText(aFirst)).toContain(SESSION_START_NUDGE);
|
|
96
|
-
// b is a distinct built server → distinct state → still nudges.
|
|
97
76
|
const bFirst = await b.callTool({ name: 'orboto_list_projects', arguments: {} });
|
|
98
77
|
expect(firstText(bFirst)).toContain(SESSION_START_NUDGE);
|
|
99
78
|
});
|
|
100
79
|
});
|
|
101
80
|
describe('ORB-1331 - stdio transport (process-local nudge)', () => {
|
|
102
81
|
it('fires once for the process: first non-session_start call nudged, all later calls clean', async () => {
|
|
103
|
-
// stdio builds one server per process → one nudge state reused for
|
|
104
|
-
// every call on this single connected client.
|
|
105
82
|
const client = await connectSession();
|
|
106
83
|
const first = await client.callTool({ name: 'orboto_list_projects', arguments: {} });
|
|
107
84
|
expect(firstText(first)).toContain(SESSION_START_NUDGE);
|
|
@@ -1,37 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ORB-1669 - safety-annotation ratchet for the MCP tool surface.
|
|
3
|
-
*
|
|
4
|
-
* MCP clients use `annotations` to decide whether a tool call needs a
|
|
5
|
-
* confirmation step. The hints DEFAULT badly for us: per the MCP spec a
|
|
6
|
-
* tool with no annotations is read-only=false / destructive=true /
|
|
7
|
-
* idempotent=false - so an unannotated read looks dangerous, and (worse,
|
|
8
|
-
* because it is the silent direction) a write annotated only with
|
|
9
|
-
* `{ readOnlyHint: false }` still reports destructive=true. Before this
|
|
10
|
-
* ticket 90 of 168 tools carried nothing at all and `orboto_bulk_close`
|
|
11
|
-
* was indistinguishable from `orboto_get_ticket`.
|
|
12
|
-
*
|
|
13
|
-
* This test enumerates the tools exactly as a client sees them -
|
|
14
|
-
* `tools/list` over an in-memory transport, not a source scan - and
|
|
15
|
-
* fails the build on a new tool that skips its annotations. There is no
|
|
16
|
-
* allowlist on purpose: unlike the dark-mode / i18n ratchets there is no
|
|
17
|
-
* legitimate "sanctioned exception", every tool can answer these
|
|
18
|
-
* questions.
|
|
19
|
-
*
|
|
20
|
-
* THE CONVENTION (apply it when you add a tool):
|
|
21
|
-
*
|
|
22
|
-
* readOnlyHint: true - performs no writes at all.
|
|
23
|
-
* destructiveHint: true - the call can DELETE or REMOVE a row, DISCARD
|
|
24
|
-
* content, take over state owned by someone
|
|
25
|
-
* else, or mutate many tickets at once. If ANY
|
|
26
|
-
* argument combination can do that, the tool is
|
|
27
|
-
* destructive - the hint is per-tool and cannot
|
|
28
|
-
* be conditioned on a flag (this is why
|
|
29
|
-
* `orboto_claim` is destructive: `sole=true`
|
|
30
|
-
* strips every other assignee).
|
|
31
|
-
* destructiveHint: false- purely additive, or a reversible single-entity
|
|
32
|
-
* field/state update.
|
|
33
|
-
* idempotentHint: true - repeating the call with identical arguments
|
|
34
|
-
* has no further effect.
|
|
35
3
|
*/
|
|
36
4
|
import { describe, it, expect, beforeAll } from 'vitest';
|
|
37
5
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
@@ -48,11 +16,6 @@ function unsafeTakeoverTools(list) {
|
|
|
48
16
|
.map((tool) => tool.name);
|
|
49
17
|
}
|
|
50
18
|
beforeAll(async () => {
|
|
51
|
-
// Port 1 is never listened on -> the connect-time /agent-instructions
|
|
52
|
-
// fetch fails immediately and the server falls back to its built-in
|
|
53
|
-
// rules. No network, no fixture server, no timeout.
|
|
54
|
-
// ORB-1520 made the CURATED manifest the default; the annotation contract
|
|
55
|
-
// covers every registered tool, so this ratchet lists the FULL toolset.
|
|
56
19
|
const server = await buildOrbotoMcpServer({ baseUrl: 'http://127.0.0.1:1', apiKey: 'orb_test', toolset: 'full' });
|
|
57
20
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
58
21
|
const client = new Client({ name: 'annotation-ratchet', version: '0' });
|
|
@@ -62,8 +25,6 @@ beforeAll(async () => {
|
|
|
62
25
|
});
|
|
63
26
|
describe('MCP tool safety annotations (ORB-1669)', () => {
|
|
64
27
|
it('enumerates the full tool surface', () => {
|
|
65
|
-
// Guards the guard: if the in-memory handshake ever silently returns
|
|
66
|
-
// nothing, every expectation below would vacuously pass.
|
|
67
28
|
expect(tools.length).toBeGreaterThan(150);
|
|
68
29
|
});
|
|
69
30
|
it('every tool declares annotations with an explicit readOnlyHint', () => {
|
|
@@ -74,9 +35,6 @@ describe('MCP tool safety annotations (ORB-1669)', () => {
|
|
|
74
35
|
'Add an `annotations` block to the tool config in apps/mcp/src/tools/ - see the convention at the top of this file.').toEqual([]);
|
|
75
36
|
});
|
|
76
37
|
it('every write tool declares an explicit destructiveHint', () => {
|
|
77
|
-
// The load-bearing one. `{ readOnlyHint: false }` alone leaves
|
|
78
|
-
// destructiveHint defaulting to TRUE, so a harmless write advertises
|
|
79
|
-
// itself as destructive and the signal stops meaning anything.
|
|
80
38
|
const offenders = tools
|
|
81
39
|
.filter((t) => t.annotations?.readOnlyHint === false)
|
|
82
40
|
.filter((t) => typeof t.annotations?.destructiveHint !== 'boolean')
|
|
@@ -92,10 +50,8 @@ describe('MCP tool safety annotations (ORB-1669)', () => {
|
|
|
92
50
|
});
|
|
93
51
|
it('every takeover-capable tool is a destructive write for all argument combinations', () => {
|
|
94
52
|
const capable = tools.filter((tool) => Object.prototype.hasOwnProperty.call(tool.inputSchema?.properties ?? {}, 'takeover'));
|
|
95
|
-
// Pin the actual siblings so losing schema discovery cannot pass vacuously.
|
|
96
53
|
expect(capable.map((tool) => tool.name)).toEqual(expect.arrayContaining(['orboto_work_start', 'orboto_work_session_start']));
|
|
97
54
|
expect(unsafeTakeoverTools(tools)).toEqual([]);
|
|
98
|
-
// Reservation without a takeover path is not an account-state takeover.
|
|
99
55
|
expect(tools.find((tool) => tool.name === 'orboto_work_next')?.annotations).toMatchObject({ readOnlyHint: false, destructiveHint: false });
|
|
100
56
|
});
|
|
101
57
|
it('the takeover ratchet catches missing, false and contradictory hints on future tools', () => {
|