@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.
Files changed (131) hide show
  1. package/dist/event-bridge.js +1 -47
  2. package/dist/event-bridge.test.js +0 -19
  3. package/dist/http-transport-binding.test.js +1 -4
  4. package/dist/http-transport.js +1 -213
  5. package/dist/http-transport.test.js +0 -56
  6. package/dist/index.js +1 -48
  7. package/dist/input-schema.js +2 -76
  8. package/dist/input-schema.test.js +1 -15
  9. package/dist/instructions-budget.test.js +0 -2
  10. package/dist/manifest-size.test.js +3 -72
  11. package/dist/oauth-bootstrap.js +0 -47
  12. package/dist/oauth-bootstrap.test.js +4 -16
  13. package/dist/orboto-client.js +0 -30
  14. package/dist/orboto-client.test.js +0 -12
  15. package/dist/prompts.js +0 -39
  16. package/dist/protect-text-usage.test.js +1 -9
  17. package/dist/required-rules-wrapper.test.js +0 -5
  18. package/dist/required-rules.js +0 -2
  19. package/dist/resources.js +0 -62
  20. package/dist/resources.test.js +0 -16
  21. package/dist/response-budget.js +2 -150
  22. package/dist/response-budget.test.js +1 -32
  23. package/dist/server.js +4 -180
  24. package/dist/session-nudge.js +0 -9
  25. package/dist/session-nudge.test.js +2 -10
  26. package/dist/session-nudge.transport.test.js +0 -23
  27. package/dist/tool-annotations.test.js +0 -44
  28. package/dist/tool-docs.js +1 -12
  29. package/dist/tool-docs.test.js +2 -8
  30. package/dist/tool-parameters.test.js +0 -19
  31. package/dist/tools/absence-writes.js +1 -17
  32. package/dist/tools/admin-translations.js +0 -11
  33. package/dist/tools/admin-writes.js +0 -28
  34. package/dist/tools/admin-writes.test.js +0 -1
  35. package/dist/tools/agent-coordination.js +2 -24
  36. package/dist/tools/agent-coordination.test.js +0 -2
  37. package/dist/tools/agent-instructions.js +0 -10
  38. package/dist/tools/agent-instructions.test.js +0 -2
  39. package/dist/tools/agent-messages.js +0 -1
  40. package/dist/tools/ai-status.js +0 -14
  41. package/dist/tools/ai-usage.js +0 -10
  42. package/dist/tools/analytics.js +0 -2
  43. package/dist/tools/api-call.js +0 -12
  44. package/dist/tools/api-call.test.js +1 -2
  45. package/dist/tools/api-search.js +1 -6
  46. package/dist/tools/approvals.js +0 -6
  47. package/dist/tools/attach.js +0 -25
  48. package/dist/tools/backup.js +0 -13
  49. package/dist/tools/backup.test.js +1 -2
  50. package/dist/tools/bulk-create.js +1 -25
  51. package/dist/tools/bulk-create.test.js +0 -4
  52. package/dist/tools/bulk-writes.js +69 -61
  53. package/dist/tools/bulk-writes.test.js +34 -4
  54. package/dist/tools/check-similar.js +0 -17
  55. package/dist/tools/checklist-writes.js +82 -27
  56. package/dist/tools/checklist-writes.test.js +69 -1
  57. package/dist/tools/claim.js +1 -71
  58. package/dist/tools/claim.test.js +0 -2
  59. package/dist/tools/critical-path.js +0 -5
  60. package/dist/tools/critical-path.test.js +0 -3
  61. package/dist/tools/cross-project-links.ee.js +1 -24
  62. package/dist/tools/doc-attachments.js +0 -28
  63. package/dist/tools/doc-attachments.test.js +1 -4
  64. package/dist/tools/doc-comments.js +1 -37
  65. package/dist/tools/doc-comments.test.js +1 -2
  66. package/dist/tools/doc-edits.js +1 -31
  67. package/dist/tools/doc-edits.test.js +0 -9
  68. package/dist/tools/doc-export.js +0 -24
  69. package/dist/tools/doc-export.test.js +0 -12
  70. package/dist/tools/doc-revisions.js +0 -23
  71. package/dist/tools/docs-ai.js +0 -34
  72. package/dist/tools/docs-ai.test.js +0 -11
  73. package/dist/tools/docs.js +1 -75
  74. package/dist/tools/docs.test.js +1 -3
  75. package/dist/tools/embedding-status.js +0 -13
  76. package/dist/tools/embedding-status.test.js +0 -1
  77. package/dist/tools/feedback.js +1 -15
  78. package/dist/tools/feedback.test.js +0 -1
  79. package/dist/tools/get-checklists.js +6 -14
  80. package/dist/tools/get-project-primer.js +1 -25
  81. package/dist/tools/get-project.js +0 -17
  82. package/dist/tools/get-ticket.js +1 -61
  83. package/dist/tools/get-timer.js +0 -11
  84. package/dist/tools/git-health-reasons.js +0 -1
  85. package/dist/tools/identity.js +0 -13
  86. package/dist/tools/list-projects.js +0 -20
  87. package/dist/tools/list-projects.test.js +0 -1
  88. package/dist/tools/list-tickets.js +10 -9
  89. package/dist/tools/milestones-crud.test.js +0 -4
  90. package/dist/tools/milestones.js +0 -43
  91. package/dist/tools/my-tickets.js +0 -7
  92. package/dist/tools/phase-b-tools.test.js +14 -21
  93. package/dist/tools/primer-facts.js +1 -49
  94. package/dist/tools/primer-facts.test.js +0 -20
  95. package/dist/tools/project-listings.js +0 -25
  96. package/dist/tools/query.js +1 -12
  97. package/dist/tools/query.test.js +0 -8
  98. package/dist/tools/raci.js +0 -11
  99. package/dist/tools/requirements-spec.js +1 -12
  100. package/dist/tools/response-expand.js +0 -19
  101. package/dist/tools/review-policy.js +0 -24
  102. package/dist/tools/review-policy.test.js +0 -2
  103. package/dist/tools/search.js +1 -13
  104. package/dist/tools/session-start.js +1 -136
  105. package/dist/tools/session-start.test.js +1 -44
  106. package/dist/tools/set-parent.js +1 -19
  107. package/dist/tools/shared.js +0 -41
  108. package/dist/tools/shared.test.js +0 -4
  109. package/dist/tools/similar-projection.js +0 -15
  110. package/dist/tools/ticket-attachments.js +1 -25
  111. package/dist/tools/ticket-schedules.js +0 -9
  112. package/dist/tools/ticket-schedules.test.js +0 -1
  113. package/dist/tools/ticket-writes.js +1 -110
  114. package/dist/tools/ticket-writes.test.js +1 -15
  115. package/dist/tools/time-writes.js +26 -46
  116. package/dist/tools/time-writes.test.js +18 -0
  117. package/dist/tools/update-project.js +1 -27
  118. package/dist/tools/update-project.test.js +1 -1
  119. package/dist/tools/wiki.js +0 -8
  120. package/dist/tools/work-next.js +0 -5
  121. package/dist/tools/work-sessions-start.test.js +0 -2
  122. package/dist/tools/work-sessions.js +0 -48
  123. package/dist/tools/work-sessions.test.js +0 -5
  124. package/dist/toolset.js +2 -81
  125. package/dist/toolset.test.js +0 -20
  126. package/dist/version-guard.test.js +0 -4
  127. package/dist/version.js +0 -12
  128. package/dist/with-metrics-output-schema.test.js +1 -14
  129. package/dist/with-metrics.js +2 -74
  130. package/dist/with-metrics.test.js +7 -29
  131. package/package.json +1 -1
