@ggui-ai/mcp-apps-react-native 0.10.0 → 0.11.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/mcp-apps-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "React Native SDK for ggui — components and hooks for embedding agent-generated UIs in mobile apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@modelcontextprotocol/ext-apps": "^1.7.2",
|
|
61
|
-
"@ggui-ai/design": "0.
|
|
62
|
-
"@ggui-ai/protocol": "0.
|
|
63
|
-
"@ggui-ai/iframe-runtime": "0.
|
|
61
|
+
"@ggui-ai/design": "0.11.0",
|
|
62
|
+
"@ggui-ai/protocol": "0.11.0",
|
|
63
|
+
"@ggui-ai/iframe-runtime": "0.11.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
classifyRendererEnvelope,
|
|
69
69
|
deriveResourceMountSource,
|
|
70
70
|
dispatchHostBridgeRequest,
|
|
71
|
+
readSizeChangedNotification,
|
|
71
72
|
type HostBridgeContext,
|
|
72
73
|
type HostBridgeRequest,
|
|
73
74
|
type HostBridgeResponse,
|
|
@@ -107,6 +108,7 @@ export const McpAppIframe = forwardRef<McpAppIframeRef, McpAppIframeProps>(
|
|
|
107
108
|
onError,
|
|
108
109
|
onObserve,
|
|
109
110
|
onLifecycle,
|
|
111
|
+
onSizeChanged,
|
|
110
112
|
},
|
|
111
113
|
ref,
|
|
112
114
|
) {
|
|
@@ -226,6 +228,16 @@ export const McpAppIframe = forwardRef<McpAppIframeRef, McpAppIframeProps>(
|
|
|
226
228
|
return;
|
|
227
229
|
}
|
|
228
230
|
case 'jsonrpc': {
|
|
231
|
+
// `ui/notifications/size-changed` (ggui#589 rider 5) — an
|
|
232
|
+
// id-less NOTIFICATION, so it never reaches the request
|
|
233
|
+
// dispatcher below (which returns null for notifications;
|
|
234
|
+
// before this branch the announce was dropped and content
|
|
235
|
+
// taller than the container clipped). Surface it and stop.
|
|
236
|
+
const sizeChange = readSizeChangedNotification(payload);
|
|
237
|
+
if (sizeChange !== null) {
|
|
238
|
+
onSizeChanged?.(sizeChange);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
229
241
|
const req = payload as HostBridgeRequest;
|
|
230
242
|
const response = await dispatchHostBridgeRequest(req, ctxRef.current);
|
|
231
243
|
if (response) deliverToWebView(response);
|
|
@@ -256,7 +268,7 @@ export const McpAppIframe = forwardRef<McpAppIframeRef, McpAppIframeProps>(
|
|
|
256
268
|
return;
|
|
257
269
|
}
|
|
258
270
|
},
|
|
259
|
-
[deliverToWebView, onError, onObserve, onLifecycle],
|
|
271
|
+
[deliverToWebView, onError, onObserve, onLifecycle, onSizeChanged],
|
|
260
272
|
);
|
|
261
273
|
|
|
262
274
|
// Mount-source null → caller gave an unmountable resource. Emit
|
|
@@ -306,7 +318,11 @@ export const McpAppIframe = forwardRef<McpAppIframeRef, McpAppIframeProps>(
|
|
|
306
318
|
|
|
307
319
|
if (mountSource === null) {
|
|
308
320
|
// Render an empty View so the caller still sees a slot; onError
|
|
309
|
-
// already fired the bootstrap-failed error.
|
|
321
|
+
// already fired the bootstrap-failed error. NO card chrome —
|
|
322
|
+
// the HOST owns borders/radius entirely (ggui#589 round 6: the
|
|
323
|
+
// hardcoded #e5e5e5 border ran at its own radius inside the
|
|
324
|
+
// embedder's rounded rim — the two-radii mismatch was the
|
|
325
|
+
// store-frame rejection).
|
|
310
326
|
return (
|
|
311
327
|
<View
|
|
312
328
|
testID="mcp-app-iframe-empty"
|
|
@@ -315,9 +331,6 @@ export const McpAppIframe = forwardRef<McpAppIframeRef, McpAppIframeProps>(
|
|
|
315
331
|
height: dims.height ?? 480,
|
|
316
332
|
...(dims.maxWidth !== undefined ? { maxWidth: dims.maxWidth } : {}),
|
|
317
333
|
...(dims.maxHeight !== undefined ? { maxHeight: dims.maxHeight } : {}),
|
|
318
|
-
borderWidth: 1,
|
|
319
|
-
borderColor: '#e5e5e5',
|
|
320
|
-
borderRadius: 8,
|
|
321
334
|
}}
|
|
322
335
|
/>
|
|
323
336
|
);
|
|
@@ -337,14 +350,15 @@ export const McpAppIframe = forwardRef<McpAppIframeRef, McpAppIframeProps>(
|
|
|
337
350
|
{...(lifecycleState !== null
|
|
338
351
|
? { accessibilityValue: { text: lifecycleState } }
|
|
339
352
|
: {})}
|
|
353
|
+
// NO card chrome (ggui#589 round 6) — the HOST owns borders/
|
|
354
|
+
// radius/silhouette entirely, the same doctrine as the round-5
|
|
355
|
+
// frameless ruling one layer down. `overflow: 'hidden'` stays:
|
|
356
|
+
// it is containment (clip the WebView to the slot), not chrome.
|
|
340
357
|
style={{
|
|
341
358
|
width: dims.width ?? '100%',
|
|
342
359
|
height: dims.height ?? 480,
|
|
343
360
|
...(dims.maxWidth !== undefined ? { maxWidth: dims.maxWidth } : {}),
|
|
344
361
|
...(dims.maxHeight !== undefined ? { maxHeight: dims.maxHeight } : {}),
|
|
345
|
-
borderWidth: 1,
|
|
346
|
-
borderColor: '#e5e5e5',
|
|
347
|
-
borderRadius: 8,
|
|
348
362
|
overflow: 'hidden',
|
|
349
363
|
}}
|
|
350
364
|
>
|
|
@@ -307,6 +307,43 @@ export function classifyRendererEnvelope(data: unknown): RendererEnvelopeTag {
|
|
|
307
307
|
return 'unknown';
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Read a spec-canonical `ui/notifications/size-changed` NOTIFICATION
|
|
312
|
+
* (ggui#589 rider 5). The view sends it when its rendered content
|
|
313
|
+
* changes size (ext-apps `App.sendSizeChanged` / its autoResize
|
|
314
|
+
* observer); before this reader existed, the jsonrpc branch dropped
|
|
315
|
+
* every id-less message (`dispatchHostBridgeRequest` returns null for
|
|
316
|
+
* notifications) and content taller than the container clipped.
|
|
317
|
+
*
|
|
318
|
+
* Returns the validated `{width?, height?}` params, or `null` when the
|
|
319
|
+
* payload is not this notification OR carries nothing usable — each
|
|
320
|
+
* dimension must be a finite non-negative number to pass; a payload
|
|
321
|
+
* where NEITHER dimension survives is `null` (nothing to resize on).
|
|
322
|
+
* Pure function — testable without a WebView present.
|
|
323
|
+
*/
|
|
324
|
+
export function readSizeChangedNotification(
|
|
325
|
+
data: unknown,
|
|
326
|
+
): { width?: number; height?: number } | null {
|
|
327
|
+
if (data === null || typeof data !== 'object') return null;
|
|
328
|
+
const d = data as { jsonrpc?: unknown; method?: unknown; params?: unknown };
|
|
329
|
+
if (d.jsonrpc !== '2.0' || d.method !== 'ui/notifications/size-changed') {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
if (d.params === null || typeof d.params !== 'object' || Array.isArray(d.params)) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const params = d.params as { width?: unknown; height?: unknown };
|
|
336
|
+
const dim = (v: unknown): number | undefined =>
|
|
337
|
+
typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
|
|
338
|
+
const width = dim(params.width);
|
|
339
|
+
const height = dim(params.height);
|
|
340
|
+
if (width === undefined && height === undefined) return null;
|
|
341
|
+
return {
|
|
342
|
+
...(width !== undefined ? { width } : {}),
|
|
343
|
+
...(height !== undefined ? { height } : {}),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
310
347
|
export function buildDispatchActionNotification(
|
|
311
348
|
name: string,
|
|
312
349
|
data: unknown,
|
|
@@ -211,6 +211,26 @@ export interface McpAppIframeProps {
|
|
|
211
211
|
* neither mirrors nor blocks).
|
|
212
212
|
*/
|
|
213
213
|
readonly onLifecycle?: (event: McpAppLifecycleEvent) => void;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Fired when the view announces a content-size change via the
|
|
217
|
+
* spec-canonical `ui/notifications/size-changed` notification
|
|
218
|
+
* (ext-apps `App.sendSizeChanged` / its autoResize observer).
|
|
219
|
+
* Both dimensions are optional per the spec — the common autoResize
|
|
220
|
+
* case announces `height` only. Values are validated finite
|
|
221
|
+
* non-negative numbers before they reach this callback; malformed
|
|
222
|
+
* announcements are dropped silently (the same tolerant posture as
|
|
223
|
+
* every other envelope).
|
|
224
|
+
*
|
|
225
|
+
* The host does NOT resize itself: `containerDimensions` stays the
|
|
226
|
+
* embedder's declaration. Bind this to drive your own container
|
|
227
|
+
* sizing (autoResize) — without it, content taller than the
|
|
228
|
+
* container clips.
|
|
229
|
+
*/
|
|
230
|
+
readonly onSizeChanged?: (size: {
|
|
231
|
+
readonly width?: number;
|
|
232
|
+
readonly height?: number;
|
|
233
|
+
}) => void;
|
|
214
234
|
}
|
|
215
235
|
|
|
216
236
|
/**
|