@code-yeongyu/senpi 2026.10.25-2 → 2026.10.25-4
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/CHANGELOG.md +37 -0
- package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
- package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
- package/dist/bundle/chunks/{chunk-XBUSVKWC.js → chunk-2RB5EL4X.js} +1 -1
- package/dist/bundle/chunks/{chunk-KUE4AQN6.js → chunk-3QCYBOKN.js} +1 -1
- package/dist/bundle/chunks/{chunk-2EFVQWXZ.js → chunk-4MOZXOJX.js} +5 -5
- package/dist/bundle/chunks/{chunk-MXZPAUFB.js → chunk-5JJDR6XI.js} +1 -1
- package/dist/bundle/chunks/chunk-6VWIMHMH.js +2 -0
- package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
- package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
- package/dist/bundle/chunks/{chunk-VNN2V74Y.js → chunk-BZ7726QO.js} +1 -1
- package/dist/bundle/chunks/{chunk-J2U5VDG7.js → chunk-C2MQ4QXN.js} +30 -30
- package/dist/bundle/chunks/{chunk-JRXKLIEQ.js → chunk-DODH3N6U.js} +5 -5
- package/dist/bundle/chunks/{chunk-EHQXLHRB.js → chunk-E6D4RIT7.js} +2 -2
- package/dist/bundle/chunks/chunk-J37GRED2.js +5 -0
- package/dist/bundle/chunks/chunk-JMOUU75H.js +2 -0
- package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
- package/dist/bundle/chunks/chunk-KKD4CYCI.js +3 -0
- package/dist/bundle/chunks/{chunk-YN7ELQ43.js → chunk-KNVNKDER.js} +1 -1
- package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
- package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
- package/dist/bundle/chunks/{chunk-G2UETS2R.js → chunk-LOIF6PHI.js} +4 -4
- package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
- package/dist/bundle/chunks/chunk-N7IN46Q5.js +27 -0
- package/dist/bundle/chunks/{chunk-LYGAV6KU.js → chunk-PT6JJUN2.js} +1 -1
- package/dist/bundle/chunks/{chunk-WFXUCVIR.js → chunk-SBGIXMX3.js} +2 -5
- package/dist/bundle/chunks/{chunk-W2BKZBYJ.js → chunk-STMXIBXF.js} +2 -2
- package/dist/bundle/chunks/chunk-TBU6VYHN.js +13 -0
- package/dist/bundle/chunks/{chunk-RXKNOEQQ.js → chunk-V2UYXTRR.js} +1 -1
- package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
- package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
- package/dist/bundle/chunks/chunk-XMJKZK5H.js +2 -0
- package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
- package/dist/bundle/chunks/cli-main-YYH3IBC6.js +3 -0
- package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
- package/dist/bundle/chunks/dispatch-MXG5YWLV.js +9 -0
- package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
- package/dist/bundle/chunks/host-command-TDLLHXVI.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-3VHHIGDL.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +8 -6
- package/dist/bundle/chunks/host-session-command-56XF6ACS.js +20 -0
- package/dist/bundle/chunks/{host-socket-request-AS2OCRLP.js → host-socket-request-QL2WEZTN.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +2 -0
- package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-VTTY5MFT.js → multi-session-host-Z2CW4SDM.js} +12 -9
- package/dist/bundle/chunks/{package-manager-cli-DZFOC3MF.js → package-manager-cli-N55U6PEO.js} +1 -1
- package/dist/bundle/chunks/rotation-stream-CBGCBYHV.js +2 -0
- package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-B5NQ4ZVH.js → rpc-monitor-source-AE3C6EQV.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-SLISTG4V.js → schedule-command-VS7CZC2J.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +3 -0
- package/dist/bundle/chunks/session-picker-E4KUJDLO.js +2 -0
- package/dist/bundle/chunks/session-worker.js +292 -278
- package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
- package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
- package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
- package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +8 -6
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +9 -0
- package/dist/cli/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +7 -1
- package/dist/cli/host-command.js +3 -1
- package/dist/core/agent-session.js +22 -7
- package/dist/core/extensions/builtin/goal/index.js +1 -0
- package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/connection.js +46 -1
- package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
- package/dist/core/extensions/builtin/mcp/elicitation.js +54 -2
- package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
- package/dist/core/extensions/builtin/mcp/health.js +5 -0
- package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
- package/dist/core/extensions/builtin/mcp/index.js +2 -0
- package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
- package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
- package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
- package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
- package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
- package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
- package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
- package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
- package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
- package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
- package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/resources.js +19 -4
- package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/service.js +79 -4
- package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
- package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
- package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
- package/dist/core/extensions/runner.js +3 -2
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-ledger.d.ts +1 -0
- package/dist/core/lifecycle-ledger.js +7 -2
- package/dist/core/lifecycle-record.js +3 -0
- package/dist/core/lifecycle-session-binding.d.ts +11 -1
- package/dist/core/lifecycle-session-binding.js +29 -2
- package/dist/core/lifecycle-stop.d.ts +41 -0
- package/dist/core/lifecycle-stop.js +92 -0
- package/dist/core/mcp-form-types.d.ts +89 -0
- package/dist/core/mcp-form-types.js +23 -0
- package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
- package/dist/core/mcp-mrtr-presentations.js +17 -0
- package/dist/core/session-control-actions.js +0 -2
- package/dist/core/session-lifecycle-types.d.ts +15 -0
- package/dist/core/session-lifecycle.d.ts +17 -1
- package/dist/core/session-lifecycle.js +40 -2
- package/dist/main.js +5 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.js +34 -8
- package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
- package/dist/modes/interactive/mcp-local-presenter.js +109 -0
- package/dist/modes/interactive/session-control-attach.d.ts +20 -0
- package/dist/modes/interactive/session-control-attach.js +13 -0
- package/dist/modes/interactive/session-control-commands.js +16 -6
- package/dist/modes/interactive/session-control-registry.d.ts +2 -1
- package/dist/modes/interactive/session-control-registry.js +3 -2
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +4 -2
- package/dist/modes/interactive/session-control-rpc-commands.js +114 -4
- package/dist/modes/interactive/session-control-server.js +9 -3
- package/dist/modes/rpc/connection-handler.d.ts +1 -0
- package/dist/modes/rpc/connection-handler.js +13 -41
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/get-entries-command.d.ts +13 -0
- package/dist/modes/rpc/get-entries-command.js +61 -0
- package/dist/modes/rpc/get-entries-page.d.ts +22 -0
- package/dist/modes/rpc/get-entries-page.js +41 -2
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +6 -2
- package/dist/modes/rpc/host-ensure-start.js +13 -2
- package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.js +1 -0
- package/dist/modes/rpc/host-handoff.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.js +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
- package/dist/modes/rpc/host-observe-request.d.ts +2 -1
- package/dist/modes/rpc/host-observe-request.js +4 -1
- package/dist/modes/rpc/host-runner.d.ts +2 -0
- package/dist/modes/rpc/host-runner.js +8 -6
- package/dist/modes/rpc/host-successor.js +11 -1
- package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
- package/dist/modes/rpc/mcp-form-dialog.js +159 -0
- package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
- package/dist/modes/rpc/mcp-form-worker.js +167 -0
- package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
- package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
- package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
- package/dist/modes/rpc/mcp-presenter-control.js +330 -0
- package/dist/modes/rpc/multi-session-host.d.ts +3 -0
- package/dist/modes/rpc/multi-session-host.js +36 -1
- package/dist/modes/rpc/rpc-read-response-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +8 -1
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-event-writer.d.ts +14 -0
- package/dist/modes/rpc/session-event-writer.js +128 -1
- package/dist/modes/rpc/session-router-routing.js +1 -0
- package/dist/modes/rpc/session-worker-client.d.ts +3 -0
- package/dist/modes/rpc/session-worker-client.js +89 -0
- package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
- package/dist/modes/rpc/session-worker.js +9 -0
- package/docs/extensions.md +5 -0
- package/docs/mcp.md +20 -3
- package/docs/rpc.md +151 -8
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-3LNSLJ7N.js +0 -2
- package/dist/bundle/chunks/chunk-B6TPQDBW.js +0 -5
- package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
- package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
- package/dist/bundle/chunks/chunk-KJZAY5YH.js +0 -27
- package/dist/bundle/chunks/chunk-MSQCTH6V.js +0 -2
- package/dist/bundle/chunks/chunk-OI4NMCKP.js +0 -3
- package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
- package/dist/bundle/chunks/cli-main-SWKFMTZQ.js +0 -3
- package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
- package/dist/bundle/chunks/host-command-BC6UWP33.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-TN5NEGNF.js +0 -3
- package/dist/bundle/chunks/host-session-command-MGVBLBRV.js +0 -20
- package/dist/bundle/chunks/interactive-mode-SCSROHD5.js +0 -2
- package/dist/bundle/chunks/rotation-stream-U7YQMC7H.js +0 -2
- package/dist/bundle/chunks/rpc-mode-TOXYD4XA.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-HFFMBALG.js +0 -3
- package/dist/bundle/chunks/session-picker-465FVXQG.js +0 -2
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { queuedForm } from "../../core/extensions/builtin/mcp/modern/consent-queue.js";
|
|
3
|
+
import { isRecord, validateMrtrForm } from "../../core/extensions/builtin/mcp/modern/form-schema.js";
|
|
4
|
+
import { McpFormDialog, mcpFormClock } from "./mcp-form-dialog.js";
|
|
5
|
+
/** Host-owned only. No public session command can call enrollment or install a registration. */
|
|
6
|
+
export class McpPresenterAuthority {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.instance = randomUUID();
|
|
9
|
+
this.connections = new Map();
|
|
10
|
+
this.bindings = new Map();
|
|
11
|
+
this.registrations = new Map();
|
|
12
|
+
this.clock = options.clock ?? mcpFormClock;
|
|
13
|
+
}
|
|
14
|
+
dispose() {
|
|
15
|
+
for (const binding of [...this.bindings.values()])
|
|
16
|
+
binding.dispose();
|
|
17
|
+
this.connections.clear();
|
|
18
|
+
this.registrations.clear();
|
|
19
|
+
}
|
|
20
|
+
connect(id, send) {
|
|
21
|
+
const connection = { id, send, epoch: randomUUID(), nonce: randomUUID(), attachments: new Map() };
|
|
22
|
+
this.connections.set(connection, connection);
|
|
23
|
+
return connection;
|
|
24
|
+
}
|
|
25
|
+
attach(sender, handle) {
|
|
26
|
+
const connection = this.connections.get(sender);
|
|
27
|
+
const binding = [...this.bindings.values()].find((value) => value === handle);
|
|
28
|
+
if (connection && binding && !connection.attachments.has(binding))
|
|
29
|
+
connection.attachments.set(binding, { id: randomUUID(), epoch: randomUUID() });
|
|
30
|
+
}
|
|
31
|
+
connectionContext(sender, sessionId) {
|
|
32
|
+
const connection = this.connections.get(sender);
|
|
33
|
+
const binding = this.bindings.get(sessionId);
|
|
34
|
+
const attachment = binding && connection?.attachments.get(binding);
|
|
35
|
+
if (!connection || !binding || !attachment)
|
|
36
|
+
return undefined;
|
|
37
|
+
return {
|
|
38
|
+
hostInstance: this.instance,
|
|
39
|
+
connectionNonce: connection.nonce,
|
|
40
|
+
bindingEpoch: binding.epoch,
|
|
41
|
+
attachmentId: attachment.id,
|
|
42
|
+
attachmentEpoch: attachment.epoch,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
detach(sender, handle) {
|
|
46
|
+
const connection = this.connections.get(sender);
|
|
47
|
+
if (!connection)
|
|
48
|
+
return;
|
|
49
|
+
for (const binding of connection.attachments.keys()) {
|
|
50
|
+
if (handle !== undefined && handle !== binding)
|
|
51
|
+
continue;
|
|
52
|
+
connection.attachments.delete(binding);
|
|
53
|
+
if (binding.grant?.registration.connection === connection)
|
|
54
|
+
this.revoke(binding.grant.registration);
|
|
55
|
+
}
|
|
56
|
+
if (handle === undefined)
|
|
57
|
+
this.connections.delete(sender);
|
|
58
|
+
}
|
|
59
|
+
withdraw(sender) {
|
|
60
|
+
const connection = this.connections.get(sender);
|
|
61
|
+
if (!connection)
|
|
62
|
+
return;
|
|
63
|
+
for (const binding of connection.attachments.keys())
|
|
64
|
+
if (binding.grant?.registration.connection === connection)
|
|
65
|
+
this.revoke(binding.grant.registration);
|
|
66
|
+
}
|
|
67
|
+
bind(sessionId, durableId, admit) {
|
|
68
|
+
this.bindings.get(sessionId)?.dispose();
|
|
69
|
+
const lifetime = new AbortController();
|
|
70
|
+
const binding = {
|
|
71
|
+
sessionId,
|
|
72
|
+
durableId,
|
|
73
|
+
admit,
|
|
74
|
+
epoch: randomUUID(),
|
|
75
|
+
lifetime,
|
|
76
|
+
signal: lifetime.signal,
|
|
77
|
+
listeners: new Set(),
|
|
78
|
+
version: () => (binding.grant && this.current(binding.grant) ? binding.grant.id : undefined),
|
|
79
|
+
subscribe: (listener) => {
|
|
80
|
+
binding.listeners.add(listener);
|
|
81
|
+
return () => {
|
|
82
|
+
binding.listeners.delete(listener);
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
capture: () => {
|
|
86
|
+
const grant = binding.grant;
|
|
87
|
+
return grant && this.current(grant) ? this.presentation(grant) : undefined;
|
|
88
|
+
},
|
|
89
|
+
dispose: () => {
|
|
90
|
+
binding.lifetime.abort();
|
|
91
|
+
if (binding.grant)
|
|
92
|
+
this.revoke(binding.grant.registration, true);
|
|
93
|
+
if (this.bindings.get(sessionId) === binding)
|
|
94
|
+
this.bindings.delete(sessionId);
|
|
95
|
+
for (const connection of this.connections.values())
|
|
96
|
+
connection.attachments.delete(binding);
|
|
97
|
+
binding.listeners.clear();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
this.bindings.set(sessionId, binding);
|
|
101
|
+
return binding;
|
|
102
|
+
}
|
|
103
|
+
/** Called only after the protected bootstrap has pinned and authenticated its launch principal. */
|
|
104
|
+
enroll(principal = randomUUID()) {
|
|
105
|
+
const generation = randomUUID();
|
|
106
|
+
return {
|
|
107
|
+
register: (handle, sender, options) => {
|
|
108
|
+
const binding = [...this.bindings.values()].find((value) => value === handle);
|
|
109
|
+
const connection = this.connections.get(sender);
|
|
110
|
+
if (!binding || binding.grant || !connection?.attachments.has(binding))
|
|
111
|
+
throw new Error("presenter_not_authorized");
|
|
112
|
+
const registration = {
|
|
113
|
+
id: randomUUID(),
|
|
114
|
+
principal,
|
|
115
|
+
generation,
|
|
116
|
+
binding,
|
|
117
|
+
connection,
|
|
118
|
+
kind: options.kind,
|
|
119
|
+
active: true,
|
|
120
|
+
};
|
|
121
|
+
this.registrations.set(registration, registration);
|
|
122
|
+
return registration;
|
|
123
|
+
},
|
|
124
|
+
acquire: (handle) => {
|
|
125
|
+
const registration = this.registrations.get(handle);
|
|
126
|
+
if (!registration || registration.principal !== principal || !registration.active)
|
|
127
|
+
throw new Error("presenter_not_authorized");
|
|
128
|
+
const { binding, connection } = registration;
|
|
129
|
+
const attachment = connection.attachments.get(binding);
|
|
130
|
+
if (!attachment || binding.lifetime.signal.aborted || binding.grant)
|
|
131
|
+
throw new Error("presenter_not_authorized");
|
|
132
|
+
const scope = {
|
|
133
|
+
hostInstance: this.instance,
|
|
134
|
+
durableSessionId: binding.durableId,
|
|
135
|
+
bindingEpoch: binding.epoch,
|
|
136
|
+
attachmentId: attachment.id,
|
|
137
|
+
attachmentEpoch: attachment.epoch,
|
|
138
|
+
connectionId: connection.id,
|
|
139
|
+
connectionEpoch: connection.epoch,
|
|
140
|
+
presenterPrincipalId: principal,
|
|
141
|
+
presenterGeneration: generation,
|
|
142
|
+
presentationBindingId: registration.id,
|
|
143
|
+
presenterKind: registration.kind,
|
|
144
|
+
purpose: "mcp_form",
|
|
145
|
+
};
|
|
146
|
+
const grant = {
|
|
147
|
+
id: randomUUID(),
|
|
148
|
+
registration,
|
|
149
|
+
scope,
|
|
150
|
+
lifetime: new AbortController(),
|
|
151
|
+
dialogs: new Map(),
|
|
152
|
+
};
|
|
153
|
+
binding.grant = grant;
|
|
154
|
+
for (const listener of binding.listeners)
|
|
155
|
+
listener();
|
|
156
|
+
return { grantId: grant.id, scope };
|
|
157
|
+
},
|
|
158
|
+
revoke: (handle) => {
|
|
159
|
+
const registration = this.registrations.get(handle);
|
|
160
|
+
if (registration?.principal === principal)
|
|
161
|
+
this.revoke(registration);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
receive(sender, value) {
|
|
166
|
+
const connection = this.connections.get(sender);
|
|
167
|
+
const binding = isRecord(value) && typeof value.sessionId === "string" ? this.bindings.get(value.sessionId) : undefined;
|
|
168
|
+
const grant = binding?.grant;
|
|
169
|
+
// Check authority before parsing the private namespace, including invented dialog ids.
|
|
170
|
+
if (!connection || !grant || grant.registration.connection !== connection || !this.current(grant))
|
|
171
|
+
return { error: "presenter_not_authorized" };
|
|
172
|
+
if (!isRecord(value) ||
|
|
173
|
+
value.grantId !== grant.id ||
|
|
174
|
+
value.bindingEpoch !== grant.scope.bindingEpoch ||
|
|
175
|
+
value.attachmentEpoch !== grant.scope.attachmentEpoch)
|
|
176
|
+
return { error: "presenter_not_authorized" };
|
|
177
|
+
const allowed = [
|
|
178
|
+
"type",
|
|
179
|
+
"id",
|
|
180
|
+
"sessionId",
|
|
181
|
+
"bindingEpoch",
|
|
182
|
+
"attachmentEpoch",
|
|
183
|
+
"grantId",
|
|
184
|
+
"dialogId",
|
|
185
|
+
"dialogToken",
|
|
186
|
+
"formRevision",
|
|
187
|
+
];
|
|
188
|
+
if (value.type === "mcp_form_response")
|
|
189
|
+
allowed.push("action", "content");
|
|
190
|
+
if (Object.keys(value).some((key) => !allowed.includes(key)))
|
|
191
|
+
return { error: "invalid_form_response" };
|
|
192
|
+
if (value.type === "mcp_form_sync") {
|
|
193
|
+
const form = grant.dialogs.values().next().value?.sync();
|
|
194
|
+
return form === undefined ? { ok: true } : { ok: true, form };
|
|
195
|
+
}
|
|
196
|
+
if (typeof value.dialogId !== "string" ||
|
|
197
|
+
typeof value.dialogToken !== "string" ||
|
|
198
|
+
typeof value.formRevision !== "string")
|
|
199
|
+
return { error: "invalid_form_response" };
|
|
200
|
+
const dialog = grant.dialogs.get(value.dialogId);
|
|
201
|
+
if (!dialog)
|
|
202
|
+
return { error: "form_stale" };
|
|
203
|
+
if (!dialog.matches(value.dialogToken, value.formRevision))
|
|
204
|
+
return { error: "invalid_form_response" };
|
|
205
|
+
switch (value.type) {
|
|
206
|
+
case "mcp_form_presented":
|
|
207
|
+
return dialog.presented();
|
|
208
|
+
case "mcp_form_response":
|
|
209
|
+
return typeof value.action === "string"
|
|
210
|
+
? dialog.respond(value.action, value.content)
|
|
211
|
+
: { error: "invalid_form_response" };
|
|
212
|
+
default:
|
|
213
|
+
return { error: "invalid_form_response" };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
current(grant) {
|
|
217
|
+
const { registration, scope } = grant;
|
|
218
|
+
const { binding, connection } = registration;
|
|
219
|
+
return (registration.active &&
|
|
220
|
+
!binding.lifetime.signal.aborted &&
|
|
221
|
+
!grant.lifetime.signal.aborted &&
|
|
222
|
+
this.bindings.get(binding.sessionId) === binding &&
|
|
223
|
+
binding.grant === grant &&
|
|
224
|
+
this.connections.get(connection) === connection &&
|
|
225
|
+
connection.attachments.get(binding)?.epoch === scope.attachmentEpoch);
|
|
226
|
+
}
|
|
227
|
+
revoke(registration, operation = false) {
|
|
228
|
+
registration.active = false;
|
|
229
|
+
this.registrations.delete(registration);
|
|
230
|
+
const binding = registration.binding;
|
|
231
|
+
const grant = binding.grant;
|
|
232
|
+
if (!grant || grant.registration !== registration)
|
|
233
|
+
return;
|
|
234
|
+
binding.grant = undefined;
|
|
235
|
+
for (const listener of binding.listeners)
|
|
236
|
+
listener();
|
|
237
|
+
// Retain entry references after authority is fenced. Cleanup never validates the revoked sender.
|
|
238
|
+
for (const dialog of [...grant.dialogs.values()])
|
|
239
|
+
dialog.terminate(operation ? "operation_aborted" : "presentation_lost");
|
|
240
|
+
grant.lifetime.abort();
|
|
241
|
+
}
|
|
242
|
+
presentation(grant) {
|
|
243
|
+
return {
|
|
244
|
+
signal: grant.lifetime.signal,
|
|
245
|
+
ready: () => this.current(grant),
|
|
246
|
+
present: (input) => this.present(grant, input),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
present(grant, input) {
|
|
250
|
+
const unavailable = {
|
|
251
|
+
kind: "processing_failure",
|
|
252
|
+
source: "host_termination",
|
|
253
|
+
humanAdmission: false,
|
|
254
|
+
};
|
|
255
|
+
if (!this.current(grant) ||
|
|
256
|
+
!validateMrtrForm(input.schema) ||
|
|
257
|
+
Object.keys(input.schema.properties).length > 64 ||
|
|
258
|
+
Buffer.byteLength(JSON.stringify({ message: input.message, schema: input.schema })) > 512 * 1024)
|
|
259
|
+
return Promise.resolve(unavailable);
|
|
260
|
+
const binding = grant.registration.binding;
|
|
261
|
+
const deadline = this.clock.now() + input.deadlineMs;
|
|
262
|
+
const expired = new AbortController();
|
|
263
|
+
const timer = this.clock.schedule(() => expired.abort(), Math.max(0, input.deadlineMs));
|
|
264
|
+
const signal = AbortSignal.any([
|
|
265
|
+
input.operationSignal,
|
|
266
|
+
input.presentationSignal,
|
|
267
|
+
grant.lifetime.signal,
|
|
268
|
+
expired.signal,
|
|
269
|
+
]);
|
|
270
|
+
return queuedForm(grant, signal, () => {
|
|
271
|
+
this.clock.cancel(timer);
|
|
272
|
+
if (!this.current(grant))
|
|
273
|
+
return Promise.resolve(unavailable);
|
|
274
|
+
const dialogId = randomUUID();
|
|
275
|
+
const dialog = new McpFormDialog({
|
|
276
|
+
...input,
|
|
277
|
+
presentationSignal: signal,
|
|
278
|
+
deadlineMs: Math.max(0, deadline - this.clock.now()),
|
|
279
|
+
}, {
|
|
280
|
+
clock: this.clock,
|
|
281
|
+
envelope: {
|
|
282
|
+
sessionId: binding.sessionId,
|
|
283
|
+
bindingEpoch: binding.epoch,
|
|
284
|
+
attachmentEpoch: grant.scope.attachmentEpoch,
|
|
285
|
+
grantId: grant.id,
|
|
286
|
+
dialogId,
|
|
287
|
+
formRevision: randomUUID(),
|
|
288
|
+
},
|
|
289
|
+
current: () => this.current(grant),
|
|
290
|
+
remove: () => {
|
|
291
|
+
grant.dialogs.delete(dialogId);
|
|
292
|
+
},
|
|
293
|
+
admit: binding.admit,
|
|
294
|
+
send: grant.registration.connection.send,
|
|
295
|
+
});
|
|
296
|
+
grant.dialogs.set(dialogId, dialog);
|
|
297
|
+
dialog.start();
|
|
298
|
+
return dialog.result;
|
|
299
|
+
})
|
|
300
|
+
.catch(() => ({
|
|
301
|
+
...unavailable,
|
|
302
|
+
kind: input.operationSignal.aborted || binding.signal.aborted || input.operationCurrent?.() === false
|
|
303
|
+
? "operation_aborted"
|
|
304
|
+
: "processing_failure",
|
|
305
|
+
}))
|
|
306
|
+
.finally(() => this.clock.cancel(timer));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=mcp-presenter-authority.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
import { Duplex } from "node:stream";
|
|
3
|
+
export declare const MCP_PRESENTER_BOOTSTRAP_FD = 4;
|
|
4
|
+
/** The descriptor is not authority: only this live channel's enrollment transcript can issue grants. */
|
|
5
|
+
export declare function presenterBootstrapStream(fd: number): Duplex | undefined;
|
|
6
|
+
/** Relay one bounded enrollment exchange; fd 3 and stdin are never touched. */
|
|
7
|
+
export declare function relayPresenterBootstrap(upstream: Duplex, child: ChildProcess): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=mcp-presenter-bootstrap.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createReadStream, fstatSync, write } from "node:fs";
|
|
2
|
+
import { Socket } from "node:net";
|
|
3
|
+
import { Duplex } from "node:stream";
|
|
4
|
+
import { attachJsonlLineReader } from "./jsonl.js";
|
|
5
|
+
export const MCP_PRESENTER_BOOTSTRAP_FD = 4;
|
|
6
|
+
/** The descriptor is not authority: only this live channel's enrollment transcript can issue grants. */
|
|
7
|
+
export function presenterBootstrapStream(fd) {
|
|
8
|
+
if (fd !== MCP_PRESENTER_BOOTSTRAP_FD)
|
|
9
|
+
return undefined;
|
|
10
|
+
try {
|
|
11
|
+
const stat = fstatSync(fd);
|
|
12
|
+
if (!stat.isSocket() && !stat.isFIFO())
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Bootstrap is optional authority, never a reason to terminate the shared host.
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
// One stream owns the descriptor. Separate read/write stream destructors can close it twice.
|
|
20
|
+
const input = createReadStream("", { fd, autoClose: true });
|
|
21
|
+
const stream = new Duplex({
|
|
22
|
+
read() {
|
|
23
|
+
input.resume();
|
|
24
|
+
},
|
|
25
|
+
write(chunk, _encoding, done) {
|
|
26
|
+
if (chunk.length === 0) {
|
|
27
|
+
done();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const send = (offset) => {
|
|
31
|
+
write(fd, chunk, offset, chunk.length - offset, null, (error, count) => {
|
|
32
|
+
if (error)
|
|
33
|
+
done(error);
|
|
34
|
+
else if (count === 0)
|
|
35
|
+
done(new Error("presenter_bootstrap_write_failed"));
|
|
36
|
+
else if (offset + count === chunk.length)
|
|
37
|
+
done();
|
|
38
|
+
else
|
|
39
|
+
send(offset + count);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
send(0);
|
|
43
|
+
},
|
|
44
|
+
destroy(error, done) {
|
|
45
|
+
if (input.closed)
|
|
46
|
+
done(error);
|
|
47
|
+
else {
|
|
48
|
+
input.once("close", () => done(error));
|
|
49
|
+
input.destroy();
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
input.on("data", (chunk) => {
|
|
54
|
+
if (!stream.push(chunk))
|
|
55
|
+
input.pause();
|
|
56
|
+
});
|
|
57
|
+
input.on("end", () => stream.push(null));
|
|
58
|
+
input.on("error", (error) => stream.destroy(error));
|
|
59
|
+
return stream;
|
|
60
|
+
}
|
|
61
|
+
/** Relay one bounded enrollment exchange; fd 3 and stdin are never touched. */
|
|
62
|
+
export function relayPresenterBootstrap(upstream, child) {
|
|
63
|
+
const downstream = child.stdio[MCP_PRESENTER_BOOTSTRAP_FD];
|
|
64
|
+
if (!(downstream instanceof Socket)) {
|
|
65
|
+
upstream.destroy();
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
let ended = false;
|
|
70
|
+
let acknowledging = false;
|
|
71
|
+
const finish = () => {
|
|
72
|
+
if (ended)
|
|
73
|
+
return;
|
|
74
|
+
ended = true;
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
unreadUp();
|
|
77
|
+
unreadDown();
|
|
78
|
+
upstream.destroy();
|
|
79
|
+
downstream.destroy();
|
|
80
|
+
resolve();
|
|
81
|
+
};
|
|
82
|
+
const timer = setTimeout(finish, 30_000);
|
|
83
|
+
const unreadUp = attachJsonlLineReader(upstream, (line) => {
|
|
84
|
+
if (!downstream.write(`${line}\n`))
|
|
85
|
+
finish();
|
|
86
|
+
}, { maxLineLength: 16 * 1024, onOversizedLine: finish });
|
|
87
|
+
const unreadDown = attachJsonlLineReader(downstream, (line) => {
|
|
88
|
+
try {
|
|
89
|
+
const record = JSON.parse(line);
|
|
90
|
+
acknowledging =
|
|
91
|
+
typeof record === "object" && record !== null && "type" in record && record.type === "enrolled";
|
|
92
|
+
upstream.write(`${line}\n`, () => {
|
|
93
|
+
if (acknowledging)
|
|
94
|
+
finish();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
finish();
|
|
99
|
+
}
|
|
100
|
+
}, { maxLineLength: 16 * 1024, onOversizedLine: finish });
|
|
101
|
+
upstream.once("error", finish);
|
|
102
|
+
downstream.once("error", finish);
|
|
103
|
+
upstream.once("end", finish);
|
|
104
|
+
downstream.once("end", () => {
|
|
105
|
+
if (!acknowledging)
|
|
106
|
+
finish();
|
|
107
|
+
});
|
|
108
|
+
child.once("exit", finish);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=mcp-presenter-bootstrap.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Duplex } from "node:stream";
|
|
2
|
+
import type { SessionEventWriter } from "./session-event-writer.ts";
|
|
3
|
+
/** Separate authenticated control endpoint. Ordinary RPC credentials cannot enroll or acquire. */
|
|
4
|
+
export declare class McpPresenterControl {
|
|
5
|
+
private readonly writer;
|
|
6
|
+
private readonly authority;
|
|
7
|
+
private readonly principals;
|
|
8
|
+
private readonly controls;
|
|
9
|
+
private readonly revokeControls;
|
|
10
|
+
private launchOwner?;
|
|
11
|
+
private readonly backendGenerations;
|
|
12
|
+
private readonly successors;
|
|
13
|
+
private readonly server;
|
|
14
|
+
readonly path: string;
|
|
15
|
+
constructor(writer: SessionEventWriter, path: string);
|
|
16
|
+
listen(): Promise<void>;
|
|
17
|
+
/** A deferred handover is spawned by the old host, not the CLI that already returned. */
|
|
18
|
+
successorBootstrap(): Duplex | undefined;
|
|
19
|
+
/** Only the inherited launch channel may introduce the installation public key. */
|
|
20
|
+
bootstrap(stream: Duplex): void;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
private manage;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=mcp-presenter-control.d.ts.map
|