@@ -33,10 +33,6 @@ function formatApiError(err) {
33
33
  : 'Request rejected.';
34
34
  return `orboto API error ${err.status}. ${hint}\nDetail: ${detail || '(no message)'}`;
35
35
  }
36
- // ORB-1817 - McpLogEntry / redactSecrets / postLogEntry live in
37
- // mcp-instrument.ts (a leaf module) so input-schema.ts can log validation
38
- // failures through the same instrument path without an import cycle
39
- // (this file already imports FROM input-schema.ts).
40
36
  /**
41
37
  * Wrap a CallToolResult-returning handler so every invocation posts
42
38
  * one row to mcp_call_log via /admin/mcp/instrument.
@@ -46,27 +42,10 @@ function formatApiError(err) {
46
42
  * Wired centrally in `server.ts` so per-tool files don't need to
47
43
  * know about instrumentation.
48
44
  */
49
- export function withMetrics(client, toolName, clientHint,
50
- // ORB-1252 - forward the SDK's `extra` (RequestHandlerExtra) so handlers can
51
- // read the per-connection MCP sessionId (distinct per HTTP client even on a
52
- // shared server). Backward-compatible: handlers that ignore it are unaffected.
53
- handler,
54
- // ORB-1331 - per-session/-process nudge state. When present, the first
55
- // tool dispatch that is not `orboto_session_start` gets the one-time
56
- // reminder prepended to its response. Optional so direct callers/tests
57
- // that don't care are unaffected.
58
- nudge) {
45
+ export function withMetrics(client, toolName, clientHint, handler, nudge) {
59
46
  return async (args, extra) => {
60
47
  const start = Date.now();
61
- // ORB-1331 - decide (and advance the flag) once per dispatch, before
62
- // the handler runs, so the "first tool call" is the first dispatch
63
- // regardless of its outcome. Applied to the returned result below.
64
48
  const wantsNudge = nudge ? shouldNudge(nudge, toolName) : false;
65
- // ORB-1471 - HARD session-start gate. When the workspace requires it,
66
- // refuse every tool call until `orboto_session_start` succeeds. The gate message
67
- // supersedes the soft nudge, so we return it directly without running the
68
- // handler. Disabled by default => `shouldGate` returns false and nothing
69
- // below changes.
70
49
  if (nudge && shouldGate(nudge, toolName)) {
71
50
  const gated = gateResult();
72
51
  void postLogEntry(client, {
@@ -80,17 +59,8 @@ nudge) {
80
59
  }
81
60
  try {
82
61
  const handlerResult = await handler(args, extra);
83
- // ORB-1697 - the central response budget. Applied HERE, once, so it
84
- // covers every registered tool instead of relying on 168 handlers to
85
- // each stay small. Over-budget payloads come back truncated with an
86
- // explicit `__truncation` block + a handle for the remainder; the
87
- // measured sizes go into the call log either way.
88
62
  const budgeted = applyResponseBudget(toolName, handlerResult);
89
63
  const result = budgeted.result;
90
- // Success path - but the handler can also signal a "soft"
91
- // failure via { isError: true } in the result. We treat that
92
- // as success=false in the log so dashboards reflect actual
93
- // user-visible failures.
94
64
  const isError = result.isError === true;
95
65
  if (nudge)
96
66
  recordSessionStartResult(nudge, toolName, !isError);
@@ -105,11 +75,6 @@ nudge) {
105
75
  responseChars: budgeted.responseChars,
106
76
  truncatedChars: budgeted.truncatedChars,
107
77
  });
108
- // ORB-1727 - agent-mail piggyback: the client captured the api's
109
- // `x-orboto-agent-mail` header on the request this tool just made.
110
- // Append a compact pointer while mail is pending - zero bytes when
111
- // the inbox is empty, and never on the messages tool itself (the
112
- // caller is already fetching).
113
78
  const withMail = appendMailNudge(client, toolName, result);
114
79
  return wantsNudge ? prependNudge(withMail) : withMail;
115
80
  }
@@ -121,20 +86,12 @@ nudge) {
121
86
  void postLogEntry(client, { toolName, durationMs, success: false, statusCode: err.status, errorMessage: err.message, clientHint });
122
87
  return { isError: true, content: [{ type: 'text', text: err.message }], structuredContent: { errorKey: err.errorKey, reason: err.reason, ...(err.status ? { status: err.status } : {}) } };
123
88
  }
124
- // ORB-1174 - an OrbotoApiError carries a real HTTP status + message.
125
- // Return it as a structured isError result so the agent + human see
126
- // WHY (401 vs 404 vs 500) and can self-correct, instead of the
127
- // runtime's opaque "Error occurred during tool execution".
128
89
  if (err instanceof OrbotoApiError) {
129
90
  const text = formatApiError(err);
130
91
  void postLogEntry(client, { toolName, durationMs, success: false, statusCode: err.status, errorMessage: redactSecrets(text).slice(0, 500), clientHint });
131
- // Still surface the reminder alongside the actionable error so a
132
- // first-call failure doesn't swallow the one-time nudge.
133
92
  const errResult = { isError: true, content: [{ type: 'text', text }] };
134
93
  return wantsNudge ? prependNudge(errResult) : errResult;
135
94
  }
136
- // Anything else is unexpected (a bug, not an API rejection) - log
137
- // and re-throw so it surfaces loudly rather than being swallowed.
138
95
  void postLogEntry(client, {
139
96
  toolName,
140
97
  durationMs,
@@ -163,52 +120,23 @@ nudge) {
163
120
  export function appendMailNudge(client, toolName, result) {
164
121
  if (!client.pendingAgentMail || toolName === 'orboto_messages')
165
122
  return result;
166
- // ORB-1733: the nudge lives ONLY in a text content block. Never touch
167
- // structuredContent - the SDK validates it against the tool's declared
168
- // outputSchema, and an injected extra key fails validation and errors
169
- // the entire tool call for as long as mail sits unread.
170
123
  const line = `You have ${client.pendingAgentMail} unread agent message(s) - fetch them with orboto_messages.`;
171
124
  return {
172
125
  ...result,
173
126
  content: [...result.content, { type: 'text', text: line }],
174
127
  };
175
128
  }
176
- export function registerWithMetrics(server, client, clientHint,
177
- // ORB-1331 - shared per-session/-process nudge state. Every tool
178
- // registered through this closure reads/advances the same flag, so the
179
- // one-time session-start reminder fires on whichever tool is called
180
- // first (unless it is `orboto_session_start`).
181
- nudge) {
129
+ export function registerWithMetrics(server, client, clientHint, nudge) {
182
130
  return (toolName, config, handler) => {
183
- // ORB-1692 - every raw-shape input becomes strict + alias-resolving.
184
- // Central here so a new tool cannot ship permissive; empty/absent
185
- // shapes stay untouched (they carry no fields to guard).
186
131
  let cfg = config?.inputSchema && isRawShape(config.inputSchema)
187
- // ORB-1817 - `client` + `clientHint` let a validation failure log
188
- // through the same instrument path as a handler error (Part C):
189
- // the SDK validates BEFORE this handler wrapper ever runs, so this
190
- // is the only place that sees the failure.
191
132
  ? { ...config, inputSchema: buildStrictInputSchema(toolName, config.inputSchema, client, clientHint) }
192
133
  : config;
193
- // ORB-1738 - every declared output shape ADVERTISES the response
194
- // budget's optional __truncation marker. The SDK emits
195
- // additionalProperties:false for output schemas, and strict clients
196
- // validate structuredContent against that - an over-budget response
197
- // carrying the (undeclared) marker was rejected wholesale
198
- // (list_projects failed on EVERY call). Same central place as the
199
- // input fix so no tool can ship an outputSchema that fights the
200
- // budget layer.
201
134
  if (cfg?.outputSchema && isRawShape(cfg.outputSchema)) {
202
135
  cfg = {
203
136
  ...cfg,
204
- // ORB-1805 - the ADVERTISED (compact, open) form; the exact
205
- // shape stays TruncationBlockSchema for runtime assertions.
206
137
  outputSchema: { ...cfg.outputSchema, __truncation: TruncationBlockAdvertisedSchema.optional() },
207
138
  };
208
139
  }
209
- // ORB-1741 - manifest diet: the wire manifest carries a one-sentence
210
- // summary; the full guidance is captured for orboto_help. Central
211
- // here so a new tool cannot ship a manifest essay by accident.
212
140
  if (typeof cfg?.description === 'string' && cfg.description.length > 0) {
213
141
  captureToolDoc(toolName, cfg.description);
214
142
  cfg = { ...cfg, description: summarizeToolDescription(toolName, cfg.description) };
@@ -37,7 +37,6 @@ describe('withMetrics', () => {
37
37
  }));
38
38
  const result = await wrapped({});
39
39
  expect(result.content[0]).toEqual({ type: 'text', text: 'ok' });
40
- // Wait one microtask so the fire-and-forget POST resolves.
41
40
  await new Promise((r) => setImmediate(r));
42
41
  expect(calls).toHaveLength(1);
43
42
  expect(calls[0].url).toContain('/admin/mcp/instrument');
@@ -57,8 +56,6 @@ describe('withMetrics', () => {
57
56
  expect(body.success).toBe(false);
58
57
  expect(body.errorMessage).toBe('kaboom');
59
58
  });
60
- // ORB-1174 - an OrbotoApiError becomes a structured, actionable isError
61
- // result (not the runtime's opaque generic) so the agent can self-correct.
62
59
  it('maps OrbotoApiError to a distinct, actionable isError result per status', async () => {
63
60
  captureFetch();
64
61
  const run = async (status, body) => {
@@ -72,14 +69,13 @@ describe('withMetrics', () => {
72
69
  const unauth = await run(401, '{"error":"Invalid API key"}');
73
70
  expect(unauth).toContain('401');
74
71
  expect(unauth).toMatch(/re-?authenticate/i);
75
- expect(unauth).toContain('Invalid API key'); // the API's own message
72
+ expect(unauth).toContain('Invalid API key');
76
73
  const notFound = await run(404, '{"error":"Ticket not found"}');
77
74
  expect(notFound).toContain('404');
78
75
  expect(notFound).toMatch(/not found/i);
79
76
  const server = await run(503, 'upstream down');
80
77
  expect(server).toContain('503');
81
78
  expect(server).toMatch(/retry/i);
82
- // distinct messages, not the same opaque blob
83
79
  expect(unauth).not.toBe(notFound);
84
80
  expect(notFound).not.toBe(server);
85
81
  });
@@ -107,10 +103,6 @@ describe('withMetrics', () => {
107
103
  expect(body.clientHint).toBe('cursor');
108
104
  });
109
105
  it('does not block when the instrument POST fails - handler still resolves', async () => {
110
- // First call (the handler-as-fetch?) returns ok; but our handler
111
- // doesn't fetch; the only fetch the wrapper makes is to /instrument.
112
- // Mock that one to fail - caller must still get the original
113
- // result back.
114
106
  vi.spyOn(globalThis, 'fetch').mockImplementation(async () => ({
115
107
  ok: false, status: 500, statusText: 'Server Error',
116
108
  json: async () => ({ error: 'kaboom' }),
@@ -122,8 +114,6 @@ describe('withMetrics', () => {
122
114
  const result = await wrapped({});
123
115
  expect(result.content[0]).toEqual({ type: 'text', text: 'survives' });
124
116
  });
125
- // ORB-1180 - admin-panel visibility: the failure log carries the
126
- // structured HTTP status, and any secret-shaped text is redacted.
127
117
  it('logs the structured statusCode on an OrbotoApiError', async () => {
128
118
  const calls = captureFetch();
129
119
  const wrapped = withMetrics(client, 'orboto_x', undefined, async () => {
@@ -136,10 +126,6 @@ describe('withMetrics', () => {
136
126
  expect(body.statusCode).toBe(403);
137
127
  expect(body.errorMessage).toContain('403');
138
128
  });
139
- // ORB-1331 - the shared nudge state threads through the wrapper: the
140
- // first non-session_start dispatch carries the one-time reminder,
141
- // later dispatches are clean, and a session_start-first flow never
142
- // sees it. structuredContent is left intact.
143
129
  it('prepends the session-start nudge on the first non-session_start dispatch, once', async () => {
144
130
  captureFetch();
145
131
  const nudge = createNudgeState();
@@ -151,9 +137,9 @@ describe('withMetrics', () => {
151
137
  const first = await list({});
152
138
  expect(first.content[0].text).toBe(SESSION_START_NUDGE);
153
139
  expect(first.content[1].text).toBe('ok');
154
- expect(first.structuredContent).toEqual({ a: 1 }); // untouched
140
+ expect(first.structuredContent).toEqual({ a: 1 });
155
141
  const second = await list({});
156
- expect(second.content[0].text).toBe('ok'); // clean
142
+ expect(second.content[0].text).toBe('ok');
157
143
  });
158
144
  it('does not nudge when the first dispatch IS orboto_session_start', async () => {
159
145
  captureFetch();
@@ -162,9 +148,9 @@ describe('withMetrics', () => {
162
148
  const start = withMetrics(client, 'orboto_session_start', undefined, handler, nudge);
163
149
  const list = withMetrics(client, 'orboto_list_projects', undefined, handler, nudge);
164
150
  const first = await start({});
165
- expect(first.content[0].text).toBe('rules'); // no nudge
151
+ expect(first.content[0].text).toBe('rules');
166
152
  const second = await list({});
167
- expect(second.content[0].text).toBe('rules'); // still clean
153
+ expect(second.content[0].text).toBe('rules');
168
154
  });
169
155
  it('surfaces the nudge alongside an OrbotoApiError on a first-call failure', async () => {
170
156
  captureFetch();
@@ -177,18 +163,13 @@ describe('withMetrics', () => {
177
163
  expect(res.content[0].text).toBe(SESSION_START_NUDGE);
178
164
  expect(res.content[1].text).toContain('403');
179
165
  });
180
- // ORB-1471 - the HARD session-start gate. When the workspace requires it,
181
- // the wrapper REFUSES a non-session-start tool without running its handler,
182
- // returns the instructive gate message, and logs the refusal. A
183
- // session_start call unlocks the session; after that everything runs.
184
166
  it('gates a non-session-start tool until session_start runs, without invoking the handler', async () => {
185
167
  const calls = captureFetch();
186
- const nudge = createNudgeState(true); // gate ON
168
+ const nudge = createNudgeState(true);
187
169
  const listHandler = vi.fn(async () => ({ content: [{ type: 'text', text: 'projects' }] }));
188
170
  const startHandler = vi.fn(async () => ({ content: [{ type: 'text', text: 'rules' }] }));
189
171
  const list = withMetrics(client, 'orboto_list_projects', undefined, listHandler, nudge);
190
172
  const start = withMetrics(client, 'orboto_session_start', undefined, startHandler, nudge);
191
- // First call is refused - handler never runs.
192
173
  const refused = await list({});
193
174
  expect(refused.isError).toBe(true);
194
175
  expect(refused.content[0].text).toBe(SESSION_START_GATE_MESSAGE);
@@ -196,22 +177,19 @@ describe('withMetrics', () => {
196
177
  await new Promise((r) => setImmediate(r));
197
178
  const gateLog = calls.find((c) => c.body?.errorMessage?.includes('session-start gate'));
198
179
  expect(gateLog).toBeDefined();
199
- // Running session_start unlocks the session.
200
180
  const rules = await start({});
201
181
  expect(rules.content[0].text).toBe('rules');
202
182
  expect(startHandler).toHaveBeenCalledTimes(1);
203
- // Now the previously-gated tool runs normally.
204
183
  const ok = await list({});
205
184
  expect(ok.content[0].text).toBe('projects');
206
185
  expect(listHandler).toHaveBeenCalledTimes(1);
207
186
  });
208
187
  it('does not gate anything when the gate is disabled (default)', async () => {
209
188
  captureFetch();
210
- const nudge = createNudgeState(); // gate OFF
189
+ const nudge = createNudgeState();
211
190
  const handler = vi.fn(async () => ({ content: [{ type: 'text', text: 'ok' }] }));
212
191
  const list = withMetrics(client, 'orboto_list_projects', undefined, handler, nudge);
213
192
  const res = await list({});
214
- // Still nudged (soft), but NOT refused - the handler ran.
215
193
  expect(res.content[1].text).toBe('ok');
216
194
  expect(handler).toHaveBeenCalledTimes(1);
217
195
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orboto/mcp",
3
- "version": "0.192.0",
3
+ "version": "0.194.0",
4
4
  "description": "Model Context Protocol server for orboto - exposes tickets, projects, milestones, time, docs, primer-facts, alerts and absences as MCP tools for Claude Desktop / Cursor / Copilot Chat / any MCP-aware AI client.",
5
5
  "license": "MIT",
6
6
  "type": "module",