@orboto/mcp 0.178.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +123 -0
- package/dist/event-bridge.js +250 -0
- package/dist/event-bridge.test.js +165 -0
- package/dist/http-transport-binding.test.js +151 -0
- package/dist/http-transport.js +608 -0
- package/dist/http-transport.test.js +408 -0
- package/dist/index.js +128 -0
- package/dist/input-schema.js +293 -0
- package/dist/input-schema.test.js +198 -0
- package/dist/instructions-budget.test.js +34 -0
- package/dist/manifest-size.test.js +192 -0
- package/dist/mcp-instrument.js +24 -0
- package/dist/oauth-bootstrap.js +413 -0
- package/dist/oauth-bootstrap.test.js +212 -0
- package/dist/orboto-client.js +250 -0
- package/dist/orboto-client.test.js +141 -0
- package/dist/prompts.js +219 -0
- package/dist/prompts.test.js +79 -0
- package/dist/protect-text-usage.test.js +38 -0
- package/dist/resources.js +260 -0
- package/dist/resources.test.js +191 -0
- package/dist/response-budget.js +515 -0
- package/dist/response-budget.test.js +343 -0
- package/dist/server.js +547 -0
- package/dist/session-nudge.js +81 -0
- package/dist/session-nudge.test.js +100 -0
- package/dist/session-nudge.transport.test.js +100 -0
- package/dist/tool-annotations.test.js +97 -0
- package/dist/tool-docs.js +64 -0
- package/dist/tool-docs.test.js +90 -0
- package/dist/tool-parameters.test.js +145 -0
- package/dist/tools/absence-writes.js +93 -0
- package/dist/tools/absence-writes.test.js +106 -0
- package/dist/tools/admin-translations.js +83 -0
- package/dist/tools/admin-writes.js +174 -0
- package/dist/tools/admin-writes.test.js +120 -0
- package/dist/tools/agent-coordination.js +160 -0
- package/dist/tools/agent-coordination.test.js +156 -0
- package/dist/tools/agent-drift.js +79 -0
- package/dist/tools/agent-drift.test.js +87 -0
- package/dist/tools/agent-instructions.js +172 -0
- package/dist/tools/agent-instructions.test.js +124 -0
- package/dist/tools/agent-messages.js +75 -0
- package/dist/tools/ai-status.js +55 -0
- package/dist/tools/ai-status.test.js +43 -0
- package/dist/tools/ai-usage.js +68 -0
- package/dist/tools/analytics.js +72 -0
- package/dist/tools/analytics.test.js +71 -0
- package/dist/tools/api-call.js +72 -0
- package/dist/tools/api-call.test.js +104 -0
- package/dist/tools/api-search.js +77 -0
- package/dist/tools/api-search.test.js +90 -0
- package/dist/tools/approvals.js +68 -0
- package/dist/tools/approvals.test.js +48 -0
- package/dist/tools/attach.js +108 -0
- package/dist/tools/attach.test.js +117 -0
- package/dist/tools/backup.js +93 -0
- package/dist/tools/backup.test.js +108 -0
- package/dist/tools/bulk-create.js +213 -0
- package/dist/tools/bulk-create.test.js +135 -0
- package/dist/tools/bulk-writes.js +339 -0
- package/dist/tools/bulk-writes.test.js +239 -0
- package/dist/tools/check-similar.js +84 -0
- package/dist/tools/check-similar.test.js +101 -0
- package/dist/tools/checklist-writes.js +202 -0
- package/dist/tools/checklist-writes.test.js +184 -0
- package/dist/tools/claim.js +236 -0
- package/dist/tools/claim.test.js +207 -0
- package/dist/tools/critical-path.js +83 -0
- package/dist/tools/critical-path.test.js +114 -0
- package/dist/tools/cross-project-links.ee.js +124 -0
- package/dist/tools/cross-project-links.ee.test.js +163 -0
- package/dist/tools/customer-report.js +88 -0
- package/dist/tools/customer-report.test.js +55 -0
- package/dist/tools/doc-attachments.js +184 -0
- package/dist/tools/doc-attachments.test.js +136 -0
- package/dist/tools/doc-comments.js +228 -0
- package/dist/tools/doc-comments.test.js +192 -0
- package/dist/tools/doc-edits.js +229 -0
- package/dist/tools/doc-edits.test.js +218 -0
- package/dist/tools/doc-export.js +87 -0
- package/dist/tools/doc-export.test.js +109 -0
- package/dist/tools/doc-revisions.js +133 -0
- package/dist/tools/doc-revisions.test.js +103 -0
- package/dist/tools/docs-ai.js +171 -0
- package/dist/tools/docs-ai.test.js +160 -0
- package/dist/tools/docs.js +557 -0
- package/dist/tools/docs.test.js +365 -0
- package/dist/tools/embedding-status.js +100 -0
- package/dist/tools/embedding-status.test.js +86 -0
- package/dist/tools/free-busy.js +49 -0
- package/dist/tools/free-busy.test.js +46 -0
- package/dist/tools/get-checklists.js +72 -0
- package/dist/tools/get-project-primer.js +58 -0
- package/dist/tools/get-project.js +77 -0
- package/dist/tools/get-ticket.js +321 -0
- package/dist/tools/get-timer.js +62 -0
- package/dist/tools/help.js +44 -0
- package/dist/tools/identity.js +60 -0
- package/dist/tools/identity.test.js +67 -0
- package/dist/tools/list-git-app-installations.js +45 -0
- package/dist/tools/list-projects.js +72 -0
- package/dist/tools/list-projects.test.js +67 -0
- package/dist/tools/list-tickets.js +86 -0
- package/dist/tools/milestones-crud.test.js +154 -0
- package/dist/tools/milestones.js +279 -0
- package/dist/tools/milestones.test.js +60 -0
- package/dist/tools/my-tickets.js +65 -0
- package/dist/tools/personal-facts.js +72 -0
- package/dist/tools/personal-facts.test.js +45 -0
- package/dist/tools/phase-b-tools.test.js +530 -0
- package/dist/tools/portfolio.js +56 -0
- package/dist/tools/primer-facts.js +346 -0
- package/dist/tools/primer-facts.test.js +353 -0
- package/dist/tools/project-listings.js +116 -0
- package/dist/tools/project-listings.test.js +106 -0
- package/dist/tools/query.js +65 -0
- package/dist/tools/query.test.js +128 -0
- package/dist/tools/raci.js +108 -0
- package/dist/tools/raci.test.js +70 -0
- package/dist/tools/requirements-spec.js +89 -0
- package/dist/tools/requirements-spec.test.js +62 -0
- package/dist/tools/response-expand.js +104 -0
- package/dist/tools/review-policy.js +105 -0
- package/dist/tools/review-policy.test.js +71 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/session-start.js +435 -0
- package/dist/tools/session-start.test.js +568 -0
- package/dist/tools/set-parent.js +76 -0
- package/dist/tools/set-parent.test.js +115 -0
- package/dist/tools/shared.js +226 -0
- package/dist/tools/shared.test.js +169 -0
- package/dist/tools/similar-projection.js +30 -0
- package/dist/tools/ticket-attachments.js +131 -0
- package/dist/tools/ticket-attachments.test.js +106 -0
- package/dist/tools/ticket-schedules.js +112 -0
- package/dist/tools/ticket-schedules.test.js +102 -0
- package/dist/tools/ticket-writes.js +778 -0
- package/dist/tools/ticket-writes.test.js +643 -0
- package/dist/tools/time-writes.js +238 -0
- package/dist/tools/time-writes.test.js +138 -0
- package/dist/tools/update-project.js +165 -0
- package/dist/tools/update-project.test.js +172 -0
- package/dist/tools/wiki.js +185 -0
- package/dist/tools/wiki.test.js +104 -0
- package/dist/tools/work-sessions-next.test.js +150 -0
- package/dist/tools/work-sessions-start.test.js +174 -0
- package/dist/tools/work-sessions.js +709 -0
- package/dist/tools/work-sessions.test.js +358 -0
- package/dist/toolset.js +149 -0
- package/dist/toolset.test.js +150 -0
- package/dist/version-guard.test.js +51 -0
- package/dist/version.js +28 -0
- package/dist/with-metrics-mail-nudge.test.js +41 -0
- package/dist/with-metrics-output-schema.test.js +91 -0
- package/dist/with-metrics.js +211 -0
- package/dist/with-metrics.test.js +230 -0
- package/package.json +66 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORB-1576 - MCP transport hardening.
|
|
3
|
+
*
|
|
4
|
+
* 1. Bearer rotation is only adopted when the presented bearer resolves
|
|
5
|
+
* to the SESSION OWNER (a session id + any valid token is no longer a
|
|
6
|
+
* cross-user attach/poison capability).
|
|
7
|
+
* 2. DELETE only tears down the owner's own session (404 otherwise).
|
|
8
|
+
* 3. readJsonBody caps at 5 MB (unauthenticated memory-exhaustion DoS).
|
|
9
|
+
*/
|
|
10
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { createServer as createNodeServer } from 'node:http';
|
|
12
|
+
import { createHttpServer } from './http-transport.js';
|
|
13
|
+
let server = null;
|
|
14
|
+
let api = null;
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
if (server) {
|
|
17
|
+
const internals = server.__mcp;
|
|
18
|
+
if (internals) {
|
|
19
|
+
for (const s of internals.sessions.values()) {
|
|
20
|
+
try {
|
|
21
|
+
await s.transport.close();
|
|
22
|
+
}
|
|
23
|
+
catch { /* ignore */ }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
server.close();
|
|
27
|
+
server = null;
|
|
28
|
+
}
|
|
29
|
+
await new Promise((r) => (api ? api.close(() => r()) : r()));
|
|
30
|
+
api = null;
|
|
31
|
+
});
|
|
32
|
+
/** Fake API whose /system/mcp/status maps bearer -> per-user email. */
|
|
33
|
+
function fakeApiPerUser() {
|
|
34
|
+
const api = createNodeServer((req, res) => {
|
|
35
|
+
const url = req.url ?? '';
|
|
36
|
+
if (url.startsWith('/system/mcp/status')) {
|
|
37
|
+
const auth = (req.headers.authorization ?? '');
|
|
38
|
+
const email = auth === 'Bearer orb_alice' || auth === 'Bearer orb_alice_rot'
|
|
39
|
+
? 'alice@example.com'
|
|
40
|
+
: auth === 'Bearer orb_mallory'
|
|
41
|
+
? 'mallory@example.com'
|
|
42
|
+
: null;
|
|
43
|
+
if (!email) {
|
|
44
|
+
res.writeHead(401, { 'content-type': 'application/json' });
|
|
45
|
+
res.end(JSON.stringify({ error: 'invalid token' }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
49
|
+
res.end(JSON.stringify({ enabled: true, mcpUseGranted: true, userMcpEnabled: true, userEmail: email }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (url.startsWith('/agent-instructions')) {
|
|
53
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
54
|
+
res.end(JSON.stringify({ instructions: '' }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (url.startsWith('/sse/mcp-events')) {
|
|
58
|
+
res.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
59
|
+
res.end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.writeHead(404, { 'content-type': 'application/json' });
|
|
63
|
+
res.end('{}');
|
|
64
|
+
});
|
|
65
|
+
return api;
|
|
66
|
+
}
|
|
67
|
+
const store = {
|
|
68
|
+
register: async () => { },
|
|
69
|
+
resolve: async () => false,
|
|
70
|
+
remove: async () => { },
|
|
71
|
+
};
|
|
72
|
+
async function start() {
|
|
73
|
+
api = fakeApiPerUser();
|
|
74
|
+
await new Promise((r) => api.listen(0, r));
|
|
75
|
+
const { port: apiPort } = api.address();
|
|
76
|
+
server = createHttpServer({ baseUrl: `http://127.0.0.1:${apiPort}`, sessionStore: store });
|
|
77
|
+
await new Promise((r) => server.listen(0, r));
|
|
78
|
+
const { port } = server.address();
|
|
79
|
+
return `http://127.0.0.1:${port}`;
|
|
80
|
+
}
|
|
81
|
+
const INIT_BODY = JSON.stringify({
|
|
82
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
83
|
+
params: { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 't', version: '1' } },
|
|
84
|
+
});
|
|
85
|
+
const NON_INIT = JSON.stringify({ jsonrpc: '2.0', method: 'tools/list', id: 2 });
|
|
86
|
+
async function post(base, body, headers) {
|
|
87
|
+
return fetch(`${base}/mcp`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', ...headers },
|
|
90
|
+
body,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function initSession(base, bearer) {
|
|
94
|
+
const res = await post(base, INIT_BODY, { authorization: `Bearer ${bearer}` });
|
|
95
|
+
expect(res.status).toBe(200);
|
|
96
|
+
const sid = res.headers.get('mcp-session-id');
|
|
97
|
+
expect(sid).toBeTruthy();
|
|
98
|
+
await res.text();
|
|
99
|
+
return sid;
|
|
100
|
+
}
|
|
101
|
+
describe('ORB-1576 - session binding', () => {
|
|
102
|
+
it('rotation adopted for the SAME user, rejected for a different user', async () => {
|
|
103
|
+
const base = await start();
|
|
104
|
+
const sid = await initSession(base, 'orb_alice');
|
|
105
|
+
// Same user, rotated token -> adopted (200).
|
|
106
|
+
const rotated = await post(base, NON_INIT, { authorization: 'Bearer orb_alice_rot', 'mcp-session-id': sid });
|
|
107
|
+
expect(rotated.status).toBe(200);
|
|
108
|
+
await rotated.text();
|
|
109
|
+
// Different user with a VALID token -> 401, holder not adopted.
|
|
110
|
+
const hijack = await post(base, NON_INIT, { authorization: 'Bearer orb_mallory', 'mcp-session-id': sid });
|
|
111
|
+
expect(hijack.status).toBe(401);
|
|
112
|
+
await hijack.text();
|
|
113
|
+
});
|
|
114
|
+
it('DELETE tears down only the owner session', async () => {
|
|
115
|
+
const base = await start();
|
|
116
|
+
const sid = await initSession(base, 'orb_alice');
|
|
117
|
+
const mallory = await fetch(`${base}/mcp`, {
|
|
118
|
+
method: 'DELETE',
|
|
119
|
+
headers: { authorization: 'Bearer orb_mallory', 'mcp-session-id': sid },
|
|
120
|
+
});
|
|
121
|
+
expect(mallory.status).toBe(404);
|
|
122
|
+
const alice = await fetch(`${base}/mcp`, {
|
|
123
|
+
method: 'DELETE',
|
|
124
|
+
headers: { authorization: 'Bearer orb_alice', 'mcp-session-id': sid },
|
|
125
|
+
});
|
|
126
|
+
expect(alice.status).toBe(200);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('ORB-1576 - request body cap', () => {
|
|
130
|
+
it('a >5 MB body is rejected (no memory growth, no 200)', async () => {
|
|
131
|
+
const base = await start();
|
|
132
|
+
const big = 'x'.repeat(6 * 1024 * 1024);
|
|
133
|
+
let status = 0;
|
|
134
|
+
let errored = false;
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch(`${base}/mcp`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer orb_alice' },
|
|
139
|
+
body: big,
|
|
140
|
+
});
|
|
141
|
+
status = res.status;
|
|
142
|
+
await res.text();
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
errored = true; // connection reset mid-write is an acceptable outcome too
|
|
146
|
+
}
|
|
147
|
+
expect(errored || status === 400).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// vitest fake-timer hygiene for the bridge internals
|
|
151
|
+
void vi;
|