@orboto/mcp 0.182.4 → 0.183.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.
|
@@ -55,7 +55,14 @@ afterEach(() => { vi.restoreAllMocks(); });
|
|
|
55
55
|
* schema to the minimum (one describe() on `page`); ceiling re-pinned to
|
|
56
56
|
* 32,500 (~6 % headroom), deliberately, for that one addition.
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
// ORB-1959 - re-pinned at 37,500 (measured 36,714): the four agent
|
|
59
|
+
// coordination tools (messages, notify, broadcast, heartbeat) joined the
|
|
60
|
+
// curated tier so an agent on the DEFAULT connection can fetch, act on and
|
|
61
|
+
// ack its mail - the binding workspace rule the old manifest made impossible
|
|
62
|
+
// to follow. Their descriptions were cut to the load-bearing facts first
|
|
63
|
+
// (286 chars); the remaining ~4.2k chars are input/output schemas and the
|
|
64
|
+
// per-tool keys the SDK emits. Decision by the operator, 2026-09-06.
|
|
65
|
+
const CURATED_MAX_CHARS = 37_500;
|
|
59
66
|
const FULL_MAX_CHARS = 155_000;
|
|
60
67
|
/**
|
|
61
68
|
* ORB-1805 - the estimator the ticket measured the failure with
|
|
@@ -91,7 +98,7 @@ const MINIMAL_MAX_TOKENS = 3_100;
|
|
|
91
98
|
*/
|
|
92
99
|
// ORB-1910 - re-pinned with CURATED_MAX_CHARS (32,500 / 3.6): the curated set
|
|
93
100
|
// gained orboto_report_feedback; measured 8,514 estimated tokens.
|
|
94
|
-
const CURATED_SCHEMA_MAX_TOKENS =
|
|
101
|
+
const CURATED_SCHEMA_MAX_TOKENS = 10_450; // ORB-1959: measured 10,198 with the coordination tools
|
|
95
102
|
async function measuredManifest(toolset) {
|
|
96
103
|
const server = await buildOrbotoMcpServer({
|
|
97
104
|
baseUrl: 'https://orboto.example.com',
|
|
@@ -129,8 +136,9 @@ describe('ORB-1521 - eager-load manifest size', () => {
|
|
|
129
136
|
// eslint-disable-next-line no-console
|
|
130
137
|
console.log(`[manifest-size] curated: ${count} tools, ${chars} chars (~${estTokens(chars)} tokens)`);
|
|
131
138
|
// 31 = the ORB-1741-dieted set + the two ORB-1694 bulk tools (each
|
|
132
|
-
// REPLACES dozens of per-call responses, so they earn their slot)
|
|
133
|
-
|
|
139
|
+
// REPLACES dozens of per-call responses, so they earn their slot);
|
|
140
|
+
// ORB-1910 added report_feedback, ORB-1959 the four coordination tools.
|
|
141
|
+
expect(count).toBeLessThanOrEqual(36);
|
|
134
142
|
expect(chars).toBeLessThanOrEqual(CURATED_MAX_CHARS);
|
|
135
143
|
expect(estTokens(chars)).toBeLessThanOrEqual(CURATED_SCHEMA_MAX_TOKENS);
|
|
136
144
|
});
|
|
@@ -17,7 +17,7 @@ import { z } from 'zod';
|
|
|
17
17
|
import { mcpInstanceToken } from './shared.js';
|
|
18
18
|
export const agentHeartbeatToolConfig = {
|
|
19
19
|
title: 'Agent heartbeat (Multi-Agent Coordination)',
|
|
20
|
-
description: 'Register or refresh this agent\'s presence
|
|
20
|
+
description: 'Register or refresh this agent\'s presence. Call on startup and every ~30 s; rows older than 90 s count as offline. Persist the returned sessionToken and send it on later heartbeats. status: idle (default) | working (+workingOnTicketId) | blocked. capabilities: free-form strings for operator filters. clientInfo.name = the runtime (claude-code, cursor, ...).',
|
|
21
21
|
inputSchema: z.object({
|
|
22
22
|
sessionToken: z.string().nullable().optional(),
|
|
23
23
|
status: z.enum(['idle', 'working', 'blocked']).optional(),
|
|
@@ -96,7 +96,7 @@ export function makeAgentPresenceHandler(client) {
|
|
|
96
96
|
}
|
|
97
97
|
export const agentNotifyToolConfig = {
|
|
98
98
|
title: 'Notify another agent / user',
|
|
99
|
-
description: '
|
|
99
|
+
description: 'Send a fire-and-forget message to a user (bot or human) by email; it lands in their inbox (orboto_messages) and, for humans, in-app. kind: info | request (answer expected) | complete (sub-task done) | error. payload: free-form JSON; threadId links a reply to the message it answers.',
|
|
100
100
|
inputSchema: z.object({
|
|
101
101
|
targetEmail: z.string().email(),
|
|
102
102
|
kind: z.enum(['info', 'request', 'complete', 'error']).default('info'),
|
|
@@ -106,10 +106,10 @@ export const agentNotifyToolConfig = {
|
|
|
106
106
|
threadId: z.string().uuid().optional(),
|
|
107
107
|
// ORB-1732 - optional project scope: address "the agent working project
|
|
108
108
|
// X" when the recipient identity runs multiple sessions.
|
|
109
|
-
project: z.string().min(1).max(64).optional().describe('Project key
|
|
109
|
+
project: z.string().min(1).max(64).optional().describe('Project key or UUID: scope the message to the recipient session working that project.'),
|
|
110
110
|
// ORB-1742 - defaults to this MCP session's instance token so a shared
|
|
111
111
|
// identity never wakes itself with its own outbound mail.
|
|
112
|
-
senderRef: z.string().min(1).max(128).optional().describe('Sender-session ref for self-echo exclusion
|
|
112
|
+
senderRef: z.string().min(1).max(128).optional().describe('Sender-session ref for self-echo exclusion; defaults to this MCP session.'),
|
|
113
113
|
}).shape,
|
|
114
114
|
outputSchema: z.object({
|
|
115
115
|
ok: z.literal(true),
|
|
@@ -122,7 +122,7 @@ export const agentNotifyToolConfig = {
|
|
|
122
122
|
// ---------------------------------------------------------------------------
|
|
123
123
|
export const agentBroadcastToolConfig = {
|
|
124
124
|
title: 'Scoped broadcast to other agents',
|
|
125
|
-
description: 'Fan-out
|
|
125
|
+
description: 'Fan-out to every agent in a scope: workspace (all internal members), project (members of scopeId = project UUID) or topic (any string). Each recipient gets an inbox copy (orboto_messages, ackable); live subscribers also get a resources/updated push on orboto://broadcast/<scope>/<scope_id>. The scope replay keeps agent_broadcast_retention_days (default 7).',
|
|
126
126
|
inputSchema: z.object({
|
|
127
127
|
scopeType: z.enum(['workspace', 'project', 'topic']),
|
|
128
128
|
scopeId: z.string().default(''),
|
|
@@ -11,13 +11,13 @@ import { z } from 'zod';
|
|
|
11
11
|
import { mcpInstanceToken } from './shared.js';
|
|
12
12
|
export const agentMessagesToolConfig = {
|
|
13
13
|
title: 'Fetch / acknowledge your agent inbox',
|
|
14
|
-
description: '
|
|
14
|
+
description: 'Your store-and-forward inbox: messages other agents sent you plus broadcasts to your scopes (payload.broadcast names the scope). Default = unread, marked delivered on fetch; ackIds marks read. Reply via orboto_agent_notify with threadId = the message id.',
|
|
15
15
|
inputSchema: z.object({
|
|
16
16
|
all: z.boolean().default(false).describe('true = include already-read messages'),
|
|
17
17
|
limit: z.number().int().min(1).max(200).default(50),
|
|
18
|
-
project: z.string().min(1).max(64).optional().describe('Project key or UUID:
|
|
18
|
+
project: z.string().min(1).max(64).optional().describe('Project key or UUID: scoped messages for this project plus unscoped ones. Ack only messages that are yours.'),
|
|
19
19
|
// ORB-1742 - self-echo exclusion, on by default for MCP sessions.
|
|
20
|
-
includeOwnSends: z.boolean().default(false).describe('true =
|
|
20
|
+
includeOwnSends: z.boolean().default(false).describe('true = also list messages this session sent (hidden by default so a shared identity never wakes itself).'),
|
|
21
21
|
ackIds: z.array(z.string().uuid()).max(200).optional().describe('Message ids to mark as read'),
|
|
22
22
|
}).shape,
|
|
23
23
|
outputSchema: z.object({
|
package/dist/tools/backup.js
CHANGED
|
@@ -14,7 +14,7 @@ const mb = (n) => `${Math.round((n / 1024 / 1024) * 10) / 10} MB`;
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
export const createFullBackupToolConfig = {
|
|
16
16
|
title: 'Create + download a full workspace backup',
|
|
17
|
-
description: 'Create an on-demand FULL workspace backup (database + storage) and return the ZIP as a base64 application/zip resource attachment - create, wait and download in one call (the server runs the export as a background run and this tool polls it; large workspaces can take minutes). Requires admin:backup:export. For the scheduled named jobs use orboto_trigger_backup instead.',
|
|
17
|
+
description: 'Create an on-demand FULL workspace backup (database + storage) and return the ZIP as a base64 application/zip resource attachment - create, wait and download in one call (the server runs the export as a background run and this tool polls it; large workspaces can take minutes). Rows and file references share one snapshot; missing or incomplete files fail the run, never return a partial ZIP. Requires admin:backup:export. For the scheduled named jobs use orboto_trigger_backup instead.',
|
|
18
18
|
inputSchema: z.object({}).shape,
|
|
19
19
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
20
20
|
};
|
|
@@ -68,20 +68,22 @@ describe('orboto_create_full_backup (ORB-1301 / async since ORB-1717)', () => {
|
|
|
68
68
|
vi.useRealTimers();
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
-
it('a failed run surfaces
|
|
71
|
+
it.each(['disk full', 'Backup source object is missing, unreadable or incomplete'])('a failed run surfaces %s instead of a partial ZIP', async (message) => {
|
|
72
72
|
vi.useFakeTimers();
|
|
73
|
+
const download = vi.spyOn(client, 'getBinary');
|
|
73
74
|
vi.spyOn(globalThis, 'fetch').mockImplementation(async (url, _init) => {
|
|
74
75
|
const u = url.toString();
|
|
75
76
|
if (u.endsWith('/admin/backup/full')) {
|
|
76
77
|
return { ok: true, status: 202, statusText: 'Accepted', headers: new Headers({ 'content-type': 'application/json' }), json: async () => ({ runId: 'run-2' }), text: async () => '' };
|
|
77
78
|
}
|
|
78
|
-
return { ok: true, status: 200, statusText: 'OK', headers: new Headers({ 'content-type': 'application/json' }), json: async () => ({ status: 'failed', errorMessage:
|
|
79
|
+
return { ok: true, status: 200, statusText: 'OK', headers: new Headers({ 'content-type': 'application/json' }), json: async () => ({ status: 'failed', errorMessage: message }), text: async () => '' };
|
|
79
80
|
});
|
|
80
81
|
try {
|
|
81
82
|
const pending = makeCreateFullBackupHandler(client)();
|
|
82
|
-
const assertion = expect(pending).rejects.toThrow(
|
|
83
|
+
const assertion = expect(pending).rejects.toThrow(message);
|
|
83
84
|
await vi.advanceTimersByTimeAsync(6_000);
|
|
84
85
|
await assertion;
|
|
86
|
+
expect(download).not.toHaveBeenCalled();
|
|
85
87
|
}
|
|
86
88
|
finally {
|
|
87
89
|
vi.useRealTimers();
|
package/dist/toolset.js
CHANGED
|
@@ -81,7 +81,10 @@ export const MINIMAL_TOOLS = new Set([
|
|
|
81
81
|
'orboto_close_ticket',
|
|
82
82
|
'orboto_timer_start',
|
|
83
83
|
'orboto_timer_stop',
|
|
84
|
-
// Everything else, on demand
|
|
84
|
+
// Everything else, on demand (ORB-1959 measured orboto_messages at ~3k
|
|
85
|
+
// chars - a fifth of this tier's whole window - so the inbox stays in
|
|
86
|
+
// curated, where the default connection lives; minimal is the explicit
|
|
87
|
+
// small-model opt-in and reaches the inbox through orboto_api_call).
|
|
85
88
|
'orboto_api_search',
|
|
86
89
|
'orboto_api_call',
|
|
87
90
|
]);
|
|
@@ -118,6 +121,17 @@ export const CURATED_TOOLS = new Set([
|
|
|
118
121
|
'orboto_timer_stop', // 772
|
|
119
122
|
'orboto_log_time', // 130
|
|
120
123
|
'orboto_get_timer', // 43
|
|
124
|
+
// ORB-1959 - agent coordination on the DEFAULT connection. Without
|
|
125
|
+
// these four an agent on curated could neither receive nor acknowledge
|
|
126
|
+
// mail (broadcasts included, they land in the inbox since ORB-1959),
|
|
127
|
+
// announce itself (heartbeat / presence) nor reach another agent - the
|
|
128
|
+
// workspace rules demand exactly that, and every fresh `mcp add` forgot
|
|
129
|
+
// the ?toolset=full workaround. Four small schemas; measured cost in
|
|
130
|
+
// manifest-size.test.ts.
|
|
131
|
+
'orboto_messages',
|
|
132
|
+
'orboto_agent_notify',
|
|
133
|
+
'orboto_agent_broadcast',
|
|
134
|
+
'orboto_agent_heartbeat',
|
|
121
135
|
// Transport plumbing + the escape hatch itself
|
|
122
136
|
'orboto_response_expand',
|
|
123
137
|
// ORB-1741 - the way back from one-line manifest summaries: full
|
package/dist/toolset.test.js
CHANGED
|
@@ -54,6 +54,13 @@ describe('toolInToolset', () => {
|
|
|
54
54
|
expect(toolInToolset('orboto_claim', 'curated')).toBe(true);
|
|
55
55
|
expect(toolInToolset('orboto_api_call', 'curated')).toBe(true);
|
|
56
56
|
});
|
|
57
|
+
// ORB-1959 - coordination is part of the default connection, not an opt-in.
|
|
58
|
+
it('curated carries the four agent-coordination tools; minimal stays the 12-tool loop', () => {
|
|
59
|
+
for (const name of ['orboto_messages', 'orboto_agent_notify', 'orboto_agent_broadcast', 'orboto_agent_heartbeat']) {
|
|
60
|
+
expect(toolInToolset(name, 'curated'), name).toBe(true);
|
|
61
|
+
expect(toolInToolset(name, 'minimal'), name).toBe(false);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
57
64
|
it('minimal admits only the daily loop + the escape hatch', () => {
|
|
58
65
|
expect(toolInToolset('orboto_claim', 'minimal')).toBe(true);
|
|
59
66
|
expect(toolInToolset('orboto_api_search', 'minimal')).toBe(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orboto/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.183.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",
|