@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.
@@ -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;;;;;;;;;;;;;;;;;GAiBG;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"}
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"}
@@ -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
  /**
@@ -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;AAStD,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,aAAa,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IACpC,MAAM,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IACpC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,WAAW,GAAG,cAAc,CAAC;AAEzE,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,0EAA0E;IAC1E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAEpD;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GAC9C;IACE;;;;;;OAMG;IACH,IAAI,EAAE,iBAAiB,CAAC;IACxB,EAAE,EAAE,aAAa,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,wBAAwB,CAAC;CACtC,GACD;IACE;;;;;;;OAOG;IACH,IAAI,EAAE,qBAAqB,CAAC;IAC5B,EAAE,EAAE,aAAa,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,GACD;IACE;;;;;;;OAOG;IACH,IAAI,EAAE,sBAAsB,CAAC;IAC7B,MAAM,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CACpC,GACD;IACE;;;;;;;OAOG;IACH,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,aAAa,CAAC;IAClB,MAAM,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CACpC,GACD;IACE;;;;;;;;OAQG;IACH,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB;;;;;;;OAOG;IACH,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,+DAA+D;IAC/D,WAAW,EAAE,gBAAgB,CAAC;IAC9B,uEAAuE;IACvE,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAiCD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,wBAAwB,EAAE,OAAO,GAChC,qBAAqB,CAcvB;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,aAAa,EACjB,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GACjC,gBAAgB,CAElB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,qBAAqB,GAAG,SAAS,GACvC,gBAAgB,CASlB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,CAElD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,OAAO,GACZ,kBAAkB,CAsHpB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAEnE;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,0FAA0F;IAC1F,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,cAAc,GAAG,SAAS,GAClC,gBAAgB,GAAG,SAAS,CAqB9B"}
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
@@ -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;
@@ -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.7",
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.2"
38
+ "@silverprotocol/core": "0.5.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18"
@@ -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
@@ -62,6 +62,7 @@ export interface GuueyViewProps
62
62
  | "onCallTool"
63
63
  | "onUpdateModelContext"
64
64
  | "onUserMessage"
65
+ | "onOpenLink"
65
66
  | "onReadResource"
66
67
  | "onSizeChanged"
67
68
  | "negotiationTimeoutMs"
@@ -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.