@agent-native/core 0.72.3 → 0.73.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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +38 -0
- package/corpus/core/docs/content/skills-guide.md +14 -0
- package/corpus/core/docs/content/template-clips.md +7 -1
- package/corpus/core/package.json +6 -6
- package/corpus/core/src/a2a/client.ts +7 -3
- package/corpus/core/src/agent/durable-background.ts +25 -12
- package/corpus/core/src/cli/create.ts +1 -1
- package/corpus/core/src/cli/skills.ts +450 -19
- package/corpus/core/src/client/api-path.ts +28 -0
- package/corpus/core/src/client/embed-auth.ts +26 -0
- package/corpus/core/src/client/mcp-app-host.ts +6 -0
- package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
- package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
- package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
- package/corpus/core/src/embedding/agent.ts +6 -1
- package/corpus/core/src/embedding/bridge.ts +34 -1
- package/corpus/core/src/mcp/build-server.ts +38 -10
- package/corpus/core/src/mcp/embed-app.ts +120 -45
- package/corpus/core/src/mcp/index.ts +5 -0
- package/corpus/core/src/mcp/org-directory.ts +70 -11
- package/corpus/core/src/mcp/server.ts +14 -0
- package/corpus/core/src/mcp-client/config.ts +12 -0
- package/corpus/core/src/mcp-client/index.ts +2 -0
- package/corpus/core/src/mcp-client/manager.ts +268 -9
- package/corpus/core/src/mcp-client/remote-store.ts +154 -0
- package/corpus/core/src/mcp-client/routes.ts +2 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
- package/corpus/core/src/server/auth.ts +7 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/server/onboarding-html.ts +95 -9
- package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
- package/corpus/core/src/templates/default/AGENTS.md +4 -0
- package/corpus/core/src/templates/default/package.json +2 -1
- package/corpus/core/src/templates/headless/AGENTS.md +4 -0
- package/corpus/core/src/templates/headless/package.json +1 -0
- package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
- package/corpus/core/src/templates/workspace-root/README.md +4 -0
- package/corpus/core/src/templates/workspace-root/package.json +1 -0
- package/corpus/core/src/vite/client.ts +53 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
- package/corpus/templates/analytics/app/global.css +15 -13
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
- package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
- package/corpus/templates/assets/AGENTS.md +7 -5
- package/corpus/templates/assets/actions/generate-asset.ts +161 -0
- package/corpus/templates/assets/actions/list-assets.ts +4 -2
- package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
- package/corpus/templates/assets/app/routes/library.tsx +243 -40
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
- package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
- package/corpus/templates/clips/AGENTS.md +6 -2
- package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
- package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
- package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
- package/corpus/templates/clips/app/routes/download.tsx +2 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
- package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
- package/corpus/templates/design/AGENTS.md +10 -5
- package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
- package/corpus/templates/design/actions/insert-asset.ts +219 -0
- package/corpus/templates/design/actions/view-screen.ts +3 -1
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
- package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
- package/corpus/templates/mail/server/plugins/auth.ts +4 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
- package/corpus/templates/plan/server/plan-content.ts +45 -2
- package/dist/a2a/client.d.ts +1 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +5 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/durable-background.d.ts +9 -4
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +24 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +361 -19
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/api-path.d.ts.map +1 -1
- package/dist/client/api-path.js +27 -0
- package/dist/client/api-path.js.map +1 -1
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +27 -0
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/mcp-app-host.d.ts.map +1 -1
- package/dist/client/mcp-app-host.js +5 -0
- package/dist/client/mcp-app-host.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/resources/use-mcp-servers.d.ts +1 -0
- package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
- package/dist/client/resources/use-mcp-servers.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
- package/dist/client/settings/DemoModeSection.js +3 -1
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/demo/fetch-interceptor.js +1 -1
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/embedding/agent.d.ts +1 -1
- package/dist/embedding/agent.d.ts.map +1 -1
- package/dist/embedding/agent.js +1 -1
- package/dist/embedding/agent.js.map +1 -1
- package/dist/embedding/bridge.d.ts.map +1 -1
- package/dist/embedding/bridge.js +27 -1
- package/dist/embedding/bridge.js.map +1 -1
- package/dist/extensions/schema.d.ts +2 -2
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +32 -9
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +120 -45
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/org-directory.d.ts +1 -0
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +59 -11
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +9 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp-client/config.d.ts +12 -0
- package/dist/mcp-client/config.d.ts.map +1 -1
- package/dist/mcp-client/config.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/manager.d.ts +2 -0
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +201 -9
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/remote-store.d.ts +27 -0
- package/dist/mcp-client/remote-store.d.ts.map +1 -1
- package/dist/mcp-client/remote-store.js +105 -0
- package/dist/mcp-client/remote-store.js.map +1 -1
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +1 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
- package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +8 -8
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +8 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +7 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +1 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +15 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +73 -9
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +3 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +40 -3
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
- package/dist/sharing/actions/share-resource.d.ts +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/templates/default/AGENTS.md +4 -0
- package/dist/templates/default/package.json +2 -1
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/headless/package.json +1 -0
- package/dist/templates/workspace-root/AGENTS.md +4 -0
- package/dist/templates/workspace-root/README.md +4 -0
- package/dist/templates/workspace-root/package.json +1 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +44 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/skills-guide.md +14 -0
- package/docs/content/template-clips.md +7 -1
- package/package.json +6 -6
- package/src/templates/default/AGENTS.md +4 -0
- package/src/templates/default/package.json +2 -1
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/headless/package.json +1 -0
- package/src/templates/workspace-root/AGENTS.md +4 -0
- package/src/templates/workspace-root/README.md +4 -0
- package/src/templates/workspace-root/package.json +1 -0
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
2
9
|
import {
|
|
3
10
|
AppBridge,
|
|
4
11
|
PostMessageTransport,
|
|
@@ -8,8 +15,14 @@ import {
|
|
|
8
15
|
} from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
9
16
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
10
17
|
import { IconAlertTriangle, IconLoader2 } from "@tabler/icons-react";
|
|
18
|
+
import {
|
|
19
|
+
AGENT_NATIVE_EMBED_MESSAGE_TYPES,
|
|
20
|
+
AGENT_NATIVE_EMBED_PROTOCOL,
|
|
21
|
+
AGENT_NATIVE_EMBED_VERSION,
|
|
22
|
+
} from "../../embedding/protocol.js";
|
|
11
23
|
import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
|
|
12
24
|
import { agentNativePath } from "../api-path.js";
|
|
25
|
+
import { sendToAgentChat, type AgentChatRequestMode } from "../agent-chat.js";
|
|
13
26
|
import { cn } from "../utils.js";
|
|
14
27
|
|
|
15
28
|
export const DEFAULT_MCP_APP_IFRAME_HEIGHT = 650;
|
|
@@ -17,6 +30,8 @@ export const MCP_APP_INITIALIZE_TIMEOUT_MS = 8000;
|
|
|
17
30
|
const MIN_IFRAME_HEIGHT = 220;
|
|
18
31
|
const VIEWPORT_MARGIN = 16;
|
|
19
32
|
const SANDBOX_FLAGS = "allow-scripts allow-forms allow-popups";
|
|
33
|
+
const useBrowserLayoutEffect =
|
|
34
|
+
typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
20
35
|
|
|
21
36
|
export interface McpAppRendererProps {
|
|
22
37
|
app: AgentMcpAppPayload;
|
|
@@ -29,10 +44,24 @@ type ResourceUiMeta = {
|
|
|
29
44
|
prefersBorder?: boolean;
|
|
30
45
|
};
|
|
31
46
|
|
|
47
|
+
type McpContentPart = {
|
|
48
|
+
type?: unknown;
|
|
49
|
+
text?: unknown;
|
|
50
|
+
data?: unknown;
|
|
51
|
+
mimeType?: unknown;
|
|
52
|
+
url?: unknown;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type McpAppModelContext = {
|
|
56
|
+
content?: unknown;
|
|
57
|
+
structuredContent?: unknown;
|
|
58
|
+
};
|
|
59
|
+
|
|
32
60
|
export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
33
61
|
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
34
62
|
const desiredHeightRef = useRef(DEFAULT_MCP_APP_IFRAME_HEIGHT);
|
|
35
|
-
const
|
|
63
|
+
const modelContextRef = useRef<McpAppModelContext | null>(null);
|
|
64
|
+
const readyRef = useRef(false);
|
|
36
65
|
const [height, setHeight] = useState(DEFAULT_MCP_APP_IFRAME_HEIGHT);
|
|
37
66
|
const [error, setError] = useState<string | null>(null);
|
|
38
67
|
const [ready, setReady] = useState(false);
|
|
@@ -49,6 +78,23 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
49
78
|
);
|
|
50
79
|
const externalOpenUrl = useMemo(() => openUrlFromMcpApp(app), [app]);
|
|
51
80
|
|
|
81
|
+
// Keep the latest payload/permissions/csp reachable from the bridge effect
|
|
82
|
+
// without making them effect dependencies. The embedded resource identity is
|
|
83
|
+
// fully captured by `srcDoc`. The bridge effect must NOT re-run when a benign
|
|
84
|
+
// parent re-render hands us a new `app` object reference with identical
|
|
85
|
+
// content (common during chat streaming/polling): re-running tears down a
|
|
86
|
+
// live, already-initialized MCP App (teardownResource) and re-arms the
|
|
87
|
+
// initialize watchdog against a fresh host AppBridge that the embed shell
|
|
88
|
+
// will never re-handshake (its connect promise is memoized), surfacing a
|
|
89
|
+
// false "MCP App did not finish initializing." error after the app is
|
|
90
|
+
// visibly working.
|
|
91
|
+
const appRef = useRef(app);
|
|
92
|
+
const supportedPermissionsRef = useRef(supportedPermissions);
|
|
93
|
+
const uiCspRef = useRef(uiMeta.csp);
|
|
94
|
+
appRef.current = app;
|
|
95
|
+
supportedPermissionsRef.current = supportedPermissions;
|
|
96
|
+
uiCspRef.current = uiMeta.csp;
|
|
97
|
+
|
|
52
98
|
useEffect(() => {
|
|
53
99
|
desiredHeightRef.current = DEFAULT_MCP_APP_IFRAME_HEIGHT;
|
|
54
100
|
setHeight(
|
|
@@ -57,11 +103,18 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
57
103
|
availableMcpAppHeight(iframeRef.current),
|
|
58
104
|
),
|
|
59
105
|
);
|
|
60
|
-
|
|
106
|
+
readyRef.current = false;
|
|
61
107
|
setReady(false);
|
|
62
108
|
setError(null);
|
|
109
|
+
modelContextRef.current = null;
|
|
63
110
|
}, [srcDoc]);
|
|
64
111
|
|
|
112
|
+
const markReady = useCallback(() => {
|
|
113
|
+
readyRef.current = true;
|
|
114
|
+
setReady(true);
|
|
115
|
+
setError(null);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
65
118
|
const applyHeight = useCallback((desiredHeight?: number) => {
|
|
66
119
|
if (
|
|
67
120
|
typeof desiredHeight === "number" &&
|
|
@@ -114,11 +167,26 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
114
167
|
|
|
115
168
|
useEffect(() => {
|
|
116
169
|
const iframe = iframeRef.current;
|
|
117
|
-
if (!iframe?.contentWindow || !srcDoc
|
|
170
|
+
if (!iframe?.contentWindow || !srcDoc) return;
|
|
171
|
+
const frameWindow = iframe.contentWindow;
|
|
172
|
+
const listener = (event: MessageEvent) => {
|
|
173
|
+
if (event.source !== frameWindow) return;
|
|
174
|
+
if (isMcpAppReadyMessage(event.data)) {
|
|
175
|
+
markReady();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
window.addEventListener("message", listener);
|
|
179
|
+
return () => window.removeEventListener("message", listener);
|
|
180
|
+
}, [markReady, srcDoc]);
|
|
181
|
+
|
|
182
|
+
useBrowserLayoutEffect(() => {
|
|
183
|
+
const iframe = iframeRef.current;
|
|
184
|
+
if (!iframe?.contentWindow || !srcDoc) return;
|
|
118
185
|
|
|
186
|
+
const currentApp = appRef.current;
|
|
119
187
|
let closed = false;
|
|
120
188
|
const initializeTimer = window.setTimeout(() => {
|
|
121
|
-
if (closed) return;
|
|
189
|
+
if (closed || readyRef.current) return;
|
|
122
190
|
setReady(false);
|
|
123
191
|
setError("MCP App did not finish initializing.");
|
|
124
192
|
}, MCP_APP_INITIALIZE_TIMEOUT_MS);
|
|
@@ -131,13 +199,13 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
131
199
|
serverResources: {},
|
|
132
200
|
logging: {},
|
|
133
201
|
sandbox: {
|
|
134
|
-
permissions:
|
|
135
|
-
csp:
|
|
202
|
+
permissions: supportedPermissionsRef.current,
|
|
203
|
+
csp: uiCspRef.current ?? {},
|
|
136
204
|
},
|
|
137
205
|
},
|
|
138
206
|
{
|
|
139
207
|
hostContext: buildHostContext(
|
|
140
|
-
|
|
208
|
+
currentApp,
|
|
141
209
|
availableMcpAppHeight(iframe),
|
|
142
210
|
) as any,
|
|
143
211
|
},
|
|
@@ -152,12 +220,9 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
152
220
|
bridge.addEventListener("initialized", () => {
|
|
153
221
|
if (closed) return;
|
|
154
222
|
clearTimeout(initializeTimer);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
setError(null);
|
|
159
|
-
void bridge.sendToolInput({ arguments: app.toolInput });
|
|
160
|
-
void bridge.sendToolResult(app.toolResult as CallToolResult);
|
|
223
|
+
markReady();
|
|
224
|
+
void bridge.sendToolInput({ arguments: appRef.current.toolInput });
|
|
225
|
+
void bridge.sendToolResult(appRef.current.toolResult as CallToolResult);
|
|
161
226
|
});
|
|
162
227
|
bridge.addEventListener("loggingmessage", ({ level, data }) => {
|
|
163
228
|
if (level === "error" || level === "critical" || level === "alert") {
|
|
@@ -170,13 +235,16 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
170
235
|
return {};
|
|
171
236
|
};
|
|
172
237
|
bridge.oncalltool = async ({ name, arguments: toolArguments }) => {
|
|
173
|
-
const toolName = normalizeSameServerToolName(
|
|
238
|
+
const toolName = normalizeSameServerToolName(
|
|
239
|
+
appRef.current.serverId,
|
|
240
|
+
name,
|
|
241
|
+
);
|
|
174
242
|
if (!toolName) {
|
|
175
243
|
return errorToolResult("Cross-server MCP App tool calls are blocked.");
|
|
176
244
|
}
|
|
177
245
|
try {
|
|
178
246
|
return await postMcpAppEndpoint<CallToolResult>("call-tool", {
|
|
179
|
-
serverId:
|
|
247
|
+
serverId: appRef.current.serverId,
|
|
180
248
|
toolName,
|
|
181
249
|
arguments:
|
|
182
250
|
toolArguments && typeof toolArguments === "object"
|
|
@@ -188,22 +256,40 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
188
256
|
}
|
|
189
257
|
};
|
|
190
258
|
(bridge as any).onlisttools = async () =>
|
|
191
|
-
postMcpAppEndpoint("list-tools", { serverId:
|
|
259
|
+
postMcpAppEndpoint("list-tools", { serverId: appRef.current.serverId });
|
|
192
260
|
bridge.onreadresource = async ({ uri }) =>
|
|
193
|
-
postMcpAppEndpoint("read-resource", {
|
|
261
|
+
postMcpAppEndpoint("read-resource", {
|
|
262
|
+
serverId: appRef.current.serverId,
|
|
263
|
+
uri,
|
|
264
|
+
});
|
|
194
265
|
bridge.onlistresources = async () => ({ resources: [] });
|
|
195
266
|
bridge.onlistresourcetemplates = async () => ({ resourceTemplates: [] });
|
|
196
267
|
bridge.ondownloadfile = async () => ({ isError: true });
|
|
197
|
-
bridge.onmessage = async () =>
|
|
198
|
-
|
|
268
|
+
bridge.onmessage = async (params) => {
|
|
269
|
+
const message = messageTextFromMcpUiMessage(params);
|
|
270
|
+
if (!message.trim()) return { isError: true };
|
|
271
|
+
const mode = requestModeFromMcpUiMessage(params);
|
|
272
|
+
sendToAgentChat({
|
|
273
|
+
message,
|
|
274
|
+
context: contextTextFromMcpModelContext(modelContextRef.current),
|
|
275
|
+
images: imageDataUrlsFromMcpContent(params.content),
|
|
276
|
+
submit: true,
|
|
277
|
+
openSidebar: true,
|
|
278
|
+
...(mode ? { mode } : {}),
|
|
279
|
+
});
|
|
280
|
+
return {};
|
|
281
|
+
};
|
|
282
|
+
bridge.onupdatemodelcontext = async (params) => {
|
|
283
|
+
modelContextRef.current = params as McpAppModelContext;
|
|
284
|
+
return {};
|
|
285
|
+
};
|
|
199
286
|
|
|
200
287
|
const transport = new PostMessageTransport(
|
|
201
288
|
iframe.contentWindow,
|
|
202
289
|
iframe.contentWindow,
|
|
203
290
|
);
|
|
204
|
-
setReady(false);
|
|
205
291
|
setError(null);
|
|
206
|
-
bridge.connect(transport).catch((err: any) => {
|
|
292
|
+
void bridge.connect(transport).catch((err: any) => {
|
|
207
293
|
if (!closed) {
|
|
208
294
|
clearTimeout(initializeTimer);
|
|
209
295
|
setError(err?.message ?? "Failed to initialize MCP App.");
|
|
@@ -212,8 +298,8 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
212
298
|
|
|
213
299
|
return () => {
|
|
214
300
|
closed = true;
|
|
301
|
+
modelContextRef.current = null;
|
|
215
302
|
clearTimeout(initializeTimer);
|
|
216
|
-
setReady(false);
|
|
217
303
|
void bridge
|
|
218
304
|
.teardownResource({}, { timeout: 500 })
|
|
219
305
|
.catch(() => undefined)
|
|
@@ -221,14 +307,10 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
221
307
|
void (bridge as any).close?.().catch?.(() => undefined);
|
|
222
308
|
});
|
|
223
309
|
};
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
srcDoc,
|
|
229
|
-
supportedPermissions,
|
|
230
|
-
uiMeta.csp,
|
|
231
|
-
]);
|
|
310
|
+
// The embedded resource identity is captured by `srcDoc`; `app`,
|
|
311
|
+
// `supportedPermissions`, and `uiMeta.csp` are read via refs so a
|
|
312
|
+
// new-but-equal `app` object reference does not tear down a live bridge.
|
|
313
|
+
}, [applyHeight, markReady, srcDoc]);
|
|
232
314
|
|
|
233
315
|
if (!resourceHtml) {
|
|
234
316
|
return (
|
|
@@ -277,12 +359,83 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
|
|
|
277
359
|
sandbox={SANDBOX_FLAGS}
|
|
278
360
|
allow={buildAllowAttribute(supportedPermissions)}
|
|
279
361
|
style={{ height }}
|
|
280
|
-
onLoad={() => setLoadedSrcDoc(srcDoc)}
|
|
281
362
|
/>
|
|
282
363
|
</div>
|
|
283
364
|
);
|
|
284
365
|
}
|
|
285
366
|
|
|
367
|
+
function messageTextFromMcpUiMessage(params: { content?: unknown }): string {
|
|
368
|
+
return textPartsFromMcpContent(params.content).join("\n\n").trim();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function requestModeFromMcpUiMessage(
|
|
372
|
+
params: unknown,
|
|
373
|
+
): AgentChatRequestMode | undefined {
|
|
374
|
+
const record =
|
|
375
|
+
params && typeof params === "object" && !Array.isArray(params)
|
|
376
|
+
? (params as { mode?: unknown; requestMode?: unknown })
|
|
377
|
+
: {};
|
|
378
|
+
const mode = record.requestMode ?? record.mode;
|
|
379
|
+
return mode === "act" || mode === "plan" ? mode : undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function contextTextFromMcpModelContext(
|
|
383
|
+
context: McpAppModelContext | null,
|
|
384
|
+
): string | undefined {
|
|
385
|
+
if (!context) return undefined;
|
|
386
|
+
const parts = textPartsFromMcpContent(context.content);
|
|
387
|
+
if (context.structuredContent !== undefined) {
|
|
388
|
+
parts.push(JSON.stringify(context.structuredContent, null, 2));
|
|
389
|
+
}
|
|
390
|
+
const text = parts.join("\n\n").trim();
|
|
391
|
+
return text || undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function textPartsFromMcpContent(content: unknown): string[] {
|
|
395
|
+
if (!Array.isArray(content)) return [];
|
|
396
|
+
return content
|
|
397
|
+
.map((part): string | null => {
|
|
398
|
+
const record = contentPartRecord(part);
|
|
399
|
+
if (
|
|
400
|
+
!record ||
|
|
401
|
+
record.type !== "text" ||
|
|
402
|
+
typeof record.text !== "string"
|
|
403
|
+
) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
const text = record.text.trim();
|
|
407
|
+
return text || null;
|
|
408
|
+
})
|
|
409
|
+
.filter((text): text is string => Boolean(text));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function imageDataUrlsFromMcpContent(content: unknown): string[] | undefined {
|
|
413
|
+
if (!Array.isArray(content)) return undefined;
|
|
414
|
+
const images = content
|
|
415
|
+
.map((part): string | null => {
|
|
416
|
+
const record = contentPartRecord(part);
|
|
417
|
+
if (!record || record.type !== "image") return null;
|
|
418
|
+
if (typeof record.url === "string" && record.url.trim()) {
|
|
419
|
+
return record.url;
|
|
420
|
+
}
|
|
421
|
+
if (typeof record.data !== "string" || !record.data.trim()) return null;
|
|
422
|
+
const mimeType =
|
|
423
|
+
typeof record.mimeType === "string" &&
|
|
424
|
+
record.mimeType.startsWith("image/")
|
|
425
|
+
? record.mimeType
|
|
426
|
+
: "image/png";
|
|
427
|
+
return `data:${mimeType};base64,${record.data}`;
|
|
428
|
+
})
|
|
429
|
+
.filter((image): image is string => Boolean(image));
|
|
430
|
+
return images.length ? images : undefined;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function contentPartRecord(part: unknown): McpContentPart | null {
|
|
434
|
+
return part && typeof part === "object" && !Array.isArray(part)
|
|
435
|
+
? (part as McpContentPart)
|
|
436
|
+
: null;
|
|
437
|
+
}
|
|
438
|
+
|
|
286
439
|
function resourceUiMeta(app: AgentMcpAppPayload): ResourceUiMeta {
|
|
287
440
|
const meta = app.resource?._meta;
|
|
288
441
|
const ui =
|
|
@@ -341,8 +494,31 @@ export function supportedMcpAppPermissions(
|
|
|
341
494
|
return permissions?.clipboardWrite ? { clipboardWrite: {} } : {};
|
|
342
495
|
}
|
|
343
496
|
|
|
497
|
+
export function isMcpAppReadyMessage(data: unknown): boolean {
|
|
498
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return false;
|
|
499
|
+
const record = data as {
|
|
500
|
+
protocol?: unknown;
|
|
501
|
+
version?: unknown;
|
|
502
|
+
type?: unknown;
|
|
503
|
+
};
|
|
504
|
+
if (
|
|
505
|
+
record.protocol === AGENT_NATIVE_EMBED_PROTOCOL &&
|
|
506
|
+
record.version === AGENT_NATIVE_EMBED_VERSION &&
|
|
507
|
+
record.type === AGENT_NATIVE_EMBED_MESSAGE_TYPES.READY
|
|
508
|
+
) {
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
const type = record.type;
|
|
512
|
+
return (
|
|
513
|
+
type === "agentNative.embeddedAppReady" ||
|
|
514
|
+
type === "agentNative.frameOrigin"
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
344
518
|
export function buildMcpAppCsp(csp: McpUiResourceCsp | undefined): string {
|
|
345
|
-
const connect =
|
|
519
|
+
const connect = withLocalWebSocketSources(
|
|
520
|
+
sanitizeCspSources(csp?.connectDomains),
|
|
521
|
+
);
|
|
346
522
|
const resources = sanitizeCspSources(csp?.resourceDomains);
|
|
347
523
|
const frames = sanitizeCspSources(csp?.frameDomains);
|
|
348
524
|
const base = sanitizeCspSources(csp?.baseUriDomains);
|
|
@@ -369,6 +545,24 @@ function sanitizeCspSources(values: string[] | undefined): string[] {
|
|
|
369
545
|
return [...new Set(out)];
|
|
370
546
|
}
|
|
371
547
|
|
|
548
|
+
function withLocalWebSocketSources(sources: string[]): string[] {
|
|
549
|
+
const out = [...sources];
|
|
550
|
+
for (const source of sources) {
|
|
551
|
+
try {
|
|
552
|
+
const url = new URL(source);
|
|
553
|
+
if (
|
|
554
|
+
url.protocol === "http:" &&
|
|
555
|
+
["localhost", "127.0.0.1", "::1"].includes(url.hostname)
|
|
556
|
+
) {
|
|
557
|
+
out.push(`ws://${url.host}`);
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
// Ignore non-URL CSP source expressions.
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return [...new Set(out)];
|
|
564
|
+
}
|
|
565
|
+
|
|
372
566
|
function sanitizeCspSource(value: string): string | null {
|
|
373
567
|
const source = value.trim();
|
|
374
568
|
if (!source || source.includes("'") || /[\s;]/.test(source)) return null;
|
|
@@ -33,7 +33,9 @@ export function DemoModeSection() {
|
|
|
33
33
|
const { data } = useQuery({
|
|
34
34
|
queryKey: ["agent-native", "demo-mode"],
|
|
35
35
|
queryFn: async () => {
|
|
36
|
-
const res = await fetch(DEMO_STATUS_URL, {
|
|
36
|
+
const res = await fetch(DEMO_STATUS_URL, {
|
|
37
|
+
credentials: "same-origin",
|
|
38
|
+
});
|
|
37
39
|
if (!res.ok) return null;
|
|
38
40
|
return (await res.json()) as DemoStatus | null;
|
|
39
41
|
},
|
|
@@ -91,7 +91,7 @@ function methodOf(input: RequestInfo | URL, init?: RequestInit): string {
|
|
|
91
91
|
async function refreshDemoFlag(): Promise<void> {
|
|
92
92
|
const f = originalFetch ?? fetch;
|
|
93
93
|
try {
|
|
94
|
-
const res = await f(STATUS_PATH, { credentials: "
|
|
94
|
+
const res = await f(STATUS_PATH, { credentials: "same-origin" });
|
|
95
95
|
if (!res.ok) return;
|
|
96
96
|
const json = (await res.json()) as {
|
|
97
97
|
enabled?: boolean;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MCP_APP_CHAT_BRIDGE_QUERY_PARAM,
|
|
3
|
+
EMBED_MODE_QUERY_PARAM,
|
|
4
|
+
} from "../shared/embed-auth.js";
|
|
1
5
|
import {
|
|
2
6
|
AGENT_NATIVE_EMBED_MESSAGE_TYPES,
|
|
3
7
|
createAgentNativeEmbedEnvelope,
|
|
@@ -61,10 +65,38 @@ type PendingRequest = {
|
|
|
61
65
|
|
|
62
66
|
function resolveParentOrigin(win: Window, explicit?: string): string | null {
|
|
63
67
|
if (explicit) return explicit;
|
|
68
|
+
if (shouldUseOpaqueParentTarget(win)) return "*";
|
|
64
69
|
const referrerOrigin = embeddedAppOrigin(win.document?.referrer ?? "");
|
|
65
70
|
return referrerOrigin;
|
|
66
71
|
}
|
|
67
72
|
|
|
73
|
+
function shouldUseOpaqueParentTarget(win: Window): boolean {
|
|
74
|
+
try {
|
|
75
|
+
const url = new URL(win.location.href);
|
|
76
|
+
const chatBridge = url.searchParams.get(MCP_APP_CHAT_BRIDGE_QUERY_PARAM);
|
|
77
|
+
const embedMode = url.searchParams.get(EMBED_MODE_QUERY_PARAM);
|
|
78
|
+
return (
|
|
79
|
+
chatBridge === "1" ||
|
|
80
|
+
chatBridge === "true" ||
|
|
81
|
+
embedMode === "1" ||
|
|
82
|
+
embedMode === "true"
|
|
83
|
+
);
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function defaultAllowedOrigins(
|
|
90
|
+
parentOrigin: string | null,
|
|
91
|
+
referrerOrigin: string | null,
|
|
92
|
+
): string[] {
|
|
93
|
+
if (!parentOrigin) return [];
|
|
94
|
+
if (parentOrigin === "*") {
|
|
95
|
+
return ["null", ...(referrerOrigin ? [referrerOrigin] : [])];
|
|
96
|
+
}
|
|
97
|
+
return [parentOrigin];
|
|
98
|
+
}
|
|
99
|
+
|
|
68
100
|
function postToParent(
|
|
69
101
|
win: Window,
|
|
70
102
|
parent: Window | null,
|
|
@@ -83,9 +115,10 @@ export function createEmbeddedAppBridge(
|
|
|
83
115
|
): EmbeddedAppBridge {
|
|
84
116
|
const win = options.currentWindow ?? window;
|
|
85
117
|
const parentOrigin = resolveParentOrigin(win, options.parentOrigin);
|
|
118
|
+
const referrerOrigin = embeddedAppOrigin(win.document?.referrer ?? "");
|
|
86
119
|
const allowedOrigins =
|
|
87
120
|
options.allowedOrigins ??
|
|
88
|
-
(parentOrigin
|
|
121
|
+
defaultAllowedOrigins(parentOrigin, referrerOrigin);
|
|
89
122
|
const pending = new Map<string, PendingRequest>();
|
|
90
123
|
let destroyed = false;
|
|
91
124
|
|
|
@@ -152,6 +152,8 @@ export interface MCPCallerIdentity {
|
|
|
152
152
|
oauthScopes?: string[];
|
|
153
153
|
/** Present only for standard remote MCP OAuth access tokens. */
|
|
154
154
|
oauthClientId?: string;
|
|
155
|
+
/** Present only for framework-minted first-party MCP client tokens. */
|
|
156
|
+
firstPartyMcp?: boolean;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
/** Per-request context used to turn an action's relative deep link into the
|
|
@@ -748,7 +750,7 @@ function safeUiSegment(value: string | undefined, fallback: string): string {
|
|
|
748
750
|
|
|
749
751
|
// ChatGPT and Claude cache MCP App resource HTML by `ui://` URI. Bump this
|
|
750
752
|
// when the shared shell changes in a way that must invalidate host caches.
|
|
751
|
-
const MCP_APP_RESOURCE_SHELL_VERSION = "shell-
|
|
753
|
+
const MCP_APP_RESOURCE_SHELL_VERSION = "shell-v51";
|
|
752
754
|
|
|
753
755
|
function legacyDefaultMcpAppUri(config: MCPConfig, actionName: string): string {
|
|
754
756
|
const app = safeUiSegment(config.appId ?? config.name, "agent-native");
|
|
@@ -1785,6 +1787,7 @@ function addSecretCandidate(
|
|
|
1785
1787
|
|
|
1786
1788
|
async function verifyA2AJwtForMcp(
|
|
1787
1789
|
token: string,
|
|
1790
|
+
resourceUrl?: string | string[],
|
|
1788
1791
|
): Promise<Record<string, unknown> | null> {
|
|
1789
1792
|
const jose = await import("jose");
|
|
1790
1793
|
let unverifiedPayload: Record<string, unknown> | null = null;
|
|
@@ -1813,21 +1816,43 @@ async function verifyA2AJwtForMcp(
|
|
|
1813
1816
|
}
|
|
1814
1817
|
}
|
|
1815
1818
|
|
|
1819
|
+
const firstPartyMcp = unverifiedPayload.agent_native_first_party_mcp === true;
|
|
1820
|
+
const audiences = firstPartyMcp ? mcpAudienceList(resourceUrl) : null;
|
|
1821
|
+
if (firstPartyMcp && !audiences?.length) return null;
|
|
1822
|
+
|
|
1816
1823
|
for (const secret of candidateSecrets) {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1824
|
+
const encodedSecret = new TextEncoder().encode(secret);
|
|
1825
|
+
for (const audience of audiences ?? [undefined]) {
|
|
1826
|
+
try {
|
|
1827
|
+
const { payload } = await jose.jwtVerify(
|
|
1828
|
+
token,
|
|
1829
|
+
encodedSecret,
|
|
1830
|
+
audience ? { audience } : undefined,
|
|
1831
|
+
);
|
|
1832
|
+
return payload as Record<string, unknown>;
|
|
1833
|
+
} catch {
|
|
1834
|
+
// Try the next candidate without exposing which secret matched.
|
|
1835
|
+
}
|
|
1825
1836
|
}
|
|
1826
1837
|
}
|
|
1827
1838
|
|
|
1828
1839
|
return null;
|
|
1829
1840
|
}
|
|
1830
1841
|
|
|
1842
|
+
function mcpAudienceList(resource: string | string[] | undefined): string[] {
|
|
1843
|
+
const raw = Array.isArray(resource) ? resource : resource ? [resource] : [];
|
|
1844
|
+
const seen = new Set<string>();
|
|
1845
|
+
const out: string[] = [];
|
|
1846
|
+
for (const value of raw) {
|
|
1847
|
+
const normalized = value.replace(/\/+$/, "");
|
|
1848
|
+
if (normalized && !seen.has(normalized)) {
|
|
1849
|
+
seen.add(normalized);
|
|
1850
|
+
out.push(normalized);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return out;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1831
1856
|
async function isConnectTokenAllowed(
|
|
1832
1857
|
jti: string | undefined,
|
|
1833
1858
|
): Promise<boolean> {
|
|
@@ -1941,7 +1966,7 @@ export async function verifyAuth(
|
|
|
1941
1966
|
|
|
1942
1967
|
// Try an A2A JWT via the shared A2A_SECRET first, then the caller org's
|
|
1943
1968
|
// synced A2A secret when the token carries org_domain.
|
|
1944
|
-
const payload = await verifyA2AJwtForMcp(token);
|
|
1969
|
+
const payload = await verifyA2AJwtForMcp(token, options.resourceUrl);
|
|
1945
1970
|
if (payload) {
|
|
1946
1971
|
const tokenScope =
|
|
1947
1972
|
typeof payload.scope === "string" ? payload.scope : undefined;
|
|
@@ -1976,6 +2001,9 @@ export async function verifyAuth(
|
|
|
1976
2001
|
typeof payload.org_domain === "string"
|
|
1977
2002
|
? (payload.org_domain as string)
|
|
1978
2003
|
: undefined,
|
|
2004
|
+
...(payload.agent_native_first_party_mcp === true
|
|
2005
|
+
? { firstPartyMcp: true }
|
|
2006
|
+
: {}),
|
|
1979
2007
|
},
|
|
1980
2008
|
// Verified JWT (connect-minted or A2A delegation) — a real caller.
|
|
1981
2009
|
fullSurface: true,
|