@guuey/mcp-apps-host 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +25 -2
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +29 -2
- package/dist/block-ui.d.ts +18 -0
- package/dist/block-ui.d.ts.map +1 -1
- package/dist/block-ui.js +17 -0
- package/dist/card-mount.d.ts +4 -2
- package/dist/card-mount.d.ts.map +1 -1
- package/dist/card-mount.js +7 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/narrowing.d.ts +1 -1
- package/dist/narrowing.d.ts.map +1 -1
- package/dist/narrowing.js +1 -1
- package/dist/react.d.ts +17 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +35 -9
- package/dist/view-host-protocol.d.ts +44 -0
- package/dist/view-host-protocol.d.ts.map +1 -1
- package/dist/view-host-protocol.js +65 -3
- package/dist/view-host.d.ts +23 -0
- package/dist/view-host.d.ts.map +1 -1
- package/dist/view-host.js +43 -2
- package/package.json +2 -2
- package/src/action.ts +30 -2
- package/src/block-ui.ts +21 -0
- package/src/card-mount.ts +11 -7
- package/src/index.ts +4 -0
- package/src/narrowing.ts +1 -0
- package/src/react.tsx +56 -10
- package/src/view-host-protocol.ts +102 -2
- package/src/view-host.ts +63 -1
package/src/view-host.ts
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import {
|
|
31
31
|
initialViewHostState,
|
|
32
|
+
resourceReadResponse,
|
|
32
33
|
teardownMessage,
|
|
33
34
|
toolCallResponse,
|
|
34
35
|
viewHostElapsed,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
type ViewHostPhase,
|
|
39
40
|
type ViewHostState,
|
|
40
41
|
} from "./view-host-protocol.js";
|
|
42
|
+
import type { McpResourceReadResult } from "./reader.js";
|
|
41
43
|
import {
|
|
42
44
|
unavailableToolCallResult,
|
|
43
45
|
type McpToolCallResult,
|
|
@@ -107,6 +109,25 @@ export interface AttachViewHostConfig {
|
|
|
107
109
|
* `<GuueyView>` fills it from the mount automatically.
|
|
108
110
|
*/
|
|
109
111
|
resourceUri?: string;
|
|
112
|
+
/**
|
|
113
|
+
* The `resources/read` relay — a PRIVILEGE boundary like
|
|
114
|
+
* {@link onCallTool}, default off: with no hook, the machine refuses
|
|
115
|
+
* `resources/read` in-band and advertises no `serverResources`. The hook
|
|
116
|
+
* is structurally the SAME transport `createMcpUiResourceReader`
|
|
117
|
+
* assembles over ({@link CreateMcpUiResourceReaderDeps.readResource}) —
|
|
118
|
+
* a host with a locator reader wires the identical function here. Trust
|
|
119
|
+
* rules ride the reader discipline (`reader.ts`): enforcement lives
|
|
120
|
+
* INSIDE the transport; a miss, a deny, and a throw all answer the view
|
|
121
|
+
* with the one `Resource not found` error (deny == miss — no oracle).
|
|
122
|
+
*/
|
|
123
|
+
onReadResource?: (uri: string) => Promise<McpResourceReadResult | undefined>;
|
|
124
|
+
/**
|
|
125
|
+
* The view reported its content size (`ui/notifications/size-changed` —
|
|
126
|
+
* spec notification). Whether and how to resize the frame is the
|
|
127
|
+
* embedder's layout decision; `<GuueyView autoResize>` is one wiring of
|
|
128
|
+
* exactly this callback.
|
|
129
|
+
*/
|
|
130
|
+
onSizeChanged?: (size: { width?: number; height?: number }) => void;
|
|
110
131
|
/** Observe phase transitions (see {@link ViewHostPhase}). */
|
|
111
132
|
onPhaseChange?: (phase: ViewHostPhase) => void;
|
|
112
133
|
/**
|
|
@@ -136,14 +157,37 @@ function hostContextFor(frame: ViewFrameLike, config: AttachViewHostConfig): Mcp
|
|
|
136
157
|
|
|
137
158
|
function behaviorFor(frame: ViewFrameLike, config: AttachViewHostConfig): ViewHostBehavior {
|
|
138
159
|
const relayWired = config.onCallTool !== undefined && config.resourceUri !== undefined;
|
|
160
|
+
const readWired = config.onReadResource !== undefined;
|
|
139
161
|
return {
|
|
140
162
|
hostInfo: config.hostInfo ?? DEFAULT_HOST_INFO,
|
|
141
163
|
hostCapabilities: {
|
|
164
|
+
// A wired relay IS the implementation — advertise it; an explicit
|
|
165
|
+
// hostCapabilities entry still wins (the serverTools precedent).
|
|
142
166
|
...(relayWired ? { serverTools: {} } : {}),
|
|
167
|
+
...(readWired ? { serverResources: {} } : {}),
|
|
143
168
|
...config.hostCapabilities,
|
|
144
169
|
},
|
|
145
170
|
hostContext: hostContextFor(frame, config),
|
|
146
171
|
toolRelay: relayWired,
|
|
172
|
+
resourceRelay: readWired,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Re-narrow a read hook's answer at the trust boundary — hooks are embedder
|
|
178
|
+
* code (possibly plain JS), and the wire entry the view receives must be a
|
|
179
|
+
* real `contents[]` entry: `uri` required, a string payload arm required
|
|
180
|
+
* (a payload-less entry is a miss — the `createMcpUiResourceReader`
|
|
181
|
+
* discipline, applied to the WIRE entry rather than the mountable payload).
|
|
182
|
+
*/
|
|
183
|
+
function narrowReadEntry(entry: McpResourceReadResult | undefined): McpResourceReadResult | undefined {
|
|
184
|
+
if (entry === undefined || typeof entry.uri !== "string") return undefined;
|
|
185
|
+
if (typeof entry.text !== "string" && typeof entry.blob !== "string") return undefined;
|
|
186
|
+
return {
|
|
187
|
+
uri: entry.uri,
|
|
188
|
+
...(typeof entry.mimeType === "string" ? { mimeType: entry.mimeType } : {}),
|
|
189
|
+
...(typeof entry.text === "string" ? { text: entry.text } : {}),
|
|
190
|
+
...(typeof entry.blob === "string" ? { blob: entry.blob } : {}),
|
|
147
191
|
};
|
|
148
192
|
}
|
|
149
193
|
|
|
@@ -183,13 +227,31 @@ export function attachViewHost(frame: ViewFrameLike, config: AttachViewHostConfi
|
|
|
183
227
|
);
|
|
184
228
|
};
|
|
185
229
|
|
|
230
|
+
const relayRead = (id: number | string, uri: string): void => {
|
|
231
|
+
const { onReadResource } = config;
|
|
232
|
+
if (onReadResource === undefined) return; // machine-guarded invariant, as with `relay`
|
|
233
|
+
onReadResource(uri).then(
|
|
234
|
+
(entry) => post(resourceReadResponse(id, narrowReadEntry(entry))),
|
|
235
|
+
// A throwing hook still owes the view an answer — the same not-found
|
|
236
|
+
// the reader discipline gives a deny (deny == miss), never a hang.
|
|
237
|
+
() => post(resourceReadResponse(id, undefined)),
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
186
241
|
const onMessage = (event: { data: unknown; source: unknown }): void => {
|
|
187
242
|
if (frame.contentWindow === null || event.source !== frame.contentWindow) return;
|
|
188
243
|
const { state: next, effects } = viewHostReceive(state, behaviorFor(frame, config), event.data);
|
|
189
244
|
setState(next);
|
|
190
245
|
for (const effect of effects) {
|
|
191
246
|
if (effect.kind === "respond") post(effect.message);
|
|
192
|
-
else relay(effect.id, effect.name, effect.arguments);
|
|
247
|
+
else if (effect.kind === "relay-tool-call") relay(effect.id, effect.name, effect.arguments);
|
|
248
|
+
else if (effect.kind === "relay-resource-read") relayRead(effect.id, effect.uri);
|
|
249
|
+
else {
|
|
250
|
+
config.onSizeChanged?.({
|
|
251
|
+
...(effect.width !== undefined ? { width: effect.width } : {}),
|
|
252
|
+
...(effect.height !== undefined ? { height: effect.height } : {}),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
193
255
|
}
|
|
194
256
|
};
|
|
195
257
|
|