@matuto-ai/claude-code-broker-connector 0.0.0-stage → 0.2.1

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.
@@ -0,0 +1,289 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { mkdirSync, lstatSync, statfsSync, readdirSync, chmodSync, realpathSync } from 'node:fs';
3
+ import { resolve, join } from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { canonical, digest, validateEnvelope, validateUuid, validateSend, validateIdempotencyKey, TYPES } from '../src/protocol.mjs';
6
+ import { LocalError } from './errors.mjs';
7
+
8
+ export function newSessionIdentity(clientType) {
9
+ if (!TYPES.includes(clientType)) throw new LocalError('invalid_configuration');
10
+ return { client_id: randomUUID(), session_instance_id: randomUUID(), client_type: clientType };
11
+ }
12
+
13
+ function identityValue(identity) {
14
+ if (!identity || Object.keys(identity).sort().join(',') !== 'client_id,client_type,session_instance_id'
15
+ || !TYPES.includes(identity.client_type)) throw new LocalError('invalid_configuration');
16
+ return { client_id: validateUuid(identity.client_id), session_instance_id: validateUuid(identity.session_instance_id), client_type: identity.client_type };
17
+ }
18
+
19
+ /** One session-bound durable journal. A separate SQLite OS lock survives no process crash. */
20
+ export class Inbox {
21
+ constructor({ directory, identity, now = Date.now, maxPendingCount = 1000,
22
+ maxPendingBytes = 64 * 1024 * 1024, maxLedgerCount = 50000,
23
+ maxOutgoingCount = 10000, maxDatabaseBytes = 128 * 1024 * 1024,
24
+ minFreeBytes = 16 * 1024 * 1024, retentionSeconds = 8 * 86400,
25
+ diskCheck, failurePoint = () => {} }) {
26
+ this.identity = identityValue(identity);
27
+ this.now = now;
28
+ this.failurePoint = failurePoint;
29
+ this.retentionSeconds = retentionSeconds;
30
+ this.limits = { maxPendingCount, maxPendingBytes, maxLedgerCount, maxOutgoingCount, maxDatabaseBytes, minFreeBytes };
31
+ for (const [key, value] of Object.entries({ ...this.limits, retentionSeconds })) {
32
+ if (!Number.isSafeInteger(value) || value < (key === 'minFreeBytes' ? 0 : 1)) throw new LocalError('invalid_configuration');
33
+ }
34
+ if (retentionSeconds < 7 * 86400) throw new LocalError('invalid_configuration');
35
+ this.directory = resolve(directory);
36
+ this.diskCheck = diskCheck;
37
+ this.closed = false;
38
+ this.storageUncertain = false;
39
+ try {
40
+ mkdirSync(this.directory, { recursive: true, mode: 0o700 });
41
+ const dir = lstatSync(this.directory);
42
+ if (!dir.isDirectory() || dir.isSymbolicLink() || realpathSync(this.directory) !== this.directory || (dir.mode & 0o077) !== 0
43
+ || (process.getuid && dir.uid !== process.getuid())) throw new LocalError('invalid_configuration');
44
+ for (const name of ['inbox.sqlite', 'owner.sqlite']) {
45
+ const path = join(this.directory, name);
46
+ try {
47
+ const file = lstatSync(path);
48
+ if (!file.isFile() || file.isSymbolicLink() || (file.mode & 0o077) !== 0) throw new LocalError('invalid_configuration');
49
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
50
+ }
51
+ this.checkDisk();
52
+ this.owner = new DatabaseSync(join(this.directory, 'owner.sqlite'));
53
+ chmodSync(join(this.directory, 'owner.sqlite'), 0o600);
54
+ try { this.owner.exec('PRAGMA busy_timeout=0; PRAGMA journal_mode=DELETE; BEGIN EXCLUSIVE; CREATE TABLE IF NOT EXISTS owner(singleton INTEGER PRIMARY KEY CHECK(singleton=1));'); }
55
+ catch { throw new LocalError('owner_conflict'); }
56
+ this.db = new DatabaseSync(join(this.directory, 'inbox.sqlite'));
57
+ chmodSync(join(this.directory, 'inbox.sqlite'), 0o600);
58
+ if (this.db.prepare('PRAGMA user_version').get().user_version > 1) throw new LocalError('invalid_configuration');
59
+ this.db.exec(`PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON;
60
+ PRAGMA busy_timeout=0; PRAGMA wal_autocheckpoint=100;
61
+ CREATE TABLE IF NOT EXISTS state(key TEXT PRIMARY KEY, value TEXT NOT NULL);
62
+ CREATE TABLE IF NOT EXISTS inbox(
63
+ message_id TEXT PRIMARY KEY, sequence INTEGER NOT NULL UNIQUE, digest TEXT NOT NULL,
64
+ envelope TEXT NOT NULL, bytes INTEGER NOT NULL, state TEXT NOT NULL,
65
+ received_at INTEGER NOT NULL, retain_until INTEGER NOT NULL,
66
+ injection_id TEXT, native_receipt TEXT, consumed_at INTEGER);
67
+ CREATE TABLE IF NOT EXISTS outgoing(
68
+ key TEXT PRIMARY KEY, digest TEXT NOT NULL, request TEXT NOT NULL,
69
+ created_at INTEGER NOT NULL, retry_until INTEGER NOT NULL, result TEXT);
70
+ PRAGMA user_version=1;`);
71
+ const stored = this.getState('identity');
72
+ if (stored && canonical(stored) !== canonical(this.identity)) throw new LocalError('session_mismatch');
73
+ if (!stored) this.setState('identity', this.identity);
74
+ this.db.prepare("UPDATE inbox SET state='uncertain' WHERE state='injecting'").run();
75
+ } catch (error) {
76
+ this.db?.close();
77
+ this.owner?.close();
78
+ if (error instanceof LocalError) throw error;
79
+ throw new LocalError('storage_unavailable');
80
+ }
81
+ }
82
+
83
+ checkDisk(extra = 0) {
84
+ this.assertUsable();
85
+ if (this.closed) throw new LocalError('storage_unavailable');
86
+ try {
87
+ if (this.diskCheck && !this.diskCheck(extra)) throw new Error('disk');
88
+ const disk = statfsSync(this.directory);
89
+ if ([0x6969, 0x517b, 0xff534d42].includes(Number(disk.type))) throw new Error('unsupported filesystem');
90
+ const bytes = readdirSync(this.directory).reduce((total, name) => {
91
+ const stat = lstatSync(join(this.directory, name));
92
+ return total + (stat.isFile() ? stat.size : 0);
93
+ }, 0);
94
+ if (Number(disk.bavail) * Number(disk.bsize) < this.limits.minFreeBytes + extra
95
+ || bytes + extra > this.limits.maxDatabaseBytes) throw new Error('disk');
96
+ } catch { throw new LocalError('storage_unavailable', { retryable: true }); }
97
+ }
98
+
99
+ transaction(callback, { growth = 0 } = {}) {
100
+ this.checkDisk(growth);
101
+ let commitAttempted = false;
102
+ let committed = false;
103
+ try {
104
+ this.db.exec('BEGIN IMMEDIATE');
105
+ const result = callback();
106
+ this.failurePoint('before_commit');
107
+ commitAttempted = true;
108
+ this.db.exec('COMMIT');
109
+ committed = true;
110
+ this.failurePoint('after_commit');
111
+ return result;
112
+ } catch (error) {
113
+ let rollbackConfirmed = false;
114
+ try { this.failurePoint('before_rollback'); this.db.exec('ROLLBACK'); rollbackConfirmed = true; } catch { /* The effect may be unknown. */ }
115
+ const effect = committed || (commitAttempted && !rollbackConfirmed) || !rollbackConfirmed ? 'unknown' : 'not_applied';
116
+ // A failed rollback can leave uncommitted rows visible on this connection.
117
+ // Do not allow a later duplicate lookup to ACK those rows. Reopen first.
118
+ if (!committed && !rollbackConfirmed) this.storageUncertain = true;
119
+ if (error instanceof LocalError && effect === 'not_applied') throw error;
120
+ throw new LocalError('storage_unavailable', { retryable: true, effect });
121
+ }
122
+ }
123
+
124
+ assertUsable() {
125
+ if (this.closed || this.storageUncertain) throw new LocalError('storage_unavailable', { effect: this.storageUncertain ? 'unknown' : 'not_applied', retryable: true });
126
+ }
127
+
128
+ getState(key) {
129
+ this.assertUsable();
130
+ const row = this.db.prepare('SELECT value FROM state WHERE key=?').get(key);
131
+ return row ? JSON.parse(row.value) : null;
132
+ }
133
+
134
+ setState(key, value) {
135
+ if (!['identity', 'attachment', 'registration_pending', 'claim_pending', 'metadata'].includes(key)) throw new LocalError('invalid_configuration');
136
+ const encoded = canonical(value);
137
+ if (Buffer.byteLength(encoded) > 256 * 1024) throw new LocalError('invalid_configuration');
138
+ return this.transaction(() => this.db.prepare('INSERT INTO state(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run(key, encoded), { growth: Buffer.byteLength(encoded) + 4096 });
139
+ }
140
+
141
+ accept(rawEnvelope, { serverNow = this.now(), retentionSeconds = this.retentionSeconds, retryWindowSeconds = 86400 } = {}) {
142
+ this.assertUsable();
143
+ let envelope;
144
+ try { envelope = validateEnvelope(rawEnvelope); }
145
+ catch { throw new LocalError('invalid_envelope'); }
146
+ if (envelope.recipient_id !== this.identity.client_id) throw new LocalError('session_mismatch');
147
+ const encoded = canonical(envelope);
148
+ const hash = digest(envelope);
149
+ const bytes = Buffer.byteLength(encoded);
150
+ if (!Number.isSafeInteger(retentionSeconds) || retentionSeconds < 7 * 86400
151
+ || !Number.isSafeInteger(retryWindowSeconds) || retryWindowSeconds < 1) throw new LocalError('invalid_configuration');
152
+ const existing = this.db.prepare('SELECT digest FROM inbox WHERE message_id=?').get(envelope.message_id);
153
+ if (existing) {
154
+ if (existing.digest !== hash) throw new LocalError('envelope_mismatch');
155
+ // A late redelivery restarts the local retention guarantee. If extending it
156
+ // fails, withhold ACK: saying not_stored would contradict this known row.
157
+ try {
158
+ this.transaction(() => this.db.prepare('UPDATE inbox SET retain_until=MAX(retain_until,?) WHERE message_id=?')
159
+ .run(this.now() + Math.max(retentionSeconds, this.retentionSeconds) * 1000, envelope.message_id));
160
+ } catch { throw new LocalError('storage_unavailable', { effect: 'unknown', retryable: true }); }
161
+ return { stored: true, duplicate: true, message_id: envelope.message_id };
162
+ }
163
+ if (Date.parse(envelope.expires_at) <= serverNow) throw new LocalError('envelope_expired');
164
+ return this.transaction(() => {
165
+ const usage = this.stats();
166
+ if (usage.pending_count >= this.limits.maxPendingCount || usage.pending_bytes + bytes > this.limits.maxPendingBytes
167
+ || usage.ledger_count >= this.limits.maxLedgerCount) throw new LocalError('inbox_full', { retryable: true });
168
+ const highest = this.db.prepare('SELECT MAX(sequence) AS sequence FROM inbox').get().sequence ?? 0;
169
+ const lastSequence = this.getState('attachment')?.highest_received_sequence ?? 0;
170
+ if (envelope.recipient_sequence <= Math.max(highest, lastSequence)) throw new LocalError('envelope_mismatch');
171
+ const retainUntil = Math.max(this.now() + Math.max(retentionSeconds, this.retentionSeconds) * 1000,
172
+ this.now() + Math.max(0, Date.parse(envelope.expires_at) - serverNow) + retryWindowSeconds * 1000);
173
+ this.db.prepare(`INSERT INTO inbox(message_id,sequence,digest,envelope,bytes,state,received_at,retain_until)
174
+ VALUES(?,?,?,?,?,'pending',?,?)`).run(envelope.message_id, envelope.recipient_sequence, hash, encoded, bytes, this.now(), retainUntil);
175
+ const attachment = { ...(this.getState('attachment') ?? {}), highest_received_sequence: envelope.recipient_sequence };
176
+ this.db.prepare('INSERT INTO state(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run('attachment', canonical(attachment));
177
+ this.failurePoint('after_inbox_insert');
178
+ return { stored: true, duplicate: false, message_id: envelope.message_id };
179
+ }, { growth: bytes * 3 + 16384 });
180
+ }
181
+
182
+ stats() {
183
+ this.assertUsable();
184
+ const row = this.db.prepare("SELECT COUNT(*) AS ledger_count, COALESCE(SUM(CASE WHEN state!='consumed' THEN 1 ELSE 0 END),0) AS pending_count, COALESCE(SUM(CASE WHEN state!='consumed' THEN bytes ELSE 0 END),0) AS pending_bytes FROM inbox").get();
185
+ return { ...row };
186
+ }
187
+
188
+ head() {
189
+ this.assertUsable();
190
+ const row = this.db.prepare("SELECT * FROM inbox WHERE state!='consumed' ORDER BY sequence LIMIT 1").get();
191
+ return row ? { message_id: row.message_id, sequence: row.sequence, state: row.state,
192
+ envelope: JSON.parse(row.envelope), injection_id: row.injection_id } : null;
193
+ }
194
+
195
+ beginInjection(messageId) {
196
+ return this.transaction(() => {
197
+ const head = this.head();
198
+ if (!head || head.message_id !== messageId) throw new LocalError('unsafe_boundary');
199
+ if (head.state !== 'pending') throw new LocalError('injection_uncertain');
200
+ const injectionId = randomUUID();
201
+ this.db.prepare("UPDATE inbox SET state='injecting', injection_id=? WHERE message_id=?").run(injectionId, messageId);
202
+ return injectionId;
203
+ });
204
+ }
205
+
206
+ recordNativeAcceptance(messageId, injectionId, receipt) {
207
+ if (!receipt || receipt.accepted !== true || typeof receipt.receipt_id !== 'string'
208
+ || receipt.receipt_id.length < 1 || receipt.receipt_id.length > 256
209
+ || /[\x00-\x1f\x7f]/.test(receipt.receipt_id)) throw new LocalError('receipt_required', { effect: 'unknown' });
210
+ return this.transaction(() => {
211
+ const row = this.db.prepare('SELECT state,injection_id FROM inbox WHERE message_id=?').get(messageId);
212
+ if (!row || !['injecting', 'uncertain'].includes(row.state) || row.injection_id !== injectionId) throw new LocalError('injection_uncertain');
213
+ this.db.prepare("UPDATE inbox SET state='consumed', native_receipt=?, consumed_at=? WHERE message_id=?").run(receipt.receipt_id, this.now(), messageId);
214
+ });
215
+ }
216
+
217
+ recordNativeRejection(messageId, injectionId, { definitelyNotAccepted } = {}) {
218
+ if (definitelyNotAccepted !== true) throw new LocalError('injection_uncertain', { effect: 'unknown' });
219
+ return this.transaction(() => {
220
+ const row = this.db.prepare('SELECT state,injection_id FROM inbox WHERE message_id=?').get(messageId);
221
+ if (!row || row.state !== 'injecting' || row.injection_id !== injectionId) throw new LocalError('injection_uncertain');
222
+ this.db.prepare("UPDATE inbox SET state='pending', injection_id=NULL WHERE message_id=?").run(messageId);
223
+ });
224
+ }
225
+
226
+ /** Operator-only local API. Never expose this through an agent tool. */
227
+ resolveUncertain(messageId, { resolution, duplicateRiskAcknowledged = false } = {}) {
228
+ if (!['accepted', 'retry'].includes(resolution) || (resolution === 'retry' && duplicateRiskAcknowledged !== true)) throw new LocalError('injection_uncertain');
229
+ return this.transaction(() => {
230
+ const row = this.db.prepare('SELECT state FROM inbox WHERE message_id=?').get(messageId);
231
+ if (!row || !['injecting', 'uncertain'].includes(row.state)) throw new LocalError('injection_uncertain');
232
+ if (resolution === 'accepted') this.db.prepare("UPDATE inbox SET state='consumed',native_receipt='operator-confirmed',consumed_at=? WHERE message_id=?").run(this.now(), messageId);
233
+ else this.db.prepare("UPDATE inbox SET state='pending',injection_id=NULL WHERE message_id=?").run(messageId);
234
+ });
235
+ }
236
+
237
+ prepareSend(rawRequest, { key = randomUUID(), retryWindowSeconds = 86400 } = {}) {
238
+ key = validateIdempotencyKey(key);
239
+ const request = validateSend(rawRequest);
240
+ const encoded = canonical(request);
241
+ const hash = digest(request);
242
+ const existing = this.outgoing(key);
243
+ if (existing) {
244
+ if (existing.digest !== hash) throw new LocalError('send_conflict');
245
+ return key;
246
+ }
247
+ if (!Number.isSafeInteger(retryWindowSeconds) || retryWindowSeconds < 1) throw new LocalError('invalid_configuration');
248
+ this.transaction(() => {
249
+ if (this.db.prepare('SELECT COUNT(*) AS n FROM outgoing').get().n >= this.limits.maxOutgoingCount) throw new LocalError('inbox_full');
250
+ this.db.prepare('INSERT INTO outgoing(key,digest,request,created_at,retry_until) VALUES(?,?,?,?,?)').run(key, hash, encoded, this.now(), this.now() + retryWindowSeconds * 1000);
251
+ }, { growth: Buffer.byteLength(encoded) * 3 + 8192 });
252
+ return key;
253
+ }
254
+
255
+ outgoing(key) {
256
+ this.assertUsable();
257
+ const row = this.db.prepare('SELECT * FROM outgoing WHERE key=?').get(key);
258
+ return row ? { ...row, request: JSON.parse(row.request), result: row.result ? JSON.parse(row.result) : null } : null;
259
+ }
260
+
261
+ unresolvedSends() {
262
+ this.assertUsable();
263
+ return this.db.prepare('SELECT key,created_at,retry_until FROM outgoing WHERE result IS NULL ORDER BY created_at LIMIT 100').all().map(row => ({ ...row }));
264
+ }
265
+
266
+ recordSendResult(key, result) {
267
+ const encoded = canonical(result);
268
+ if (Buffer.byteLength(encoded) > 16384) throw new LocalError('invalid_response', { effect: 'unknown' });
269
+ this.transaction(() => {
270
+ if (!this.outgoing(key)) throw new LocalError('invalid_configuration');
271
+ this.db.prepare('UPDATE outgoing SET result=? WHERE key=?').run(encoded, key);
272
+ }, { growth: Buffer.byteLength(encoded) * 2 + 8192 });
273
+ }
274
+
275
+ collect(limit = 100) {
276
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) throw new LocalError('invalid_configuration');
277
+ return this.transaction(() => {
278
+ const removedInbox = this.db.prepare("DELETE FROM inbox WHERE message_id IN (SELECT message_id FROM inbox WHERE state='consumed' AND retain_until<? ORDER BY retain_until LIMIT ?)").run(this.now(), limit).changes;
279
+ const removedSends = this.db.prepare('DELETE FROM outgoing WHERE key IN (SELECT key FROM outgoing WHERE result IS NOT NULL AND retry_until<? ORDER BY retry_until LIMIT ?)').run(this.now(), limit).changes;
280
+ return { removed_inbox: removedInbox, removed_sends: removedSends };
281
+ });
282
+ }
283
+
284
+ close() {
285
+ if (this.closed) return;
286
+ this.closed = true;
287
+ try { this.db.close(); } finally { this.owner.close(); }
288
+ }
289
+ }
@@ -0,0 +1,153 @@
1
+ import { StringDecoder } from 'node:string_decoder';
2
+ import { canonical } from '../src/protocol.mjs';
3
+ import { invokeTool } from './tools.mjs';
4
+ import { PROVENANCE_WARNING } from './runtime.mjs';
5
+
6
+ /** Minimal MCP 2025-06-18 stdio tools only; not HTTP MCP, channels, or a TUI wakeup API. */
7
+ export const MCP_VERSION = '2025-06-18';
8
+ const text = maxLength => ({ type: 'string', maxLength });
9
+ const uuid = { type: 'string', format: 'uuid', maxLength: 36 };
10
+ const object = (properties = {}, required = []) => ({ type: 'object', properties, required, additionalProperties: false });
11
+ const model = object({ provider: { type: ['string', 'null'], maxLength: 128 }, id: { type: ['string', 'null'], maxLength: 256 }, source: { enum: ['unknown', 'observed', 'configured'] } }, ['provider', 'id', 'source']);
12
+ const metadata = object({ name: text(128), model, project: { type: ['string', 'null'], maxLength: 256 } });
13
+
14
+ export const TOOL_DEFINITIONS = Object.freeze([
15
+ { name: 'broker_list_clients', description: 'List authorized exact session addresses. Names and models are descriptive, not routing authority.', inputSchema: object({ connected: { type: 'boolean' }, client_type: { enum: ['pi', 'antigravity-cli', 'claude-code', 'opencode'] }, name: text(128), project: text(256), cursor: text(256), limit: { type: 'integer', minimum: 1, maximum: 100 } }) },
16
+ { name: 'broker_send', description: 'Send opaque untrusted AGENT text to one exact client_id. Acceptance means broker storage only; preserve send_key on uncertain results.', inputSchema: object({ recipient_id: uuid, text: text(65536), ttl_seconds: { type: 'integer', minimum: 60, maximum: 604800 }, correlation_id: text(128), reply_to_message_id: uuid, send_key: text(128) }, ['recipient_id', 'text']) },
17
+ { name: 'broker_update_metadata', description: 'CAS update of descriptive name/model/project only. Metadata does not grant access or change identity.', inputSchema: object({ metadata_patch: metadata, expected_metadata_revision: { type: 'integer', minimum: 1 } }, ['metadata_patch', 'expected_metadata_revision']) },
18
+ { name: 'broker_message_status', description: 'Read broker delivery state, never model progress or task completion.', inputSchema: object({ message_id: uuid }, ['message_id']) },
19
+ { name: 'broker_receive', description: 'Cooperatively peek at the oldest durable AGENT inbox item during this tool call. No unsolicited wakeup or native-consumption receipt; the item remains pending.', inputSchema: object() },
20
+ ]);
21
+
22
+ function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
23
+ function validArguments(value, schema) {
24
+ if (!isObject(value)) return false;
25
+ if (Object.keys(value).some(key => !Object.hasOwn(schema.properties, key))) return false;
26
+ if (schema.required.some(key => !Object.hasOwn(value, key))) return false;
27
+ for (const [key, item] of Object.entries(value)) {
28
+ const rule = schema.properties[key];
29
+ if (rule.enum && !rule.enum.includes(item)) return false;
30
+ if (rule.type === 'object' && !validArguments(item, rule)) return false;
31
+ if (rule.type === 'boolean' && typeof item !== 'boolean') return false;
32
+ if (rule.type === 'integer' && (!Number.isSafeInteger(item) || item < rule.minimum || item > (rule.maximum ?? Number.MAX_SAFE_INTEGER))) return false;
33
+ if ((rule.type === 'string' || Array.isArray(rule.type)) && !(item === null && Array.isArray(rule.type) && rule.type.includes('null'))) {
34
+ if (typeof item !== 'string' || !item.isWellFormed() || [...item].length > rule.maxLength) return false;
35
+ }
36
+ if (rule.format === 'uuid' && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(item)) return false;
37
+ }
38
+ return true;
39
+ }
40
+
41
+ /**
42
+ * Call with tools from createTools and a host-bound cooperativeBoundary. A fresh
43
+ * process is a fresh harness incarnation unless its owner independently proves
44
+ * continuity. Stream write success is never used to consume a local inbox item.
45
+ * onClose must stop heartbeats/receive work for the departed host. It is invoked
46
+ * once and is not an agent tool. No credentials are accepted by this interface.
47
+ */
48
+ export function serveStdio({ input = process.stdin, output = process.stdout, tools, onClose = async () => {},
49
+ onDiagnostic = () => {}, maxFrameBytes = 128 * 1024, maxResponseBytes = 512 * 1024, maxInflight = 8 } = {}) {
50
+ if (!tools || !input?.on || !output?.write || !Number.isSafeInteger(maxFrameBytes) || maxFrameBytes < 1024 || maxFrameBytes > 1024 * 1024
51
+ || !Number.isSafeInteger(maxResponseBytes) || maxResponseBytes < 1024 || maxResponseBytes > 1024 * 1024
52
+ || !Number.isSafeInteger(maxInflight) || maxInflight < 1 || maxInflight > 32) throw new Error('invalid_stdio_configuration');
53
+ let phase = 'new', buffer = '', active = 0, pendingWrites = 0, closed = false;
54
+ const decoder = new StringDecoder('utf8'), requestIds = new Set();
55
+ let writes = Promise.resolve(), closeResolve;
56
+ const done = new Promise(resolve => { closeResolve = resolve; });
57
+ const diagnostic = code => { try { onDiagnostic({ code }); } catch { /* No raw data. */ } };
58
+ const close = async code => {
59
+ if (closed) return done;
60
+ closed = true; phase = 'closed'; buffer = ''; input.pause?.();
61
+ input.off('data', onData); diagnostic(code);
62
+ try { await onClose(); } catch { diagnostic('stdio_close_failed'); }
63
+ closeResolve(); return done;
64
+ };
65
+ function send(message) {
66
+ if (closed) return;
67
+ if (pendingWrites >= maxInflight) { void close('stdio_output_backpressure'); return; }
68
+ let line;
69
+ try { line = canonical(message) + '\n'; } catch { void close('stdio_invalid_response'); return; }
70
+ if (Buffer.byteLength(line) > maxResponseBytes) {
71
+ line = JSON.stringify({ jsonrpc: '2.0', id: message.id ?? null, error: { code: -32603, message: 'Response exceeds the configured byte limit; no native acceptance was recorded.' } }) + '\n';
72
+ }
73
+ pendingWrites++;
74
+ writes = writes.then(() => new Promise(resolve => {
75
+ if (closed) return resolve();
76
+ try { output.write(line, error => { if (error) void close('stdio_write_failed'); resolve(); }); }
77
+ catch { void close('stdio_write_failed'); resolve(); }
78
+ })).finally(() => { pendingWrites--; });
79
+ }
80
+ const error = (id, code, message) => send({ jsonrpc: '2.0', id, error: { code, message } });
81
+
82
+ async function dispatch(request) {
83
+ const id = request.id;
84
+ if (request.method === 'ping') { send({ jsonrpc: '2.0', id, result: {} }); return; }
85
+ if (request.method === 'initialize') {
86
+ const p = request.params;
87
+ if (phase !== 'new' || !isObject(p) || typeof p.protocolVersion !== 'string' || p.protocolVersion.length > 100 || !isObject(p.capabilities) || !isObject(p.clientInfo) || typeof p.clientInfo.name !== 'string' || typeof p.clientInfo.version !== 'string') {
88
+ error(id, -32602, 'Invalid or repeated initialization.'); return;
89
+ }
90
+ phase = 'initialized';
91
+ send({ jsonrpc: '2.0', id, result: { protocolVersion: MCP_VERSION, capabilities: { tools: {} },
92
+ serverInfo: { name: 'ai-agent-msg-broker', version: '0.1.0' },
93
+ instructions: `${PROVENANCE_WARNING} broker_receive is cooperative peek only. No permission relay, channels, sampling, or automatic input injection is provided.` } });
94
+ return;
95
+ }
96
+ if (phase !== 'ready') { error(id, -32000, 'Complete initialize and notifications/initialized first.'); return; }
97
+ if (request.method === 'tools/list') {
98
+ if (request.params !== undefined && (!isObject(request.params) || Object.keys(request.params).length)) { error(id, -32602, 'Tool pagination is not supported.'); return; }
99
+ send({ jsonrpc: '2.0', id, result: { tools: TOOL_DEFINITIONS.filter(definition => typeof tools[definition.name] === 'function') } }); return;
100
+ }
101
+ if (request.method === 'tools/call') {
102
+ const p = request.params, definition = TOOL_DEFINITIONS.find(item => item.name === p?.name);
103
+ if (!isObject(p) || Object.keys(p).some(key => !['name', 'arguments', '_meta'].includes(key)) || !definition || typeof tools[definition.name] !== 'function' || !validArguments(p.arguments ?? {}, definition.inputSchema)) {
104
+ error(id, -32602, 'Unknown tool or invalid bounded arguments.'); return;
105
+ }
106
+ const result = await invokeTool(tools, p.name, p.arguments ?? {});
107
+ send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: canonical(result) }], isError: Boolean(result?.error) } }); return;
108
+ }
109
+ error(id, -32601, 'Method not supported by this tools-only stdio server.');
110
+ }
111
+
112
+ function frame(line) {
113
+ if (closed) return;
114
+ let request;
115
+ try { request = JSON.parse(line); canonical(request); } catch { error(null, -32700, 'Invalid bounded JSON.'); return; }
116
+ const hasId = isObject(request) && Object.hasOwn(request, 'id');
117
+ if (!isObject(request) || request.jsonrpc !== '2.0' || typeof request.method !== 'string' || request.method.length > 128
118
+ || Object.keys(request).some(key => !['jsonrpc', 'id', 'method', 'params'].includes(key))
119
+ || (hasId && !(typeof request.id === 'string' && request.id.length <= 128 || Number.isSafeInteger(request.id)))) {
120
+ error(null, -32600, 'Invalid JSON-RPC request.'); return;
121
+ }
122
+ if (!hasId) {
123
+ if (request.method === 'notifications/initialized' && phase === 'initialized') phase = 'ready';
124
+ // Permission/channel notifications, cancellations and unknown notifications
125
+ // have no side effects. They cannot acknowledge or authorize an inbox item.
126
+ return;
127
+ }
128
+ const key = `${typeof request.id}:${request.id}`;
129
+ if (requestIds.has(key)) { error(request.id, -32600, 'Request ID is already in flight; reconcile before retrying.'); return; }
130
+ if (active >= maxInflight) { void close('stdio_capacity_exceeded'); return; }
131
+ requestIds.add(key); active++;
132
+ dispatch(request).catch(() => error(request.id, -32603, 'Tool result unavailable; reconcile the original operation.'))
133
+ .finally(() => { requestIds.delete(key); active--; });
134
+ }
135
+ function onData(chunk) {
136
+ if (closed) return;
137
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
138
+ // A single oversized read is refused before decoding/concatenating it.
139
+ if (bytes.length > maxFrameBytes) { void close('stdio_frame_too_large'); return; }
140
+ buffer += decoder.write(bytes);
141
+ while (!closed && buffer.includes('\n')) {
142
+ const index = buffer.indexOf('\n'), line = buffer.slice(0, index); buffer = buffer.slice(index + 1);
143
+ if (Buffer.byteLength(line) > maxFrameBytes) { void close('stdio_frame_too_large'); return; }
144
+ if (line.trim()) frame(line);
145
+ }
146
+ if (Buffer.byteLength(buffer) > maxFrameBytes) void close('stdio_frame_too_large');
147
+ }
148
+ input.on('data', onData);
149
+ input.on('end', () => { const trailing = decoder.end(); void close(buffer.trim() || trailing ? 'stdio_incomplete_frame' : 'stdio_closed'); });
150
+ input.on('error', () => { void close('stdio_read_failed'); });
151
+ output.on?.('error', () => { void close('stdio_write_failed'); });
152
+ return { close: () => close('stdio_closed'), done, flush: () => writes };
153
+ }