@guuey/mcp-apps-host 0.16.7 → 0.16.9
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/dist/action-staging.d.ts +4 -0
- package/dist/action-staging.d.ts.map +1 -1
- package/dist/action-staging.js +4 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/view-host-protocol.d.ts +26 -0
- package/dist/view-host-protocol.d.ts.map +1 -1
- package/dist/view-host-protocol.js +56 -0
- package/dist/view-host.d.ts +9 -0
- package/dist/view-host.d.ts.map +1 -1
- package/dist/view-host.js +17 -0
- package/package.json +2 -2
- package/src/action-staging.ts +4 -0
- package/src/react.tsx +1 -0
- package/src/view-host-protocol.ts +74 -0
- package/src/view-host.ts +23 -0
package/dist/action-staging.d.ts
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
*
|
|
16
16
|
* A staged answer is deliberately NOT `isError` (guuey#215): queueing is
|
|
17
17
|
* an acceptance, and the error flag fed apps' legacy failure overlays.
|
|
18
|
+
*
|
|
19
|
+
* guuey#404 (the canvas wire ruling): the honest-projection refusal on a
|
|
20
|
+
* REFERENCE-shaped dispatch (`{actionId, intent}` — a correlation hash,
|
|
21
|
+
* params server-side in the consume pipe) is CORRECT behavior, not a gap.
|
|
18
22
|
*/
|
|
19
23
|
import { type McpToolCallResult } from "./action.js";
|
|
20
24
|
import type { UiActionRequest } from "./action.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-staging.d.ts","sourceRoot":"","sources":["../src/action-staging.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"action-staging.d.ts","sourceRoot":"","sources":["../src/action-staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAA4B,KAAK,iBAAiB,EAAiC,MAAM,aAAa,CAAC;AAC9G,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,0CAAqC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,qFAAqF;IACrF,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAiCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAY3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,EAC/D,OAAO,EAAE,aAAa,GAAG,SAAS,GACjC,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAY1D"}
|
package/dist/action-staging.js
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
*
|
|
16
16
|
* A staged answer is deliberately NOT `isError` (guuey#215): queueing is
|
|
17
17
|
* an acceptance, and the error flag fed apps' legacy failure overlays.
|
|
18
|
+
*
|
|
19
|
+
* guuey#404 (the canvas wire ruling): the honest-projection refusal on a
|
|
20
|
+
* REFERENCE-shaped dispatch (`{actionId, intent}` — a correlation hash,
|
|
21
|
+
* params server-side in the consume pipe) is CORRECT behavior, not a gap.
|
|
18
22
|
*/
|
|
19
23
|
import { UI_SEMANTIC_ACTION_TOOLS } from "./action.js";
|
|
20
24
|
/** The staged-acceptance notice a view receives instead of a relay answer. */
|
package/dist/react.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export type { AttachViewHostConfig, ViewCspEvents, ViewFrameLike, ViewHostEvents
|
|
|
41
41
|
export { attachSandboxPageDelivery, isSandboxProxyReady, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, type SandboxPageDeliveryConfig, } from "./sandbox-page.js";
|
|
42
42
|
export type { ViewCspDiagnosis, ViewCspOrigins, ViewHostPhase } from "./view-host-protocol.js";
|
|
43
43
|
export type { ResolvedViewMount, ViewMount, ViewMountChannel } from "./card-mount.js";
|
|
44
|
-
export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabilities" | "hostInfo" | "hostContext" | "onCallTool" | "onUpdateModelContext" | "onUserMessage" | "onReadResource" | "onSizeChanged" | "negotiationTimeoutMs" | "cspOrigins" | "onCspDiagnosis"> {
|
|
44
|
+
export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabilities" | "hostInfo" | "hostContext" | "onCallTool" | "onUpdateModelContext" | "onUserMessage" | "onOpenLink" | "onReadResource" | "onSizeChanged" | "negotiationTimeoutMs" | "cspOrigins" | "onCspDiagnosis"> {
|
|
45
45
|
/** The resolved card to mount (see `toolResultViewMount`/`resolveViewMount`). */
|
|
46
46
|
mount: ResolvedViewMount;
|
|
47
47
|
/**
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjG,OAAO,EAAoC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACzG,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/F,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKtF,MAAM,WAAW,cACf,SAAQ,IAAI,CACV,oBAAoB,EAClB,kBAAkB,GAClB,UAAU,GACV,aAAa,GACb,YAAY,GACZ,sBAAsB,GACtB,eAAe,GACf,gBAAgB,GAChB,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,gBAAgB,CACnB;IACD,iFAAiF;IACjF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,gBAAgB,KAAK,SAAS,CAAC;CAClF;AA8CD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CA0K1D"}
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjG,OAAO,EAAoC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACzG,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/F,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKtF,MAAM,WAAW,cACf,SAAQ,IAAI,CACV,oBAAoB,EAClB,kBAAkB,GAClB,UAAU,GACV,aAAa,GACb,YAAY,GACZ,sBAAsB,GACtB,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,gBAAgB,CACnB;IACD,iFAAiF;IACjF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,gBAAgB,KAAK,SAAS,CAAC;CAClF;AA8CD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CA0K1D"}
|
|
@@ -72,6 +72,17 @@ export declare const MESSAGE_METHOD_LOCAL = "ui/message";
|
|
|
72
72
|
* no `@modelcontextprotocol/sdk` dependency.
|
|
73
73
|
*/
|
|
74
74
|
export declare const RESOURCES_READ_METHOD = "resources/read";
|
|
75
|
+
/**
|
|
76
|
+
* `ui/open-link` (guuey#522) — a view asks the HOST to open a URL in a
|
|
77
|
+
* new browsing context. The machine owns the SECURITY wall: only
|
|
78
|
+
* absolute http/https URLs pass (the #515-family policy, platform-
|
|
79
|
+
* narrowed — mailto and every other scheme refuse in-band with -32602);
|
|
80
|
+
* a valid ask answers `{}` FIRST and surfaces an `open-link` effect. The
|
|
81
|
+
* kit's default executor never navigates sight-unseen: it SHOWS the URL
|
|
82
|
+
* to the human and opens only on their own click (a real gesture — the
|
|
83
|
+
* browser's activation gate and ours agree by construction).
|
|
84
|
+
*/
|
|
85
|
+
export declare const OPEN_LINK_METHOD_LOCAL = "ui/open-link";
|
|
75
86
|
/** A JSON-RPC id as the wire allows it. */
|
|
76
87
|
export type ViewRequestId = number | string;
|
|
77
88
|
/** The messages this host posts INTO the view frame. */
|
|
@@ -173,6 +184,16 @@ export type ViewHostEffect = {
|
|
|
173
184
|
params: {
|
|
174
185
|
[key: string]: unknown;
|
|
175
186
|
};
|
|
187
|
+
} | {
|
|
188
|
+
/**
|
|
189
|
+
* The view asked to open a URL (`ui/open-link`, guuey#522) and the
|
|
190
|
+
* machine's scheme wall passed it (absolute http/https only). Only
|
|
191
|
+
* ever emitted when {@link ViewHostBehavior.linkOpener} is true —
|
|
192
|
+
* the response was already sent; the executor SURFACES the URL to
|
|
193
|
+
* the human and opens on their own gesture, never sight-unseen.
|
|
194
|
+
*/
|
|
195
|
+
kind: "open-link";
|
|
196
|
+
url: string;
|
|
176
197
|
} | {
|
|
177
198
|
/**
|
|
178
199
|
* The view reported its content size (`ui/notifications/size-changed`
|
|
@@ -210,6 +231,11 @@ export interface ViewHostBehavior {
|
|
|
210
231
|
modelContextSink: boolean;
|
|
211
232
|
/** Whether a ui/message sink is wired (see `view-host.ts`, guuey#422). */
|
|
212
233
|
messageSink: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* `ui/open-link` wired (guuey#522). Unwired, the method refuses with
|
|
236
|
+
* the honest -32601 — the host chose today's no-links posture.
|
|
237
|
+
*/
|
|
238
|
+
linkOpener: boolean;
|
|
213
239
|
/** Whether a `resources/read` relay hook is wired (see `view-host.ts`). */
|
|
214
240
|
resourceRelay: boolean;
|
|
215
241
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-host-protocol.d.ts","sourceRoot":"","sources":["../src/view-host-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAKL,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAKD,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAE9C;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"view-host-protocol.d.ts","sourceRoot":"","sources":["../src/view-host-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -63,6 +63,17 @@ export const MESSAGE_METHOD_LOCAL = "ui/message";
|
|
|
63
63
|
* no `@modelcontextprotocol/sdk` dependency.
|
|
64
64
|
*/
|
|
65
65
|
export const RESOURCES_READ_METHOD = "resources/read";
|
|
66
|
+
/**
|
|
67
|
+
* `ui/open-link` (guuey#522) — a view asks the HOST to open a URL in a
|
|
68
|
+
* new browsing context. The machine owns the SECURITY wall: only
|
|
69
|
+
* absolute http/https URLs pass (the #515-family policy, platform-
|
|
70
|
+
* narrowed — mailto and every other scheme refuse in-band with -32602);
|
|
71
|
+
* a valid ask answers `{}` FIRST and surfaces an `open-link` effect. The
|
|
72
|
+
* kit's default executor never navigates sight-unseen: it SHOWS the URL
|
|
73
|
+
* to the human and opens only on their own click (a real gesture — the
|
|
74
|
+
* browser's activation gate and ours agree by construction).
|
|
75
|
+
*/
|
|
76
|
+
export const OPEN_LINK_METHOD_LOCAL = "ui/open-link";
|
|
66
77
|
/**
|
|
67
78
|
* MCP's `Resource not found` JSON-RPC code — the one answer for a miss, a
|
|
68
79
|
* transport deny, AND a relay failure (deny == miss: the reader discipline,
|
|
@@ -224,6 +235,51 @@ export function viewHostReceive(state, behavior, data) {
|
|
|
224
235
|
],
|
|
225
236
|
};
|
|
226
237
|
}
|
|
238
|
+
if (req.method === OPEN_LINK_METHOD_LOCAL && behavior.linkOpener) {
|
|
239
|
+
// The machine validates BEFORE anything reaches an executor: absolute
|
|
240
|
+
// http/https only. A refusal is in-band (-32602 naming the policy) and
|
|
241
|
+
// emits NO effect — no silent arbitrary-scheme opens, and the view
|
|
242
|
+
// learns honestly instead of wondering.
|
|
243
|
+
const params = isPlainObject(req.params) ? req.params : {};
|
|
244
|
+
const url = typeof params.url === "string" ? params.url : null;
|
|
245
|
+
let allowed = false;
|
|
246
|
+
if (url !== null) {
|
|
247
|
+
try {
|
|
248
|
+
const parsed = new URL(url);
|
|
249
|
+
allowed = parsed.protocol === "https:" || parsed.protocol === "http:";
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
allowed = false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (url === null || !allowed) {
|
|
256
|
+
return {
|
|
257
|
+
state,
|
|
258
|
+
effects: [
|
|
259
|
+
{
|
|
260
|
+
kind: "respond",
|
|
261
|
+
message: {
|
|
262
|
+
jsonrpc: "2.0",
|
|
263
|
+
id: req.id,
|
|
264
|
+
error: {
|
|
265
|
+
code: -32602,
|
|
266
|
+
message: "ui/open-link: `url` must be an absolute http/https URL — other schemes are refused by host policy.",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// Answer accepted FIRST (the spec result is empty) and surface the ask
|
|
274
|
+
// as an effect — the view never waits on the human's decision.
|
|
275
|
+
return {
|
|
276
|
+
state,
|
|
277
|
+
effects: [
|
|
278
|
+
{ kind: "respond", message: { jsonrpc: "2.0", id: req.id, result: {} } },
|
|
279
|
+
{ kind: "open-link", url },
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
227
283
|
if (req.method === MESSAGE_METHOD_LOCAL && behavior.messageSink) {
|
|
228
284
|
// Answer accepted FIRST (spec result: rejected-flag absent = delivered)
|
|
229
285
|
// and deliver as an effect — the sink starting a turn must never make
|
package/dist/view-host.d.ts
CHANGED
|
@@ -120,6 +120,15 @@ export interface AttachViewHostConfig {
|
|
|
120
120
|
onUserMessage?: (params: {
|
|
121
121
|
[key: string]: unknown;
|
|
122
122
|
}) => void;
|
|
123
|
+
/**
|
|
124
|
+
* `ui/open-link` executor (guuey#522) — receives a URL the machine's
|
|
125
|
+
* scheme wall already passed (absolute http/https only). The kit
|
|
126
|
+
* default SURFACES the URL to the human and opens on their own click;
|
|
127
|
+
* a custom executor should keep that posture (never navigate
|
|
128
|
+
* sight-unseen). `false` opts the host OUT: the capability is not
|
|
129
|
+
* advertised and the method refuses honestly.
|
|
130
|
+
*/
|
|
131
|
+
onOpenLink?: ((url: string) => void) | false;
|
|
123
132
|
/**
|
|
124
133
|
* The mounted resource's `ui://` locator — the scope stamped on every
|
|
125
134
|
* relayed {@link UiActionRequest}. Required for the relay to fire;
|
package/dist/view-host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-host.d.ts","sourceRoot":"","sources":["../src/view-host.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"view-host.d.ts","sourceRoot":"","sources":["../src/view-host.ts"],"names":[],"mappings":"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"}
|
package/dist/view-host.js
CHANGED
|
@@ -46,6 +46,9 @@ function behaviorFor(frame, config) {
|
|
|
46
46
|
const readWired = config.onReadResource !== undefined;
|
|
47
47
|
const contextSinkWired = config.onUpdateModelContext !== undefined;
|
|
48
48
|
const messageSinkWired = config.onUserMessage !== undefined;
|
|
49
|
+
// guuey#522: `false` is the explicit opt-OUT (the founder's on-by-default
|
|
50
|
+
// ruling inverts the usual polarity — hosts choose to REMOVE links).
|
|
51
|
+
const linkOpenerWired = config.onOpenLink !== undefined && config.onOpenLink !== false;
|
|
49
52
|
return {
|
|
50
53
|
hostInfo: config.hostInfo ?? DEFAULT_HOST_INFO,
|
|
51
54
|
hostCapabilities: {
|
|
@@ -55,10 +58,12 @@ function behaviorFor(frame, config) {
|
|
|
55
58
|
...(readWired ? { serverResources: {} } : {}),
|
|
56
59
|
...(contextSinkWired ? { updateModelContext: { text: {} } } : {}),
|
|
57
60
|
...(messageSinkWired ? { message: { text: {} } } : {}),
|
|
61
|
+
...(linkOpenerWired ? { openLinks: {} } : {}),
|
|
58
62
|
...config.hostCapabilities,
|
|
59
63
|
},
|
|
60
64
|
hostContext: hostContextFor(frame, config),
|
|
61
65
|
toolRelay: relayWired,
|
|
66
|
+
linkOpener: linkOpenerWired,
|
|
62
67
|
resourceRelay: readWired,
|
|
63
68
|
modelContextSink: contextSinkWired,
|
|
64
69
|
messageSink: messageSinkWired,
|
|
@@ -143,6 +148,18 @@ export function attachViewHost(frame, config = {}) {
|
|
|
143
148
|
// Observer failure is the embedder's bug; the view is answered.
|
|
144
149
|
}
|
|
145
150
|
}
|
|
151
|
+
else if (effect.kind === "open-link") {
|
|
152
|
+
// The machine's scheme wall already passed this URL; the executor
|
|
153
|
+
// SURFACES it to the human (the kit default renders a disclosure
|
|
154
|
+
// affordance — navigation only ever happens on their own click).
|
|
155
|
+
try {
|
|
156
|
+
if (typeof config.onOpenLink === "function")
|
|
157
|
+
config.onOpenLink(effect.url);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Observer failure is the embedder's bug; the view is answered.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
146
163
|
else if (effect.kind === "model-context-update") {
|
|
147
164
|
// Delivery only — the machine already answered the view. Contained:
|
|
148
165
|
// a throwing embedder sink must not unwind the message pump.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guuey/mcp-apps-host",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.9",
|
|
4
4
|
"description": "The MCP Apps (SEP-1865) Host role for guuey's chat surfaces — view-mount narrowing across UI channels, ui:// locator rehydration by resources/read, and the sandbox-trust channel contract. Vendor-neutral: any spec-following MCP App mounts through it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@ggui-ai/protocol": "0.12.0",
|
|
37
37
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
38
|
-
"@silverprotocol/core": "0.5.
|
|
38
|
+
"@silverprotocol/core": "0.5.3"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": ">=18"
|
package/src/action-staging.ts
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
*
|
|
16
16
|
* A staged answer is deliberately NOT `isError` (guuey#215): queueing is
|
|
17
17
|
* an acceptance, and the error flag fed apps' legacy failure overlays.
|
|
18
|
+
*
|
|
19
|
+
* guuey#404 (the canvas wire ruling): the honest-projection refusal on a
|
|
20
|
+
* REFERENCE-shaped dispatch (`{actionId, intent}` — a correlation hash,
|
|
21
|
+
* params server-side in the consume pipe) is CORRECT behavior, not a gap.
|
|
18
22
|
*/
|
|
19
23
|
import { UI_SEMANTIC_ACTION_TOOLS, type McpToolCallResult, type McpToolStructuredContent } from "./action.js";
|
|
20
24
|
import type { UiActionRequest } from "./action.js";
|
package/src/react.tsx
CHANGED
|
@@ -90,6 +90,18 @@ export const MESSAGE_METHOD_LOCAL = "ui/message";
|
|
|
90
90
|
*/
|
|
91
91
|
export const RESOURCES_READ_METHOD = "resources/read";
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* `ui/open-link` (guuey#522) — a view asks the HOST to open a URL in a
|
|
95
|
+
* new browsing context. The machine owns the SECURITY wall: only
|
|
96
|
+
* absolute http/https URLs pass (the #515-family policy, platform-
|
|
97
|
+
* narrowed — mailto and every other scheme refuse in-band with -32602);
|
|
98
|
+
* a valid ask answers `{}` FIRST and surfaces an `open-link` effect. The
|
|
99
|
+
* kit's default executor never navigates sight-unseen: it SHOWS the URL
|
|
100
|
+
* to the human and opens only on their own click (a real gesture — the
|
|
101
|
+
* browser's activation gate and ours agree by construction).
|
|
102
|
+
*/
|
|
103
|
+
export const OPEN_LINK_METHOD_LOCAL = "ui/open-link";
|
|
104
|
+
|
|
93
105
|
/**
|
|
94
106
|
* MCP's `Resource not found` JSON-RPC code — the one answer for a miss, a
|
|
95
107
|
* transport deny, AND a relay failure (deny == miss: the reader discipline,
|
|
@@ -197,6 +209,17 @@ export type ViewHostEffect =
|
|
|
197
209
|
id: ViewRequestId;
|
|
198
210
|
params: { [key: string]: unknown };
|
|
199
211
|
}
|
|
212
|
+
| {
|
|
213
|
+
/**
|
|
214
|
+
* The view asked to open a URL (`ui/open-link`, guuey#522) and the
|
|
215
|
+
* machine's scheme wall passed it (absolute http/https only). Only
|
|
216
|
+
* ever emitted when {@link ViewHostBehavior.linkOpener} is true —
|
|
217
|
+
* the response was already sent; the executor SURFACES the URL to
|
|
218
|
+
* the human and opens on their own gesture, never sight-unseen.
|
|
219
|
+
*/
|
|
220
|
+
kind: "open-link";
|
|
221
|
+
url: string;
|
|
222
|
+
}
|
|
200
223
|
| {
|
|
201
224
|
/**
|
|
202
225
|
* The view reported its content size (`ui/notifications/size-changed`
|
|
@@ -235,6 +258,11 @@ export interface ViewHostBehavior {
|
|
|
235
258
|
modelContextSink: boolean;
|
|
236
259
|
/** Whether a ui/message sink is wired (see `view-host.ts`, guuey#422). */
|
|
237
260
|
messageSink: boolean;
|
|
261
|
+
/**
|
|
262
|
+
* `ui/open-link` wired (guuey#522). Unwired, the method refuses with
|
|
263
|
+
* the honest -32601 — the host chose today's no-links posture.
|
|
264
|
+
*/
|
|
265
|
+
linkOpener: boolean;
|
|
238
266
|
/** Whether a `resources/read` relay hook is wired (see `view-host.ts`). */
|
|
239
267
|
resourceRelay: boolean;
|
|
240
268
|
}
|
|
@@ -424,6 +452,52 @@ export function viewHostReceive(
|
|
|
424
452
|
};
|
|
425
453
|
}
|
|
426
454
|
|
|
455
|
+
if (req.method === OPEN_LINK_METHOD_LOCAL && behavior.linkOpener) {
|
|
456
|
+
// The machine validates BEFORE anything reaches an executor: absolute
|
|
457
|
+
// http/https only. A refusal is in-band (-32602 naming the policy) and
|
|
458
|
+
// emits NO effect — no silent arbitrary-scheme opens, and the view
|
|
459
|
+
// learns honestly instead of wondering.
|
|
460
|
+
const params = isPlainObject(req.params) ? req.params : {};
|
|
461
|
+
const url = typeof params.url === "string" ? params.url : null;
|
|
462
|
+
let allowed = false;
|
|
463
|
+
if (url !== null) {
|
|
464
|
+
try {
|
|
465
|
+
const parsed = new URL(url);
|
|
466
|
+
allowed = parsed.protocol === "https:" || parsed.protocol === "http:";
|
|
467
|
+
} catch {
|
|
468
|
+
allowed = false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (url === null || !allowed) {
|
|
472
|
+
return {
|
|
473
|
+
state,
|
|
474
|
+
effects: [
|
|
475
|
+
{
|
|
476
|
+
kind: "respond",
|
|
477
|
+
message: {
|
|
478
|
+
jsonrpc: "2.0",
|
|
479
|
+
id: req.id,
|
|
480
|
+
error: {
|
|
481
|
+
code: -32602,
|
|
482
|
+
message:
|
|
483
|
+
"ui/open-link: `url` must be an absolute http/https URL — other schemes are refused by host policy.",
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
// Answer accepted FIRST (the spec result is empty) and surface the ask
|
|
491
|
+
// as an effect — the view never waits on the human's decision.
|
|
492
|
+
return {
|
|
493
|
+
state,
|
|
494
|
+
effects: [
|
|
495
|
+
{ kind: "respond", message: { jsonrpc: "2.0", id: req.id, result: {} } },
|
|
496
|
+
{ kind: "open-link", url },
|
|
497
|
+
],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
427
501
|
if (req.method === MESSAGE_METHOD_LOCAL && behavior.messageSink) {
|
|
428
502
|
// Answer accepted FIRST (spec result: rejected-flag absent = delivered)
|
|
429
503
|
// and deliver as an effect — the sink starting a turn must never make
|
package/src/view-host.ts
CHANGED
|
@@ -138,6 +138,15 @@ export interface AttachViewHostConfig {
|
|
|
138
138
|
* (text modality); the machine answers the view BEFORE delivery.
|
|
139
139
|
*/
|
|
140
140
|
onUserMessage?: (params: { [key: string]: unknown }) => void;
|
|
141
|
+
/**
|
|
142
|
+
* `ui/open-link` executor (guuey#522) — receives a URL the machine's
|
|
143
|
+
* scheme wall already passed (absolute http/https only). The kit
|
|
144
|
+
* default SURFACES the URL to the human and opens on their own click;
|
|
145
|
+
* a custom executor should keep that posture (never navigate
|
|
146
|
+
* sight-unseen). `false` opts the host OUT: the capability is not
|
|
147
|
+
* advertised and the method refuses honestly.
|
|
148
|
+
*/
|
|
149
|
+
onOpenLink?: ((url: string) => void) | false;
|
|
141
150
|
/**
|
|
142
151
|
* The mounted resource's `ui://` locator — the scope stamped on every
|
|
143
152
|
* relayed {@link UiActionRequest}. Required for the relay to fire;
|
|
@@ -230,6 +239,9 @@ function behaviorFor(frame: ViewFrameLike, config: AttachViewHostConfig): ViewHo
|
|
|
230
239
|
const readWired = config.onReadResource !== undefined;
|
|
231
240
|
const contextSinkWired = config.onUpdateModelContext !== undefined;
|
|
232
241
|
const messageSinkWired = config.onUserMessage !== undefined;
|
|
242
|
+
// guuey#522: `false` is the explicit opt-OUT (the founder's on-by-default
|
|
243
|
+
// ruling inverts the usual polarity — hosts choose to REMOVE links).
|
|
244
|
+
const linkOpenerWired = config.onOpenLink !== undefined && config.onOpenLink !== false;
|
|
233
245
|
return {
|
|
234
246
|
hostInfo: config.hostInfo ?? DEFAULT_HOST_INFO,
|
|
235
247
|
hostCapabilities: {
|
|
@@ -239,10 +251,12 @@ function behaviorFor(frame: ViewFrameLike, config: AttachViewHostConfig): ViewHo
|
|
|
239
251
|
...(readWired ? { serverResources: {} } : {}),
|
|
240
252
|
...(contextSinkWired ? { updateModelContext: { text: {} } } : {}),
|
|
241
253
|
...(messageSinkWired ? { message: { text: {} } } : {}),
|
|
254
|
+
...(linkOpenerWired ? { openLinks: {} } : {}),
|
|
242
255
|
...config.hostCapabilities,
|
|
243
256
|
},
|
|
244
257
|
hostContext: hostContextFor(frame, config),
|
|
245
258
|
toolRelay: relayWired,
|
|
259
|
+
linkOpener: linkOpenerWired,
|
|
246
260
|
resourceRelay: readWired,
|
|
247
261
|
modelContextSink: contextSinkWired,
|
|
248
262
|
messageSink: messageSinkWired,
|
|
@@ -328,6 +342,15 @@ export function attachViewHost(frame: ViewFrameLike, config: AttachViewHostConfi
|
|
|
328
342
|
} catch {
|
|
329
343
|
// Observer failure is the embedder's bug; the view is answered.
|
|
330
344
|
}
|
|
345
|
+
} else if (effect.kind === "open-link") {
|
|
346
|
+
// The machine's scheme wall already passed this URL; the executor
|
|
347
|
+
// SURFACES it to the human (the kit default renders a disclosure
|
|
348
|
+
// affordance — navigation only ever happens on their own click).
|
|
349
|
+
try {
|
|
350
|
+
if (typeof config.onOpenLink === "function") config.onOpenLink(effect.url);
|
|
351
|
+
} catch {
|
|
352
|
+
// Observer failure is the embedder's bug; the view is answered.
|
|
353
|
+
}
|
|
331
354
|
} else if (effect.kind === "model-context-update") {
|
|
332
355
|
// Delivery only — the machine already answered the view. Contained:
|
|
333
356
|
// a throwing embedder sink must not unwind the message pump.
|