@guuey/mcp-apps-host 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,7 @@
41
41
  * from the response and never from mount material a producer inlined.
42
42
  */
43
43
  import { type McpUiResourcePayload } from "./block-ui.js";
44
+ import type { McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
44
45
  import type { AgBlock, JsonValue } from "@silverprotocol/core";
45
46
  /**
46
47
  * Which sandbox-trust channel a mount rides. See this module's header.
@@ -77,6 +78,16 @@ export interface ResolvedViewMount {
77
78
  channel: "inline" | "ggui";
78
79
  /** The payload an mcp-ui host mounts, identical in shape for both channels. */
79
80
  resource: McpUiResourcePayload;
81
+ /**
82
+ * The server's per-resource CSP declaration (`_meta.ui.csp` on the
83
+ * `resources/read` result — MCP Apps SEP-1865; guuey#312), when the read
84
+ * path saw one. Spec-schema-validated at the reader
85
+ * (`declaredResourceCsp`) — never fabricated, absent = undeclared. This
86
+ * is the generic successor to the uri-prefix channel heuristic: hosts
87
+ * derive per-card sandbox CSP / tripwire filters from it
88
+ * (`<GuueyView>` defaults its `cspOrigins` from here).
89
+ */
90
+ csp?: McpUiResourceCsp;
80
91
  }
81
92
  /** The durable-identity arm: no mount material, only the uri to re-fetch. */
82
93
  export interface LocatorViewMount {
@@ -1 +1 @@
1
- {"version":3,"file":"card-mount.d.ts","sourceRoot":"","sources":["../src/card-mount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAA+D,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACvH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,SAAS,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,oBAAoB,CAAC;CAChC;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,SAAS,CAAC;IACnB,4GAA4G;IAC5G,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAEvF;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC,GAC/C,SAAS,GAAG,SAAS,CAKvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAUhF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAKxC"}
1
+ {"version":3,"file":"card-mount.d.ts","sourceRoot":"","sources":["../src/card-mount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAA+D,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACvH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,SAAS,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,EAAE,oBAAoB,CAAC;IAC/B;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,SAAS,CAAC;IACnB,4GAA4G;IAC5G,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAEvF;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC,GAC/C,SAAS,GAAG,SAAS,CAKvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAUhF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAKxC"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  */
6
6
  export { asResourcePayload, asUiResource, blockUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, snapshotUiResource, toolResultLocator, toolResultUiResource, uiLocator, type McpUiResourcePayload, } from "./block-ui.js";
7
7
  export { resolveViewMount, snapshotViewMount, toolResultViewMount, type LocatorViewMount, type ResolvedViewMount, type UiResourceReader, type ViewMount, type ViewMountChannel, } from "./card-mount.js";
8
- export { createMcpUiResourceReader, uiResourceChannel, type CreateMcpUiResourceReaderDeps, type McpResourceReadResult, } from "./reader.js";
8
+ export { createMcpUiResourceReader, declaredResourceCsp, uiResourceChannel, type CreateMcpUiResourceReaderDeps, type McpResourceReadResult, } from "./reader.js";
9
+ export type { McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
9
10
  export { asToolCallResult, createMcpUiActionRelay, unavailableToolCallResult, UI_ACTION_TOOLS, UI_ACTION_UNAVAILABLE_TEXT, UI_SEMANTIC_ACTION_TOOLS, type CreateMcpUiActionRelayDeps, type McpToolCallContent, type McpToolCallResult, type McpToolStructuredContent, type UiActionRequest, } from "./action.js";
10
11
  export { initializeResult, initialViewHostState, resourceReadResponse, RESOURCES_READ_METHOD, teardownMessage, toolCallResponse, TOOLS_CALL_METHOD, viewHostElapsed, viewHostReceive, diagnoseCspViolation, type CspViolationLike, type ViewCspDiagnosis, type ViewCspOrigins, type ViewHostBehavior, type ViewHostEffect, type ViewHostOutbound, type ViewHostPhase, type ViewHostInfo, type ViewHostState, type ViewHostTransition, type ViewRequestId, } from "./view-host-protocol.js";
11
12
  export { attachViewHost, viewDocumentHtml, type AttachViewHostConfig, type ViewCspEvents, type ViewFrameLike, type ViewHostEvents, } from "./view-host.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,4BAA4B,EAC5B,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC;AAIrB,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EACf,0BAA0B,EAC1B,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export { asResourcePayload, asUiResource, blockUiResource, isJsonObject, resourceHtml, scanProviderRawForUiResource, snapshotUiResource, toolResultLocator, toolResultUiResource, uiLocator, } from "./block-ui.js";
7
7
  export { resolveViewMount, snapshotViewMount, toolResultViewMount, } from "./card-mount.js";
8
- export { createMcpUiResourceReader, uiResourceChannel, } from "./reader.js";
8
+ export { createMcpUiResourceReader, declaredResourceCsp, uiResourceChannel, } from "./reader.js";
9
9
  export { asToolCallResult, createMcpUiActionRelay, unavailableToolCallResult, UI_ACTION_TOOLS, UI_ACTION_UNAVAILABLE_TEXT, UI_SEMANTIC_ACTION_TOOLS, } from "./action.js";
10
10
  export { initializeResult, initialViewHostState, resourceReadResponse, RESOURCES_READ_METHOD, teardownMessage, toolCallResponse, TOOLS_CALL_METHOD, viewHostElapsed, viewHostReceive, diagnoseCspViolation, } from "./view-host-protocol.js";
11
11
  export { attachViewHost, viewDocumentHtml, } from "./view-host.js";
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" | "onReadResource" | "onSizeChanged" | "negotiationTimeoutMs" | "cspOrigins" | "onCspDiagnosis"> {
44
+ export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabilities" | "hostInfo" | "hostContext" | "onCallTool" | "onUpdateModelContext" | "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,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,CAmK1D"}
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,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"}
package/dist/react.js CHANGED
@@ -124,8 +124,15 @@ export function GuueyView(props) {
124
124
  if (sandboxPageUrl !== undefined && page === undefined)
125
125
  return; // refused config — nothing mounts
126
126
  const resourceUri = mount.resource.uri;
127
+ // guuey#312: the resolved mount's DECLARED per-resource CSP
128
+ // (`_meta.ui.csp`, spec-schema-validated at the reader) is the CSP
129
+ // tripwire's default filter — the declaration finally doing host-side
130
+ // work. An explicit `cspOrigins` prop always wins; both absent keeps
131
+ // the tripwire inert exactly as before.
132
+ const cspOrigins = latest.current.hostConfig.cspOrigins ?? mount.csp;
127
133
  const detachHost = attachViewHost(frame, {
128
134
  ...latest.current.hostConfig,
135
+ ...(cspOrigins !== undefined ? { cspOrigins } : {}),
129
136
  resourceUri,
130
137
  onPhaseChange: (next) => {
131
138
  setPhase(next);
package/dist/reader.d.ts CHANGED
@@ -1,10 +1,40 @@
1
+ /**
2
+ * The generic SEP-1865 locator reader (guuey#127): resolve a persisted
3
+ * `ui://` locator ({@link LocatorViewMount}) into a mountable
4
+ * {@link ResolvedViewMount} by ONE fresh `resources/read` over a transport
5
+ * the HOST supplies.
6
+ *
7
+ * The transport is a structural callable — deliberately no
8
+ * `@modelcontextprotocol/sdk` dependency — because hosts hold wildly
9
+ * different connections: a raw MCP client (ggui's `with-guuey-web` sample
10
+ * keeps one for guest `tools/call` relay), an authenticated platform proxy
11
+ * (`@guuey/agent-client`'s `createUiResourceReader` is exactly this assembly
12
+ * over guuey's `GET /v1/threads/:threadId/ui-resource` route), or anything
13
+ * else that can perform one read. The host adapts whatever client it holds.
14
+ *
15
+ * Trust rules, identical to the platform reader and now in ONE place:
16
+ *
17
+ * - a transport deny, a miss, and a thrown transport error are all
18
+ * `undefined` → the host renders its placeholder, never an error surface
19
+ * (deny == miss — no oracle for which locators resolve);
20
+ * - enforcement (user-ownership, tenancy, credential handling) belongs
21
+ * INSIDE the transport, before any bytes come back — this assembly never
22
+ * sees a credential;
23
+ * - the sandbox-trust channel derives from the REQUESTED locator uri, never
24
+ * from the response — a server answering with a foreign `ui://ggui/…` uri
25
+ * must not steer its HTML into the ggui-CSP host page.
26
+ */
27
+ import { type McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
1
28
  import type { ResolvedViewMount, ViewMountChannel } from "./card-mount.js";
2
29
  /**
3
30
  * One `resources/read` `contents[]` entry, structurally (SEP-1865). Kept a
4
31
  * separate type from {@link McpUiResourcePayload} even though the shapes
5
32
  * coincide today: this is the UNVALIDATED wire-side entry a transport hands
6
- * over (phase 2 grows `_meta` here per-resource declared CSP, conformance
7
- * map step 3), while the payload is the narrowed thing a host mounts.
33
+ * over, while the payload is the narrowed thing a host mounts. `_meta` is
34
+ * phase 2's growth (guuey#312, conformance map step 3): the per-resource
35
+ * declarations (`_meta.ui.csp` — MCP Apps SEP-1865) ride here when the
36
+ * transport forwards them; {@link declaredResourceCsp} is the ONLY door
37
+ * from this open extension map into typed material.
8
38
  */
9
39
  export interface McpResourceReadResult {
10
40
  uri: string;
@@ -12,14 +42,32 @@ export interface McpResourceReadResult {
12
42
  text?: string;
13
43
  /** Base64 payload arm — hosts decode via {@link resourceHtml}. */
14
44
  blob?: string;
45
+ /**
46
+ * The entry's `_meta` as the transport saw it — an open extension map,
47
+ * so genuinely unknown-shaped at this boundary; never read it directly,
48
+ * narrow through {@link declaredResourceCsp}.
49
+ */
50
+ _meta?: unknown;
15
51
  }
52
+ /**
53
+ * The server's per-resource CSP declaration from a read result's
54
+ * `_meta.ui.csp` (guuey#312 — the phase-2 channel `uiResourceChannel`'s
55
+ * docs promised). Validated against the SPEC's own schema
56
+ * (`McpUiResourceCspSchema` from `@modelcontextprotocol/ext-apps`), never
57
+ * a hand-rolled mirror. Malformed or absent → `undefined` — an undeclared
58
+ * resource is the secure default (consumers only ever use a declaration to
59
+ * build allowances; absence widens nothing).
60
+ */
61
+ export declare function declaredResourceCsp(entry: McpResourceReadResult): McpUiResourceCsp | undefined;
16
62
  /**
17
63
  * Phase-1 sandbox-trust channel resolution (conformance map, retirement
18
64
  * step 3): a `ui://ggui/…` locator mounts through the ggui-CSP host page
19
65
  * (`"ggui"` — the shell boots ggui's runtime bundle and opens its WSS),
20
66
  * anything else through the self-only page (`"inline"` — arbitrary tenant
21
- * HTML). Retired when servers declare runtime/ws origins per resource
22
- * (`_meta.ui.csp` on the read result); until then the uri prefix is the only
67
+ * HTML). The declaration itself ships (guuey#312 `ResolvedViewMount.csp`
68
+ * via {@link declaredResourceCsp}); this heuristic retires when hosts route
69
+ * SANDBOX TRUST from that declaration too (per-card CSP construction, the
70
+ * conformance map's step-3 second half). Until then the uri prefix is the only
23
71
  * signal a host has BEFORE fetching mount material.
24
72
  */
25
73
  export declare function uiResourceChannel(resourceUri: string): Exclude<ViewMountChannel, "locator">;
@@ -1 +1 @@
1
- {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAE3F;AAED,oFAAoF;AACpF,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;CAC3E;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,6BAA6B,GAClC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAsBjE"}
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAE/F,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE3E;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,SAAS,CAW9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAE3F;AAED,oFAAoF;AACpF,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;CAC3E;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,6BAA6B,GAClC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CA+BjE"}
package/dist/reader.js CHANGED
@@ -1,10 +1,60 @@
1
+ /**
2
+ * The generic SEP-1865 locator reader (guuey#127): resolve a persisted
3
+ * `ui://` locator ({@link LocatorViewMount}) into a mountable
4
+ * {@link ResolvedViewMount} by ONE fresh `resources/read` over a transport
5
+ * the HOST supplies.
6
+ *
7
+ * The transport is a structural callable — deliberately no
8
+ * `@modelcontextprotocol/sdk` dependency — because hosts hold wildly
9
+ * different connections: a raw MCP client (ggui's `with-guuey-web` sample
10
+ * keeps one for guest `tools/call` relay), an authenticated platform proxy
11
+ * (`@guuey/agent-client`'s `createUiResourceReader` is exactly this assembly
12
+ * over guuey's `GET /v1/threads/:threadId/ui-resource` route), or anything
13
+ * else that can perform one read. The host adapts whatever client it holds.
14
+ *
15
+ * Trust rules, identical to the platform reader and now in ONE place:
16
+ *
17
+ * - a transport deny, a miss, and a thrown transport error are all
18
+ * `undefined` → the host renders its placeholder, never an error surface
19
+ * (deny == miss — no oracle for which locators resolve);
20
+ * - enforcement (user-ownership, tenancy, credential handling) belongs
21
+ * INSIDE the transport, before any bytes come back — this assembly never
22
+ * sees a credential;
23
+ * - the sandbox-trust channel derives from the REQUESTED locator uri, never
24
+ * from the response — a server answering with a foreign `ui://ggui/…` uri
25
+ * must not steer its HTML into the ggui-CSP host page.
26
+ */
27
+ import { McpUiResourceCspSchema } from "@modelcontextprotocol/ext-apps";
28
+ /**
29
+ * The server's per-resource CSP declaration from a read result's
30
+ * `_meta.ui.csp` (guuey#312 — the phase-2 channel `uiResourceChannel`'s
31
+ * docs promised). Validated against the SPEC's own schema
32
+ * (`McpUiResourceCspSchema` from `@modelcontextprotocol/ext-apps`), never
33
+ * a hand-rolled mirror. Malformed or absent → `undefined` — an undeclared
34
+ * resource is the secure default (consumers only ever use a declaration to
35
+ * build allowances; absence widens nothing).
36
+ */
37
+ export function declaredResourceCsp(entry) {
38
+ const meta = entry._meta;
39
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta) || !("ui" in meta)) {
40
+ return undefined;
41
+ }
42
+ const ui = meta.ui;
43
+ if (typeof ui !== "object" || ui === null || Array.isArray(ui) || !("csp" in ui)) {
44
+ return undefined;
45
+ }
46
+ const parsed = McpUiResourceCspSchema.safeParse(ui.csp);
47
+ return parsed.success ? parsed.data : undefined;
48
+ }
1
49
  /**
2
50
  * Phase-1 sandbox-trust channel resolution (conformance map, retirement
3
51
  * step 3): a `ui://ggui/…` locator mounts through the ggui-CSP host page
4
52
  * (`"ggui"` — the shell boots ggui's runtime bundle and opens its WSS),
5
53
  * anything else through the self-only page (`"inline"` — arbitrary tenant
6
- * HTML). Retired when servers declare runtime/ws origins per resource
7
- * (`_meta.ui.csp` on the read result); until then the uri prefix is the only
54
+ * HTML). The declaration itself ships (guuey#312 `ResolvedViewMount.csp`
55
+ * via {@link declaredResourceCsp}); this heuristic retires when hosts route
56
+ * SANDBOX TRUST from that declaration too (per-card CSP construction, the
57
+ * conformance map's step-3 second half). Until then the uri prefix is the only
8
58
  * signal a host has BEFORE fetching mount material.
9
59
  */
10
60
  export function uiResourceChannel(resourceUri) {
@@ -40,6 +90,15 @@ export function createMcpUiResourceReader(deps) {
40
90
  ...(typeof entry.text === "string" ? { text: entry.text } : {}),
41
91
  ...(typeof entry.blob === "string" ? { blob: entry.blob } : {}),
42
92
  };
43
- return { channel: uiResourceChannel(resourceUri), resource };
93
+ // guuey#312: the per-resource CSP declaration rides the resolved mount
94
+ // when the wire carried one — hosts build per-card allowances from the
95
+ // DECLARATION, never from the uri prefix. Absent = undeclared (secure
96
+ // default); never fabricated.
97
+ const csp = declaredResourceCsp(entry);
98
+ return {
99
+ channel: uiResourceChannel(resourceUri),
100
+ resource,
101
+ ...(csp !== undefined ? { csp } : {}),
102
+ };
44
103
  };
45
104
  }
@@ -45,6 +45,15 @@ export interface ViewHostInfo {
45
45
  }
46
46
  /** The standard MCP method a view uses to reach host-proxied tools. */
47
47
  export declare const TOOLS_CALL_METHOD = "tools/call";
48
+ /**
49
+ * The spec method a view uses to push a model-context snapshot at its host
50
+ * (`McpUiUpdateModelContextRequest`). A local constant — the ext-apps root
51
+ * exports the TYPE but no method constant for it (guuey#335). Producers
52
+ * (ggui context-observer) fire it on slot changes as a COMPLEMENT to the
53
+ * action path; the choice itself always rides tools/call or the live
54
+ * channel ("Actions drive turns; context observes state").
55
+ */
56
+ export declare const UPDATE_MODEL_CONTEXT_METHOD = "ui/update-model-context";
48
57
  /**
49
58
  * The standard MCP method a view uses to read host-proxied resources —
50
59
  * `ReadResourceRequest` in the spec's App→Host request union
@@ -128,6 +137,19 @@ export type ViewHostEffect = {
128
137
  kind: "relay-resource-read";
129
138
  id: ViewRequestId;
130
139
  uri: string;
140
+ } | {
141
+ /**
142
+ * A `ui/update-model-context` snapshot to hand the embedder
143
+ * (guuey#335). The machine already ANSWERED the view (success — the
144
+ * spec result is empty), so this effect is pure delivery: a throwing
145
+ * sink can never leave the view hanging. Only emitted when
146
+ * {@link ViewHostBehavior.modelContextSink} is true — unwired, the
147
+ * machine refuses in-band like every other unsupported request.
148
+ */
149
+ kind: "model-context-update";
150
+ params: {
151
+ [key: string]: unknown;
152
+ };
131
153
  } | {
132
154
  /**
133
155
  * The view reported its content size (`ui/notifications/size-changed`
@@ -161,6 +183,8 @@ export interface ViewHostBehavior {
161
183
  hostContext: McpUiHostContext;
162
184
  /** Whether a `tools/call` relay hook is wired (see `view-host.ts`). */
163
185
  toolRelay: boolean;
186
+ /** Whether a model-context sink is wired (see `view-host.ts`, guuey#335). */
187
+ modelContextSink: boolean;
164
188
  /** Whether a `resources/read` relay hook is wired (see `view-host.ts`). */
165
189
  resourceRelay: boolean;
166
190
  }
@@ -1 +1 @@
1
- {"version":3,"file":"view-host-protocol.d.ts","sourceRoot":"","sources":["../src/view-host-protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"view-host-protocol.d.ts","sourceRoot":"","sources":["../src/view-host-protocol.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,15 @@ import { INITIALIZE_METHOD, LATEST_PROTOCOL_VERSION, RESOURCE_TEARDOWN_METHOD, S
36
36
  const METHOD_NOT_SUPPORTED = -32601;
37
37
  /** The standard MCP method a view uses to reach host-proxied tools. */
38
38
  export const TOOLS_CALL_METHOD = "tools/call";
39
+ /**
40
+ * The spec method a view uses to push a model-context snapshot at its host
41
+ * (`McpUiUpdateModelContextRequest`). A local constant — the ext-apps root
42
+ * exports the TYPE but no method constant for it (guuey#335). Producers
43
+ * (ggui context-observer) fire it on slot changes as a COMPLEMENT to the
44
+ * action path; the choice itself always rides tools/call or the live
45
+ * channel ("Actions drive turns; context observes state").
46
+ */
47
+ export const UPDATE_MODEL_CONTEXT_METHOD = "ui/update-model-context";
39
48
  /**
40
49
  * The standard MCP method a view uses to read host-proxied resources —
41
50
  * `ReadResourceRequest` in the spec's App→Host request union
@@ -192,6 +201,20 @@ export function viewHostReceive(state, behavior, data) {
192
201
  }
193
202
  // fall through: a nameless tools/call is not a call we can relay.
194
203
  }
204
+ if (req.method === UPDATE_MODEL_CONTEXT_METHOD && behavior.modelContextSink) {
205
+ // Answer success FIRST-class (the spec result is empty) and hand the
206
+ // params to the sink as an effect. Producers fire-and-forget this
207
+ // method (ggui swallows failures), but answering honestly keeps any
208
+ // strict producer green — and the sink gets the snapshot either way.
209
+ const params = isPlainObject(req.params) ? req.params : {};
210
+ return {
211
+ state,
212
+ effects: [
213
+ { kind: "respond", message: { jsonrpc: "2.0", id: req.id, result: {} } },
214
+ { kind: "model-context-update", params },
215
+ ],
216
+ };
217
+ }
195
218
  if (req.method === RESOURCES_READ_METHOD && behavior.resourceRelay) {
196
219
  const uri = req.params?.["uri"];
197
220
  if (typeof uri === "string") {
@@ -203,6 +226,7 @@ export function viewHostReceive(state, behavior, data) {
203
226
  INITIALIZE_METHOD,
204
227
  ...(behavior.toolRelay ? [TOOLS_CALL_METHOD] : []),
205
228
  ...(behavior.resourceRelay ? [RESOURCES_READ_METHOD] : []),
229
+ ...(behavior.modelContextSink ? [UPDATE_MODEL_CONTEXT_METHOD] : []),
206
230
  ];
207
231
  return {
208
232
  state,
@@ -95,6 +95,20 @@ export interface AttachViewHostConfig {
95
95
  * owns allowlisting and validation (`createMcpUiActionRelay` does both).
96
96
  */
97
97
  onCallTool?: (request: UiActionRequest) => Promise<McpToolCallResult>;
98
+ /**
99
+ * Sink for `ui/update-model-context` snapshots (guuey#335): the view
100
+ * pushes its context state (e.g. ggui slot values) for the model's
101
+ * FUTURE turns. Wiring it makes the host ANSWER the method (empty
102
+ * result, per spec) instead of `method_not_supported`, and advertises
103
+ * `updateModelContext` (an explicit `hostCapabilities` entry still
104
+ * wins). The machine answers BEFORE delivery — a throwing sink never
105
+ * leaves the view hanging. Producers treat this channel as a COMPLEMENT
106
+ * to the action path (the choice itself rides tools/call / the live
107
+ * channel), so a sink that only records is honest.
108
+ */
109
+ onUpdateModelContext?: (params: {
110
+ [key: string]: unknown;
111
+ }) => void;
98
112
  /**
99
113
  * The mounted resource's `ui://` locator — the scope stamped on every
100
114
  * 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAQL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAGnB,KAAK,aAAa,EAEnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAEL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAC9F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,aAAa,EAAE;QAAE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CACd,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC5D,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC5D,IAAI,CAAC;CACT;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,IAAI,EAAE,yBAAyB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;IACrG,mBAAmB,CAAC,IAAI,EAAE,yBAAyB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;CACzG;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACtE;;;;;;;;;;OAUG;IACH,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IACpE;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAC;IAC7E;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACpE,6DAA6D;IAC7D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvD,6EAA6E;IAC7E,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAuDD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,GAAE,oBAAyB,GAAG,MAAM,IAAI,CAwHlG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAcnF"}
package/dist/view-host.js CHANGED
@@ -44,6 +44,7 @@ function hostContextFor(frame, config) {
44
44
  function behaviorFor(frame, config) {
45
45
  const relayWired = config.onCallTool !== undefined && config.resourceUri !== undefined;
46
46
  const readWired = config.onReadResource !== undefined;
47
+ const contextSinkWired = config.onUpdateModelContext !== undefined;
47
48
  return {
48
49
  hostInfo: config.hostInfo ?? DEFAULT_HOST_INFO,
49
50
  hostCapabilities: {
@@ -51,11 +52,13 @@ function behaviorFor(frame, config) {
51
52
  // hostCapabilities entry still wins (the serverTools precedent).
52
53
  ...(relayWired ? { serverTools: {} } : {}),
53
54
  ...(readWired ? { serverResources: {} } : {}),
55
+ ...(contextSinkWired ? { updateModelContext: { text: {} } } : {}),
54
56
  ...config.hostCapabilities,
55
57
  },
56
58
  hostContext: hostContextFor(frame, config),
57
59
  toolRelay: relayWired,
58
60
  resourceRelay: readWired,
61
+ modelContextSink: contextSinkWired,
59
62
  };
60
63
  }
61
64
  /**
@@ -129,6 +132,17 @@ export function attachViewHost(frame, config = {}) {
129
132
  relay(effect.id, effect.name, effect.arguments);
130
133
  else if (effect.kind === "relay-resource-read")
131
134
  relayRead(effect.id, effect.uri);
135
+ else if (effect.kind === "model-context-update") {
136
+ // Delivery only — the machine already answered the view. Contained:
137
+ // a throwing embedder sink must not unwind the message pump.
138
+ try {
139
+ config.onUpdateModelContext?.(effect.params);
140
+ }
141
+ catch {
142
+ // Observer failure is the embedder's bug to notice in its own
143
+ // logs; the view's contract is already satisfied.
144
+ }
145
+ }
132
146
  else {
133
147
  config.onSizeChanged?.({
134
148
  ...(effect.width !== undefined ? { width: effect.width } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/mcp-apps-host",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
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",
@@ -33,7 +33,7 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@ggui-ai/protocol": "0.9.0",
36
+ "@ggui-ai/protocol": "0.10.0",
37
37
  "@modelcontextprotocol/ext-apps": "^1.7.5",
38
38
  "@silverprotocol/core": "0.5.0"
39
39
  },
package/src/card-mount.ts CHANGED
@@ -41,6 +41,7 @@
41
41
  * from the response and never from mount material a producer inlined.
42
42
  */
43
43
  import { snapshotUiResource, toolResultLocator, toolResultUiResource, type McpUiResourcePayload } from "./block-ui.js";
44
+ import type { McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
44
45
  import type { AgBlock, JsonValue } from "@silverprotocol/core";
45
46
 
46
47
  /**
@@ -80,6 +81,16 @@ export interface ResolvedViewMount {
80
81
  channel: "inline" | "ggui";
81
82
  /** The payload an mcp-ui host mounts, identical in shape for both channels. */
82
83
  resource: McpUiResourcePayload;
84
+ /**
85
+ * The server's per-resource CSP declaration (`_meta.ui.csp` on the
86
+ * `resources/read` result — MCP Apps SEP-1865; guuey#312), when the read
87
+ * path saw one. Spec-schema-validated at the reader
88
+ * (`declaredResourceCsp`) — never fabricated, absent = undeclared. This
89
+ * is the generic successor to the uri-prefix channel heuristic: hosts
90
+ * derive per-card sandbox CSP / tripwire filters from it
91
+ * (`<GuueyView>` defaults its `cspOrigins` from here).
92
+ */
93
+ csp?: McpUiResourceCsp;
83
94
  }
84
95
 
85
96
  /** The durable-identity arm: no mount material, only the uri to re-fetch. */
package/src/index.ts CHANGED
@@ -28,10 +28,15 @@ export {
28
28
  } from "./card-mount.js";
29
29
  export {
30
30
  createMcpUiResourceReader,
31
+ declaredResourceCsp,
31
32
  uiResourceChannel,
32
33
  type CreateMcpUiResourceReaderDeps,
33
34
  type McpResourceReadResult,
34
35
  } from "./reader.js";
36
+ // The spec's own per-resource CSP type (SEP-1865 `_meta.ui.csp`) — the
37
+ // shape of `ResolvedViewMount.csp` (guuey#312); identical to
38
+ // `ViewCspOrigins`, re-exported under its spec name for mount consumers.
39
+ export type { McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
35
40
  export {
36
41
  asToolCallResult,
37
42
  createMcpUiActionRelay,
package/src/react.tsx CHANGED
@@ -60,6 +60,7 @@ export interface GuueyViewProps
60
60
  | "hostInfo"
61
61
  | "hostContext"
62
62
  | "onCallTool"
63
+ | "onUpdateModelContext"
63
64
  | "onReadResource"
64
65
  | "onSizeChanged"
65
66
  | "negotiationTimeoutMs"
@@ -236,8 +237,15 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
236
237
  if (frame === null || html === undefined) return;
237
238
  if (sandboxPageUrl !== undefined && page === undefined) return; // refused config — nothing mounts
238
239
  const resourceUri = mount.resource.uri;
240
+ // guuey#312: the resolved mount's DECLARED per-resource CSP
241
+ // (`_meta.ui.csp`, spec-schema-validated at the reader) is the CSP
242
+ // tripwire's default filter — the declaration finally doing host-side
243
+ // work. An explicit `cspOrigins` prop always wins; both absent keeps
244
+ // the tripwire inert exactly as before.
245
+ const cspOrigins = latest.current.hostConfig.cspOrigins ?? mount.csp;
239
246
  const detachHost = attachViewHost(frame, {
240
247
  ...latest.current.hostConfig,
248
+ ...(cspOrigins !== undefined ? { cspOrigins } : {}),
241
249
  resourceUri,
242
250
  onPhaseChange: (next) => {
243
251
  setPhase(next);
package/src/reader.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  * from the response — a server answering with a foreign `ui://ggui/…` uri
25
25
  * must not steer its HTML into the ggui-CSP host page.
26
26
  */
27
+ import { McpUiResourceCspSchema, type McpUiResourceCsp } from "@modelcontextprotocol/ext-apps";
27
28
  import type { McpUiResourcePayload } from "./block-ui.js";
28
29
  import type { ResolvedViewMount, ViewMountChannel } from "./card-mount.js";
29
30
 
@@ -31,8 +32,11 @@ import type { ResolvedViewMount, ViewMountChannel } from "./card-mount.js";
31
32
  * One `resources/read` `contents[]` entry, structurally (SEP-1865). Kept a
32
33
  * separate type from {@link McpUiResourcePayload} even though the shapes
33
34
  * coincide today: this is the UNVALIDATED wire-side entry a transport hands
34
- * over (phase 2 grows `_meta` here per-resource declared CSP, conformance
35
- * map step 3), while the payload is the narrowed thing a host mounts.
35
+ * over, while the payload is the narrowed thing a host mounts. `_meta` is
36
+ * phase 2's growth (guuey#312, conformance map step 3): the per-resource
37
+ * declarations (`_meta.ui.csp` — MCP Apps SEP-1865) ride here when the
38
+ * transport forwards them; {@link declaredResourceCsp} is the ONLY door
39
+ * from this open extension map into typed material.
36
40
  */
37
41
  export interface McpResourceReadResult {
38
42
  uri: string;
@@ -40,6 +44,34 @@ export interface McpResourceReadResult {
40
44
  text?: string;
41
45
  /** Base64 payload arm — hosts decode via {@link resourceHtml}. */
42
46
  blob?: string;
47
+ /**
48
+ * The entry's `_meta` as the transport saw it — an open extension map,
49
+ * so genuinely unknown-shaped at this boundary; never read it directly,
50
+ * narrow through {@link declaredResourceCsp}.
51
+ */
52
+ _meta?: unknown;
53
+ }
54
+
55
+ /**
56
+ * The server's per-resource CSP declaration from a read result's
57
+ * `_meta.ui.csp` (guuey#312 — the phase-2 channel `uiResourceChannel`'s
58
+ * docs promised). Validated against the SPEC's own schema
59
+ * (`McpUiResourceCspSchema` from `@modelcontextprotocol/ext-apps`), never
60
+ * a hand-rolled mirror. Malformed or absent → `undefined` — an undeclared
61
+ * resource is the secure default (consumers only ever use a declaration to
62
+ * build allowances; absence widens nothing).
63
+ */
64
+ export function declaredResourceCsp(entry: McpResourceReadResult): McpUiResourceCsp | undefined {
65
+ const meta = entry._meta;
66
+ if (typeof meta !== "object" || meta === null || Array.isArray(meta) || !("ui" in meta)) {
67
+ return undefined;
68
+ }
69
+ const ui: unknown = meta.ui;
70
+ if (typeof ui !== "object" || ui === null || Array.isArray(ui) || !("csp" in ui)) {
71
+ return undefined;
72
+ }
73
+ const parsed = McpUiResourceCspSchema.safeParse(ui.csp);
74
+ return parsed.success ? parsed.data : undefined;
43
75
  }
44
76
 
45
77
  /**
@@ -47,8 +79,10 @@ export interface McpResourceReadResult {
47
79
  * step 3): a `ui://ggui/…` locator mounts through the ggui-CSP host page
48
80
  * (`"ggui"` — the shell boots ggui's runtime bundle and opens its WSS),
49
81
  * anything else through the self-only page (`"inline"` — arbitrary tenant
50
- * HTML). Retired when servers declare runtime/ws origins per resource
51
- * (`_meta.ui.csp` on the read result); until then the uri prefix is the only
82
+ * HTML). The declaration itself ships (guuey#312 `ResolvedViewMount.csp`
83
+ * via {@link declaredResourceCsp}); this heuristic retires when hosts route
84
+ * SANDBOX TRUST from that declaration too (per-card CSP construction, the
85
+ * conformance map's step-3 second half). Until then the uri prefix is the only
52
86
  * signal a host has BEFORE fetching mount material.
53
87
  */
54
88
  export function uiResourceChannel(resourceUri: string): Exclude<ViewMountChannel, "locator"> {
@@ -93,6 +127,15 @@ export function createMcpUiResourceReader(
93
127
  ...(typeof entry.text === "string" ? { text: entry.text } : {}),
94
128
  ...(typeof entry.blob === "string" ? { blob: entry.blob } : {}),
95
129
  };
96
- return { channel: uiResourceChannel(resourceUri), resource };
130
+ // guuey#312: the per-resource CSP declaration rides the resolved mount
131
+ // when the wire carried one — hosts build per-card allowances from the
132
+ // DECLARATION, never from the uri prefix. Absent = undeclared (secure
133
+ // default); never fabricated.
134
+ const csp = declaredResourceCsp(entry);
135
+ return {
136
+ channel: uiResourceChannel(resourceUri),
137
+ resource,
138
+ ...(csp !== undefined ? { csp } : {}),
139
+ };
97
140
  };
98
141
  }
@@ -60,6 +60,16 @@ const METHOD_NOT_SUPPORTED = -32601;
60
60
  /** The standard MCP method a view uses to reach host-proxied tools. */
61
61
  export const TOOLS_CALL_METHOD = "tools/call";
62
62
 
63
+ /**
64
+ * The spec method a view uses to push a model-context snapshot at its host
65
+ * (`McpUiUpdateModelContextRequest`). A local constant — the ext-apps root
66
+ * exports the TYPE but no method constant for it (guuey#335). Producers
67
+ * (ggui context-observer) fire it on slot changes as a COMPLEMENT to the
68
+ * action path; the choice itself always rides tools/call or the live
69
+ * channel ("Actions drive turns; context observes state").
70
+ */
71
+ export const UPDATE_MODEL_CONTEXT_METHOD = "ui/update-model-context";
72
+
63
73
  /**
64
74
  * The standard MCP method a view uses to read host-proxied resources —
65
75
  * `ReadResourceRequest` in the spec's App→Host request union
@@ -152,6 +162,18 @@ export type ViewHostEffect =
152
162
  id: ViewRequestId;
153
163
  uri: string;
154
164
  }
165
+ | {
166
+ /**
167
+ * A `ui/update-model-context` snapshot to hand the embedder
168
+ * (guuey#335). The machine already ANSWERED the view (success — the
169
+ * spec result is empty), so this effect is pure delivery: a throwing
170
+ * sink can never leave the view hanging. Only emitted when
171
+ * {@link ViewHostBehavior.modelContextSink} is true — unwired, the
172
+ * machine refuses in-band like every other unsupported request.
173
+ */
174
+ kind: "model-context-update";
175
+ params: { [key: string]: unknown };
176
+ }
155
177
  | {
156
178
  /**
157
179
  * The view reported its content size (`ui/notifications/size-changed`
@@ -186,6 +208,8 @@ export interface ViewHostBehavior {
186
208
  hostContext: McpUiHostContext;
187
209
  /** Whether a `tools/call` relay hook is wired (see `view-host.ts`). */
188
210
  toolRelay: boolean;
211
+ /** Whether a model-context sink is wired (see `view-host.ts`, guuey#335). */
212
+ modelContextSink: boolean;
189
213
  /** Whether a `resources/read` relay hook is wired (see `view-host.ts`). */
190
214
  resourceRelay: boolean;
191
215
  }
@@ -360,6 +384,21 @@ export function viewHostReceive(
360
384
  // fall through: a nameless tools/call is not a call we can relay.
361
385
  }
362
386
 
387
+ if (req.method === UPDATE_MODEL_CONTEXT_METHOD && behavior.modelContextSink) {
388
+ // Answer success FIRST-class (the spec result is empty) and hand the
389
+ // params to the sink as an effect. Producers fire-and-forget this
390
+ // method (ggui swallows failures), but answering honestly keeps any
391
+ // strict producer green — and the sink gets the snapshot either way.
392
+ const params = isPlainObject(req.params) ? req.params : {};
393
+ return {
394
+ state,
395
+ effects: [
396
+ { kind: "respond", message: { jsonrpc: "2.0", id: req.id, result: {} } },
397
+ { kind: "model-context-update", params },
398
+ ],
399
+ };
400
+ }
401
+
363
402
  if (req.method === RESOURCES_READ_METHOD && behavior.resourceRelay) {
364
403
  const uri = req.params?.["uri"];
365
404
  if (typeof uri === "string") {
@@ -372,6 +411,7 @@ export function viewHostReceive(
372
411
  INITIALIZE_METHOD,
373
412
  ...(behavior.toolRelay ? [TOOLS_CALL_METHOD] : []),
374
413
  ...(behavior.resourceRelay ? [RESOURCES_READ_METHOD] : []),
414
+ ...(behavior.modelContextSink ? [UPDATE_MODEL_CONTEXT_METHOD] : []),
375
415
  ];
376
416
  return {
377
417
  state,
package/src/view-host.ts CHANGED
@@ -117,6 +117,18 @@ export interface AttachViewHostConfig {
117
117
  * owns allowlisting and validation (`createMcpUiActionRelay` does both).
118
118
  */
119
119
  onCallTool?: (request: UiActionRequest) => Promise<McpToolCallResult>;
120
+ /**
121
+ * Sink for `ui/update-model-context` snapshots (guuey#335): the view
122
+ * pushes its context state (e.g. ggui slot values) for the model's
123
+ * FUTURE turns. Wiring it makes the host ANSWER the method (empty
124
+ * result, per spec) instead of `method_not_supported`, and advertises
125
+ * `updateModelContext` (an explicit `hostCapabilities` entry still
126
+ * wins). The machine answers BEFORE delivery — a throwing sink never
127
+ * leaves the view hanging. Producers treat this channel as a COMPLEMENT
128
+ * to the action path (the choice itself rides tools/call / the live
129
+ * channel), so a sink that only records is honest.
130
+ */
131
+ onUpdateModelContext?: (params: { [key: string]: unknown }) => void;
120
132
  /**
121
133
  * The mounted resource's `ui://` locator — the scope stamped on every
122
134
  * relayed {@link UiActionRequest}. Required for the relay to fire;
@@ -207,6 +219,7 @@ function hostContextFor(frame: ViewFrameLike, config: AttachViewHostConfig): Mcp
207
219
  function behaviorFor(frame: ViewFrameLike, config: AttachViewHostConfig): ViewHostBehavior {
208
220
  const relayWired = config.onCallTool !== undefined && config.resourceUri !== undefined;
209
221
  const readWired = config.onReadResource !== undefined;
222
+ const contextSinkWired = config.onUpdateModelContext !== undefined;
210
223
  return {
211
224
  hostInfo: config.hostInfo ?? DEFAULT_HOST_INFO,
212
225
  hostCapabilities: {
@@ -214,11 +227,13 @@ function behaviorFor(frame: ViewFrameLike, config: AttachViewHostConfig): ViewHo
214
227
  // hostCapabilities entry still wins (the serverTools precedent).
215
228
  ...(relayWired ? { serverTools: {} } : {}),
216
229
  ...(readWired ? { serverResources: {} } : {}),
230
+ ...(contextSinkWired ? { updateModelContext: { text: {} } } : {}),
217
231
  ...config.hostCapabilities,
218
232
  },
219
233
  hostContext: hostContextFor(frame, config),
220
234
  toolRelay: relayWired,
221
235
  resourceRelay: readWired,
236
+ modelContextSink: contextSinkWired,
222
237
  };
223
238
  }
224
239
 
@@ -295,6 +310,16 @@ export function attachViewHost(frame: ViewFrameLike, config: AttachViewHostConfi
295
310
  if (effect.kind === "respond") post(effect.message);
296
311
  else if (effect.kind === "relay-tool-call") relay(effect.id, effect.name, effect.arguments);
297
312
  else if (effect.kind === "relay-resource-read") relayRead(effect.id, effect.uri);
313
+ else if (effect.kind === "model-context-update") {
314
+ // Delivery only — the machine already answered the view. Contained:
315
+ // a throwing embedder sink must not unwind the message pump.
316
+ try {
317
+ config.onUpdateModelContext?.(effect.params);
318
+ } catch {
319
+ // Observer failure is the embedder's bug to notice in its own
320
+ // logs; the view's contract is already satisfied.
321
+ }
322
+ }
298
323
  else {
299
324
  config.onSizeChanged?.({
300
325
  ...(effect.width !== undefined ? { width: effect.width } : {}),