@agent-native/core 0.128.3 → 0.129.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 +2 -2
- package/corpus/core/CHANGELOG.md +91 -0
- package/corpus/core/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
- package/corpus/core/package.json +6 -3
- package/corpus/core/src/a2a/client.ts +18 -4
- package/corpus/core/src/a2a/server.ts +19 -3
- package/corpus/core/src/a2a/types.ts +7 -0
- package/corpus/core/src/action.ts +43 -16
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -10
- package/corpus/core/src/agent/production-agent.ts +41 -3
- package/corpus/core/src/agent/thread-data-builder.ts +5 -0
- package/corpus/core/src/chat-threads/migrations.ts +53 -0
- package/corpus/core/src/cli/create.ts +4 -0
- package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
- package/corpus/core/src/cli/skills.ts +16 -0
- package/corpus/core/src/client/AgentPanel.tsx +9 -5
- package/corpus/core/src/client/AssistantChat.tsx +138 -46
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +16 -10
- package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
- package/corpus/core/src/client/analytics.ts +159 -102
- package/corpus/core/src/client/chat/legacy-chart-shorthand.tsx +479 -0
- package/corpus/core/src/client/chat/markdown-renderer.tsx +23 -3
- package/corpus/core/src/client/chat/message-components.tsx +12 -5
- package/corpus/core/src/client/chat/runtime.ts +2 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +25 -2
- package/corpus/core/src/client/chat-model-groups.ts +3 -0
- package/corpus/core/src/client/client-status-requests.ts +161 -0
- package/corpus/core/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
- package/corpus/core/src/client/sse-event-processor.ts +15 -3
- package/corpus/core/src/client/use-agent-engine-configured.ts +54 -50
- package/corpus/core/src/client/use-chat-models.ts +16 -11
- package/corpus/core/src/client/use-chat-threads.ts +32 -9
- package/corpus/core/src/client/use-session.ts +4 -5
- package/corpus/core/src/db/client.ts +83 -8
- package/corpus/core/src/db/ensure-additive-columns.ts +31 -20
- package/corpus/core/src/db/migrations.ts +22 -2
- package/corpus/core/src/extensions/actions.ts +1 -1
- package/corpus/core/src/mcp/approval-store.ts +99 -0
- package/corpus/core/src/mcp/build-server.ts +579 -303
- package/corpus/core/src/mcp/embed-app.ts +1 -1
- package/corpus/core/src/mcp/oauth-client-metadata.ts +380 -0
- package/corpus/core/src/mcp/oauth-route.ts +57 -52
- package/corpus/core/src/mcp/screen-memory-stdio.ts +400 -378
- package/corpus/core/src/mcp/server.ts +29 -145
- package/corpus/core/src/mcp/stdio.ts +72 -80
- package/corpus/core/src/mcp-client/manager.ts +6 -10
- package/corpus/core/src/mcp-client/oauth-client.ts +127 -36
- package/corpus/core/src/mcp-client/oauth-routes.ts +16 -6
- package/corpus/core/src/mcp-client/routes.ts +6 -5
- package/corpus/core/src/server/agent-capabilities.ts +20 -0
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +27 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +18 -0
- package/corpus/core/src/server/transcribe-voice.ts +14 -5
- package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +13 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +12 -0
- package/corpus/templates/analytics/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/analytics/actions/generate-chart.ts +2 -2
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -10
- package/corpus/templates/analytics/app/global.css +0 -31
- package/corpus/templates/analytics/app/lib/sql-query.ts +46 -20
- package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboard-loading-states-now-use-a-simpler-standar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboards-now-load-first-party-charts-reliably-in.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-cache.ts +42 -16
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +4 -2
- package/corpus/templates/analytics/shared/sql-query-limits.ts +1 -0
- package/corpus/templates/assets/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/brain/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/calendar/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/chat/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/clips/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -6
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +6 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-07-28-copied-share-links-no-longer-leave-an-unnecessary-confirmati.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-28-playback-skip-controls-no-longer-show-overlapping-numbers.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-28-public-clips-share-buttons-now-use-a-clean-text-only-label.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +14 -17
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +3 -1
- package/corpus/templates/clips/desktop/src/styles.css +13 -10
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +36 -0
- package/corpus/templates/content/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +155 -34
- package/corpus/templates/content/actions/_builder-docs-client.ts +151 -30
- package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +5 -2
- package/corpus/templates/content/app/root.tsx +35 -14
- package/corpus/templates/content/changelog/2026-07-28-builder-backed-files-media-fields-now-accept-direct-file-url.md +6 -0
- package/corpus/templates/content/changelog/2026-07-28-builder-content-reads-now-negotiate-the-newest-mcp-protocol-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-28-public-content-pages-load-faster-before-the-optional-ask-sid.md +6 -0
- package/corpus/templates/design/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +36 -16
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +42 -3
- package/corpus/templates/design/actions/open-visual-edit.ts +2 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -15
- package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +314 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +88 -3
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +1 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +7 -6
- package/corpus/templates/design/app/i18n-data.ts +56 -11
- package/corpus/templates/design/app/i18n-responsive-interact.ts +183 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +60 -0
- package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +89 -0
- package/corpus/templates/design/changelog/2026-07-28-design-edits-can-be-applied-through-the-host-coding-agent-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-show-or-hide-design-editing-chrome-with-cmd-on-apple-devices.md +2 -2
- package/corpus/templates/design/changelog/2026-07-28-visual-edit-now-always-shows-the-live-running-app-in-the-can.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/forms/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/macros/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/mail/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/plan/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/slides/.agents/skills/security/SKILL.md +13 -2
- package/corpus/templates/tasks/.agents/skills/security/SKILL.md +13 -2
- package/corpus/toolkit/CHANGELOG.md +13 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/chat-history.css +1 -2
- package/corpus/toolkit/src/composer/TiptapComposer.tsx +45 -0
- package/dist/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +8 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +18 -3
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/types.d.ts +7 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +15 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +39 -16
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +14 -10
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +35 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +2 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +3 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/chat-threads/migrations.d.ts +21 -0
- package/dist/chat-threads/migrations.d.ts.map +1 -0
- package/dist/chat-threads/migrations.js +45 -0
- package/dist/chat-threads/migrations.js.map +1 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +4 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.js +36 -16
- package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +15 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -5
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +37 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +41 -8
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +11 -10
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +19 -3
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +150 -101
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/legacy-chart-shorthand.d.ts +29 -0
- package/dist/client/chat/legacy-chart-shorthand.d.ts.map +1 -0
- package/dist/client/chat/legacy-chart-shorthand.js +330 -0
- package/dist/client/chat/legacy-chart-shorthand.js.map +1 -0
- package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
- package/dist/client/chat/markdown-renderer.js +8 -2
- package/dist/client/chat/markdown-renderer.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +2 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +11 -4
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/runtime.d.ts.map +1 -1
- package/dist/client/chat/runtime.js +2 -1
- package/dist/client/chat/runtime.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +4 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +14 -8
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +3 -0
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/client-status-requests.d.ts +14 -0
- package/dist/client/client-status-requests.d.ts.map +1 -0
- package/dist/client/client-status-requests.js +126 -0
- package/dist/client/client-status-requests.js.map +1 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +7 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +8 -3
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +29 -43
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +11 -11
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +23 -7
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/client/use-session.d.ts.map +1 -1
- package/dist/client/use-session.js +4 -4
- package/dist/client/use-session.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +48 -4
- package/dist/db/client.js.map +1 -1
- package/dist/db/ensure-additive-columns.d.ts.map +1 -1
- package/dist/db/ensure-additive-columns.js +32 -21
- package/dist/db/ensure-additive-columns.js.map +1 -1
- package/dist/db/migrations.d.ts +9 -0
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +14 -3
- package/dist/db/migrations.js.map +1 -1
- package/dist/extensions/actions.js +1 -1
- package/dist/extensions/actions.js.map +1 -1
- package/dist/mcp/approval-store.d.ts +16 -0
- package/dist/mcp/approval-store.d.ts.map +1 -0
- package/dist/mcp/approval-store.js +85 -0
- package/dist/mcp/approval-store.js.map +1 -0
- package/dist/mcp/build-server.d.ts +1 -34
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +220 -23
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.js +1 -1
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/oauth-client-metadata.d.ts +15 -0
- package/dist/mcp/oauth-client-metadata.d.ts.map +1 -0
- package/dist/mcp/oauth-client-metadata.js +297 -0
- package/dist/mcp/oauth-client-metadata.js.map +1 -0
- package/dist/mcp/oauth-route.d.ts.map +1 -1
- package/dist/mcp/oauth-route.js +39 -53
- package/dist/mcp/oauth-route.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp/screen-memory-stdio.js +304 -296
- package/dist/mcp/screen-memory-stdio.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -25
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +24 -133
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +36 -35
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +6 -6
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/oauth-client.d.ts +13 -17
- package/dist/mcp-client/oauth-client.d.ts.map +1 -1
- package/dist/mcp-client/oauth-client.js +74 -16
- package/dist/mcp-client/oauth-client.js.map +1 -1
- package/dist/mcp-client/oauth-routes.d.ts +2 -2
- package/dist/mcp-client/oauth-routes.d.ts.map +1 -1
- package/dist/mcp-client/oauth-routes.js +16 -6
- package/dist/mcp-client/oauth-routes.js.map +1 -1
- package/dist/mcp-client/routes.js +5 -5
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-capabilities.d.ts.map +1 -1
- package/dist/server/agent-capabilities.js +21 -0
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +16 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +11 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.js +12 -6
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/templates/chat/.agents/skills/security/SKILL.md +13 -2
- package/dist/templates/default/.agents/skills/security/SKILL.md +13 -2
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
- package/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
- package/package.json +7 -4
- package/src/a2a/client.ts +18 -4
- package/src/a2a/server.ts +19 -3
- package/src/a2a/types.ts +7 -0
- package/src/action.ts +43 -16
- package/src/agent/engine/ai-sdk-engine.ts +14 -10
- package/src/agent/production-agent.ts +41 -3
- package/src/agent/thread-data-builder.ts +5 -0
- package/src/chat-threads/migrations.ts +53 -0
- package/src/cli/create.ts +4 -0
- package/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
- package/src/cli/skills.ts +16 -0
- package/src/client/AgentPanel.tsx +9 -5
- package/src/client/AssistantChat.tsx +138 -46
- package/src/client/MultiTabAssistantChat.tsx +16 -10
- package/src/client/agent-chat-adapter.ts +21 -3
- package/src/client/analytics.ts +159 -102
- package/src/client/chat/legacy-chart-shorthand.tsx +479 -0
- package/src/client/chat/markdown-renderer.tsx +23 -3
- package/src/client/chat/message-components.tsx +12 -5
- package/src/client/chat/runtime.ts +2 -1
- package/src/client/chat/tool-call-display.tsx +25 -2
- package/src/client/chat-model-groups.ts +3 -0
- package/src/client/client-status-requests.ts +161 -0
- package/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
- package/src/client/sse-event-processor.ts +15 -3
- package/src/client/use-agent-engine-configured.ts +54 -50
- package/src/client/use-chat-models.ts +16 -11
- package/src/client/use-chat-threads.ts +32 -9
- package/src/client/use-session.ts +4 -5
- package/src/db/client.ts +83 -8
- package/src/db/ensure-additive-columns.ts +31 -20
- package/src/db/migrations.ts +22 -2
- package/src/extensions/actions.ts +1 -1
- package/src/mcp/approval-store.ts +99 -0
- package/src/mcp/build-server.ts +579 -303
- package/src/mcp/embed-app.ts +1 -1
- package/src/mcp/oauth-client-metadata.ts +380 -0
- package/src/mcp/oauth-route.ts +57 -52
- package/src/mcp/screen-memory-stdio.ts +400 -378
- package/src/mcp/server.ts +29 -145
- package/src/mcp/stdio.ts +72 -80
- package/src/mcp-client/manager.ts +6 -10
- package/src/mcp-client/oauth-client.ts +127 -36
- package/src/mcp-client/oauth-routes.ts +16 -6
- package/src/mcp-client/routes.ts +6 -5
- package/src/server/agent-capabilities.ts +20 -0
- package/src/server/agent-chat/action-filters-a2a.ts +27 -0
- package/src/server/agent-chat-plugin.ts +18 -0
- package/src/server/transcribe-voice.ts +14 -5
- package/src/templates/chat/.agents/skills/security/SKILL.md +13 -2
- package/src/templates/default/.agents/skills/security/SKILL.md +13 -2
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
package/src/mcp/build-server.ts
CHANGED
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
* — it can be bundled into the serverless function alongside `mountMCP`.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import type {
|
|
22
|
+
CallToolResult,
|
|
23
|
+
InputRequiredResult,
|
|
24
|
+
RequestStateCodec,
|
|
25
|
+
ServerContext,
|
|
26
|
+
Tool,
|
|
27
|
+
} from "@modelcontextprotocol/server";
|
|
28
|
+
|
|
21
29
|
import {
|
|
22
30
|
MCP_APP_EXTENSION_ID,
|
|
23
31
|
MCP_APP_MIME_TYPE,
|
|
@@ -45,6 +53,10 @@ import {
|
|
|
45
53
|
withCollapsedAgentSidebarParam,
|
|
46
54
|
} from "../shared/agent-sidebar-url.js";
|
|
47
55
|
import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
|
|
56
|
+
import {
|
|
57
|
+
consumeMcpApprovalGrant,
|
|
58
|
+
createMcpApprovalGrant,
|
|
59
|
+
} from "./approval-store.js";
|
|
48
60
|
import { getBuiltinCrossAppTools } from "./builtin-tools.js";
|
|
49
61
|
import {
|
|
50
62
|
MCP_CONNECT_OAUTH_CLIENT_ID,
|
|
@@ -163,6 +175,81 @@ export interface MCPCallerIdentity {
|
|
|
163
175
|
firstPartyMcp?: boolean;
|
|
164
176
|
}
|
|
165
177
|
|
|
178
|
+
const MCP_ACTION_APPROVAL_TTL_SECONDS = 10 * 60;
|
|
179
|
+
const MCP_ACTION_APPROVAL_INPUT_KEY = "actionApproval";
|
|
180
|
+
|
|
181
|
+
interface McpActionApprovalState {
|
|
182
|
+
version: 1;
|
|
183
|
+
nonce: string;
|
|
184
|
+
actionName: string;
|
|
185
|
+
argumentsHash: string;
|
|
186
|
+
expiresAt: number;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function canonicalJson(value: unknown): string {
|
|
190
|
+
if (
|
|
191
|
+
value === null ||
|
|
192
|
+
typeof value === "boolean" ||
|
|
193
|
+
typeof value === "string"
|
|
194
|
+
) {
|
|
195
|
+
return JSON.stringify(value);
|
|
196
|
+
}
|
|
197
|
+
if (typeof value === "number") {
|
|
198
|
+
if (!Number.isFinite(value)) {
|
|
199
|
+
throw new TypeError("MCP action arguments must contain finite numbers");
|
|
200
|
+
}
|
|
201
|
+
return JSON.stringify(value);
|
|
202
|
+
}
|
|
203
|
+
if (Array.isArray(value)) {
|
|
204
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
205
|
+
}
|
|
206
|
+
if (value && typeof value === "object") {
|
|
207
|
+
const record = value as Record<string, unknown>;
|
|
208
|
+
return `{${Object.keys(record)
|
|
209
|
+
.sort()
|
|
210
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
211
|
+
.join(",")}}`;
|
|
212
|
+
}
|
|
213
|
+
throw new TypeError("MCP action arguments must be JSON values");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function sha256Base64Url(value: string): Promise<string> {
|
|
217
|
+
const digest = await globalThis.crypto.subtle.digest(
|
|
218
|
+
"SHA-256",
|
|
219
|
+
new TextEncoder().encode(value),
|
|
220
|
+
);
|
|
221
|
+
const bytes = new Uint8Array(digest);
|
|
222
|
+
let binary = "";
|
|
223
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
224
|
+
return btoa(binary)
|
|
225
|
+
.replace(/\+/g, "-")
|
|
226
|
+
.replace(/\//g, "_")
|
|
227
|
+
.replace(/=+$/, "");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function mcpApprovalPrincipal(
|
|
231
|
+
identity: MCPCallerIdentity | undefined,
|
|
232
|
+
orgId: string | undefined,
|
|
233
|
+
requestMeta: MCPRequestMeta | undefined,
|
|
234
|
+
): string {
|
|
235
|
+
return canonicalJson({
|
|
236
|
+
caller: "mcp",
|
|
237
|
+
userEmail: identity?.userEmail?.trim().toLowerCase() ?? "",
|
|
238
|
+
orgId: orgId ?? identity?.orgId ?? "",
|
|
239
|
+
orgDomain: identity?.orgDomain?.trim().toLowerCase() ?? "",
|
|
240
|
+
oauthClientId: identity?.oauthClientId ?? "",
|
|
241
|
+
firstPartyMcp: identity?.firstPartyMcp === true,
|
|
242
|
+
origin: requestMeta?.origin ?? "",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function actionApprovalError(message: string): CallToolResult {
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: "text" as const, text: message }],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
166
253
|
/** Per-request context used to turn an action's relative deep link into the
|
|
167
254
|
* absolute web URL (and desktop `agentnative://` URL) the external agent
|
|
168
255
|
* surfaces. Derived from the inbound request headers in `mountMCP`, or from
|
|
@@ -935,6 +1022,10 @@ function mcpServerInfo(config: MCPConfig, requestMeta?: MCPRequestMeta) {
|
|
|
935
1022
|
};
|
|
936
1023
|
}
|
|
937
1024
|
|
|
1025
|
+
function compareMcpCatalogValues(a: string, b: string): number {
|
|
1026
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
938
1029
|
function safeUiSegment(value: string | undefined, fallback: string): string {
|
|
939
1030
|
const normalized = (value || fallback)
|
|
940
1031
|
.trim()
|
|
@@ -1419,14 +1510,13 @@ export async function createMCPServerForRequest(
|
|
|
1419
1510
|
identity: MCPCallerIdentity | undefined,
|
|
1420
1511
|
requestMeta?: MCPRequestMeta,
|
|
1421
1512
|
) {
|
|
1422
|
-
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
1423
1513
|
const {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
} = await import("@modelcontextprotocol/
|
|
1514
|
+
ResourceNotFoundError,
|
|
1515
|
+
Server,
|
|
1516
|
+
acceptedContent,
|
|
1517
|
+
createRequestStateCodec,
|
|
1518
|
+
inputRequired,
|
|
1519
|
+
} = await import("@modelcontextprotocol/server");
|
|
1430
1520
|
|
|
1431
1521
|
// Resolve the effective caller identity. JWT / header-derived identity
|
|
1432
1522
|
// (passed by `mountMCP` via `verifyAuth`) wins. When the caller passed no
|
|
@@ -1527,6 +1617,39 @@ export async function createMCPServerForRequest(
|
|
|
1527
1617
|
if (fullCatalogRequested) {
|
|
1528
1618
|
warnFullCatalogServed(Object.keys(advertisedActions).length);
|
|
1529
1619
|
}
|
|
1620
|
+
// Resolve orgId once per request (DB lookup) so approval state and every
|
|
1621
|
+
// downstream action see the same authenticated principal.
|
|
1622
|
+
const orgIdPromise = resolveMcpIdentityOrgId(effectiveIdentity);
|
|
1623
|
+
const hasApprovalActions = Object.values(actions).some(
|
|
1624
|
+
(entry) => entry.needsApproval !== undefined,
|
|
1625
|
+
);
|
|
1626
|
+
const approvalOrgId = hasApprovalActions ? await orgIdPromise : undefined;
|
|
1627
|
+
const approvalPrincipal = hasApprovalActions
|
|
1628
|
+
? mcpApprovalPrincipal(effectiveIdentity, approvalOrgId, requestMeta)
|
|
1629
|
+
: undefined;
|
|
1630
|
+
const approvalCallerKey = approvalPrincipal
|
|
1631
|
+
? await sha256Base64Url(approvalPrincipal)
|
|
1632
|
+
: undefined;
|
|
1633
|
+
let approvalCodec: RequestStateCodec<McpActionApprovalState> | undefined;
|
|
1634
|
+
let approvalConfigurationError = false;
|
|
1635
|
+
if (hasApprovalActions && approvalPrincipal) {
|
|
1636
|
+
try {
|
|
1637
|
+
const { getAuthSecret } =
|
|
1638
|
+
await import("../server/better-auth-instance.js");
|
|
1639
|
+
approvalCodec = createRequestStateCodec<McpActionApprovalState>({
|
|
1640
|
+
key: getAuthSecret(),
|
|
1641
|
+
ttlSeconds: MCP_ACTION_APPROVAL_TTL_SECONDS,
|
|
1642
|
+
bind: (ctx) => `${ctx.mcpReq.method}\0${approvalPrincipal}`,
|
|
1643
|
+
});
|
|
1644
|
+
} catch {
|
|
1645
|
+
// Production secret resolution deliberately throws when neither the
|
|
1646
|
+
// explicit Better Auth secret nor the workspace-derived A2A secret is
|
|
1647
|
+
// stable. Keep the MCP server available for reads, but refuse every
|
|
1648
|
+
// approval-gated action rather than minting replayable/ephemeral hosted
|
|
1649
|
+
// authorization state.
|
|
1650
|
+
approvalConfigurationError = true;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1530
1653
|
const supportsMcpApps =
|
|
1531
1654
|
compactMcpAppCatalog ||
|
|
1532
1655
|
Object.values(advertisedActions).some((entry) =>
|
|
@@ -1546,15 +1669,28 @@ export async function createMCPServerForRequest(
|
|
|
1546
1669
|
}
|
|
1547
1670
|
: {}),
|
|
1548
1671
|
},
|
|
1672
|
+
// Every catalog and resource is identity-scoped and can change at runtime
|
|
1673
|
+
// through app configuration/HMR. Explicitly mark modern cacheable results
|
|
1674
|
+
// private and immediately stale; the 2026 codec adds the required
|
|
1675
|
+
// ttlMs/cacheScope fields while 2025 responses remain byte-compatible.
|
|
1676
|
+
cacheHints: {
|
|
1677
|
+
"server/discover": { ttlMs: 0, cacheScope: "private" },
|
|
1678
|
+
"tools/list": { ttlMs: 0, cacheScope: "private" },
|
|
1679
|
+
"resources/list": { ttlMs: 0, cacheScope: "private" },
|
|
1680
|
+
"resources/templates/list": { ttlMs: 0, cacheScope: "private" },
|
|
1681
|
+
"resources/read": { ttlMs: 0, cacheScope: "private" },
|
|
1682
|
+
},
|
|
1683
|
+
...(approvalCodec
|
|
1684
|
+
? {
|
|
1685
|
+
inputRequired: {
|
|
1686
|
+
maxRounds: 2,
|
|
1687
|
+
roundTimeoutMs: 10 * 60_000,
|
|
1688
|
+
},
|
|
1689
|
+
requestState: { verify: approvalCodec.verify },
|
|
1690
|
+
}
|
|
1691
|
+
: {}),
|
|
1549
1692
|
});
|
|
1550
1693
|
|
|
1551
|
-
// Resolve orgId once per request (DB lookup) so subsequent wraps are
|
|
1552
|
-
// synchronous. The caller identity may be undefined for true dev-open —
|
|
1553
|
-
// in that case we run with no userEmail/orgId, which makes downstream
|
|
1554
|
-
// tools that require per-user scope return empty results rather than
|
|
1555
|
-
// cross-tenant data (the safe default).
|
|
1556
|
-
const orgIdPromise = resolveMcpIdentityOrgId(effectiveIdentity);
|
|
1557
|
-
|
|
1558
1694
|
/**
|
|
1559
1695
|
* Wrap a callback in
|
|
1560
1696
|
* `runWithRequestContext({ userEmail, orgId, requestOrigin }, fn)`.
|
|
@@ -1578,63 +1714,178 @@ export async function createMCPServerForRequest(
|
|
|
1578
1714
|
) as Promise<T>;
|
|
1579
1715
|
}
|
|
1580
1716
|
|
|
1717
|
+
async function requireMcpActionApproval(
|
|
1718
|
+
entry: ActionEntry,
|
|
1719
|
+
name: string,
|
|
1720
|
+
args: Record<string, unknown>,
|
|
1721
|
+
ctx: ServerContext,
|
|
1722
|
+
): Promise<CallToolResult | InputRequiredResult | undefined> {
|
|
1723
|
+
const verifiedState =
|
|
1724
|
+
ctx.mcpReq.requestState<McpActionApprovalState>() ?? undefined;
|
|
1725
|
+
const argumentsHash = await sha256Base64Url(canonicalJson(args));
|
|
1726
|
+
|
|
1727
|
+
if (verifiedState !== undefined) {
|
|
1728
|
+
if (
|
|
1729
|
+
verifiedState.version !== 1 ||
|
|
1730
|
+
typeof verifiedState.nonce !== "string" ||
|
|
1731
|
+
verifiedState.actionName !== name ||
|
|
1732
|
+
verifiedState.argumentsHash !== argumentsHash ||
|
|
1733
|
+
!Number.isFinite(verifiedState.expiresAt) ||
|
|
1734
|
+
verifiedState.expiresAt < Date.now() ||
|
|
1735
|
+
!approvalCallerKey
|
|
1736
|
+
) {
|
|
1737
|
+
return actionApprovalError(
|
|
1738
|
+
`Approval for ${name} is invalid or does not match this exact call.`,
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
const consumed = await consumeMcpApprovalGrant({
|
|
1743
|
+
nonce: verifiedState.nonce,
|
|
1744
|
+
callerKey: approvalCallerKey,
|
|
1745
|
+
actionName: name,
|
|
1746
|
+
argumentsHash,
|
|
1747
|
+
expiresAt: verifiedState.expiresAt,
|
|
1748
|
+
});
|
|
1749
|
+
if (!consumed) {
|
|
1750
|
+
return actionApprovalError(
|
|
1751
|
+
`Approval for ${name} is invalid, expired, or already used.`,
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
const approval = acceptedContent(
|
|
1756
|
+
ctx.mcpReq.inputResponses,
|
|
1757
|
+
MCP_ACTION_APPROVAL_INPUT_KEY,
|
|
1758
|
+
) as Record<string, unknown> | undefined;
|
|
1759
|
+
if (approval?.decision !== "approve") {
|
|
1760
|
+
return actionApprovalError(`${name} was not approved.`);
|
|
1761
|
+
}
|
|
1762
|
+
return undefined;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
if (entry.needsApproval === undefined) return undefined;
|
|
1766
|
+
let mustApprove = false;
|
|
1767
|
+
try {
|
|
1768
|
+
mustApprove =
|
|
1769
|
+
typeof entry.needsApproval === "function"
|
|
1770
|
+
? Boolean(
|
|
1771
|
+
await entry.needsApproval(args, {
|
|
1772
|
+
userEmail: getRequestUserEmail(),
|
|
1773
|
+
orgId: getRequestOrgId() ?? null,
|
|
1774
|
+
caller: "mcp",
|
|
1775
|
+
actionName: name,
|
|
1776
|
+
}),
|
|
1777
|
+
)
|
|
1778
|
+
: entry.needsApproval === true;
|
|
1779
|
+
} catch {
|
|
1780
|
+
mustApprove = true;
|
|
1781
|
+
}
|
|
1782
|
+
if (!mustApprove) return undefined;
|
|
1783
|
+
|
|
1784
|
+
if (approvalConfigurationError || !approvalCodec || !approvalCallerKey) {
|
|
1785
|
+
return actionApprovalError(
|
|
1786
|
+
`${name} requires approval, but secure MCP approval is not configured on this server.`,
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
const now = Date.now();
|
|
1791
|
+
const approvalState: McpActionApprovalState = {
|
|
1792
|
+
version: 1,
|
|
1793
|
+
nonce: globalThis.crypto.randomUUID(),
|
|
1794
|
+
actionName: name,
|
|
1795
|
+
argumentsHash,
|
|
1796
|
+
expiresAt: now + MCP_ACTION_APPROVAL_TTL_SECONDS * 1000,
|
|
1797
|
+
};
|
|
1798
|
+
await createMcpApprovalGrant({
|
|
1799
|
+
...approvalState,
|
|
1800
|
+
callerKey: approvalCallerKey,
|
|
1801
|
+
});
|
|
1802
|
+
const requestState = await approvalCodec.mint(approvalState, ctx);
|
|
1803
|
+
return inputRequired({
|
|
1804
|
+
inputRequests: {
|
|
1805
|
+
[MCP_ACTION_APPROVAL_INPUT_KEY]: inputRequired.elicit({
|
|
1806
|
+
message:
|
|
1807
|
+
`Action "${name}" requires your approval before it can run. ` +
|
|
1808
|
+
"Review the exact tool arguments in your MCP client, then explicitly choose Approve or Deny.",
|
|
1809
|
+
requestedSchema: {
|
|
1810
|
+
type: "object",
|
|
1811
|
+
properties: {
|
|
1812
|
+
decision: {
|
|
1813
|
+
type: "string",
|
|
1814
|
+
title: `Run ${name}?`,
|
|
1815
|
+
description:
|
|
1816
|
+
"Approve runs this exact call once. Deny leaves it unexecuted.",
|
|
1817
|
+
enum: ["approve", "deny"],
|
|
1818
|
+
},
|
|
1819
|
+
},
|
|
1820
|
+
required: ["decision"],
|
|
1821
|
+
},
|
|
1822
|
+
}),
|
|
1823
|
+
},
|
|
1824
|
+
requestState,
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1581
1828
|
// tools/list — return all actions + ask-agent meta-tool. Wrapped in the
|
|
1582
1829
|
// request context so per-user MCP visibility (mcp-client/visibility.ts)
|
|
1583
1830
|
// applies to the listing too.
|
|
1584
|
-
server.setRequestHandler(
|
|
1831
|
+
server.setRequestHandler("tools/list", async () => {
|
|
1585
1832
|
return withCallerContext(async () => {
|
|
1586
|
-
const tools = await Promise.all(
|
|
1587
|
-
Object.entries(advertisedActions)
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
mcpAppResource,
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1833
|
+
const tools: Tool[] = await Promise.all(
|
|
1834
|
+
Object.entries(advertisedActions)
|
|
1835
|
+
.sort(([a], [b]) => compareMcpCatalogValues(a, b))
|
|
1836
|
+
.map(async ([name, entry]) => {
|
|
1837
|
+
const hasLink = typeof entry.link === "function";
|
|
1838
|
+
const mcpAppResource = await resolveMcpAppResourceSafely(
|
|
1839
|
+
config,
|
|
1840
|
+
name,
|
|
1841
|
+
entry,
|
|
1842
|
+
requestMeta,
|
|
1843
|
+
);
|
|
1844
|
+
const rawToolMeta =
|
|
1845
|
+
(entry.tool as any)._meta &&
|
|
1846
|
+
typeof (entry.tool as any)._meta === "object" &&
|
|
1847
|
+
!Array.isArray((entry.tool as any)._meta)
|
|
1848
|
+
? { ...((entry.tool as any)._meta as Record<string, unknown>) }
|
|
1849
|
+
: {};
|
|
1850
|
+
const toolMeta = {
|
|
1851
|
+
...rawToolMeta,
|
|
1852
|
+
// Advertisement path: only tag the tool with its inline-embed
|
|
1853
|
+
// descriptor when the kill switch is on, so disabled embeds
|
|
1854
|
+
// never prompt a host to render/read the `ui://` resource.
|
|
1855
|
+
...(mcpAppResource && requestMeta?.inlineMcpApps
|
|
1856
|
+
? {
|
|
1857
|
+
...openAiToolDescriptorMeta(mcpAppResource),
|
|
1858
|
+
[MCP_APP_RESOURCE_URI_META_KEY]: mcpAppResource.uri,
|
|
1859
|
+
ui: mcpAppToolUiMeta(
|
|
1860
|
+
mcpAppResource,
|
|
1861
|
+
entry.mcpApp?.visibility ??
|
|
1862
|
+
metadataObject(rawToolMeta.ui).visibility,
|
|
1863
|
+
),
|
|
1864
|
+
}
|
|
1865
|
+
: {}),
|
|
1866
|
+
};
|
|
1867
|
+
const baseDescription = entry.tool.description ?? name;
|
|
1868
|
+
const annotations: Record<string, unknown> = {
|
|
1869
|
+
readOnlyHint: entry.readOnly === true,
|
|
1870
|
+
destructiveHint:
|
|
1871
|
+
entry.publicAgent?.isConsequential === true ||
|
|
1872
|
+
entry.needsApproval !== undefined,
|
|
1873
|
+
openWorldHint: false,
|
|
1874
|
+
};
|
|
1875
|
+
if (hasLink) annotations["agent-native/producesOpenLink"] = true;
|
|
1876
|
+
return {
|
|
1877
|
+
name,
|
|
1878
|
+
description: hasLink
|
|
1879
|
+
? `${baseDescription} After calling, surface the returned "Open in … →" link to the user.`
|
|
1880
|
+
: baseDescription,
|
|
1881
|
+
inputSchema: entry.tool.parameters ?? {
|
|
1882
|
+
type: "object" as const,
|
|
1883
|
+
properties: {},
|
|
1884
|
+
},
|
|
1885
|
+
...(Object.keys(toolMeta).length > 0 ? { _meta: toolMeta } : {}),
|
|
1886
|
+
annotations,
|
|
1887
|
+
} as Tool;
|
|
1888
|
+
}),
|
|
1638
1889
|
);
|
|
1639
1890
|
|
|
1640
1891
|
if (
|
|
@@ -1679,6 +1930,7 @@ export async function createMCPServerForRequest(
|
|
|
1679
1930
|
});
|
|
1680
1931
|
}
|
|
1681
1932
|
|
|
1933
|
+
tools.sort((a, b) => compareMcpCatalogValues(a.name, b.name));
|
|
1682
1934
|
return { tools };
|
|
1683
1935
|
});
|
|
1684
1936
|
});
|
|
@@ -1686,52 +1938,208 @@ export async function createMCPServerForRequest(
|
|
|
1686
1938
|
// tools/call — dispatch to action registry or ask-agent. Wrapped in the
|
|
1687
1939
|
// request context so the action's `run(args)` and `askAgent()` execute
|
|
1688
1940
|
// with the verified caller's identity, not the platform default.
|
|
1689
|
-
server.setRequestHandler(
|
|
1690
|
-
|
|
1691
|
-
|
|
1941
|
+
server.setRequestHandler(
|
|
1942
|
+
"tools/call",
|
|
1943
|
+
async (request: any, ctx: ServerContext) => {
|
|
1944
|
+
return withCallerContext(async () => {
|
|
1945
|
+
const { name, arguments: args } = request.params;
|
|
1946
|
+
|
|
1947
|
+
if (name === "ask-agent" && config.askAgent) {
|
|
1948
|
+
if (compactMcpAppCatalog || connectorCatalogActive) {
|
|
1949
|
+
return {
|
|
1950
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1951
|
+
isError: true,
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
if (!hasMcpOAuthScope(effectiveIdentity?.oauthScopes, "mcp:write")) {
|
|
1955
|
+
return {
|
|
1956
|
+
content: [
|
|
1957
|
+
{
|
|
1958
|
+
type: "text",
|
|
1959
|
+
text: "Forbidden: OAuth scope does not allow ask-agent",
|
|
1960
|
+
},
|
|
1961
|
+
],
|
|
1962
|
+
isError: true,
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
const message = args?.message ?? "";
|
|
1966
|
+
try {
|
|
1967
|
+
// Keep the legacy meta-tool compatible for local callers, but use
|
|
1968
|
+
// the same durable A2A submission path as ask_app whenever this is
|
|
1969
|
+
// an HTTP/hosted request. A full agent loop must never be held open
|
|
1970
|
+
// for minutes behind one MCP tools/call request. Always route
|
|
1971
|
+
// through ask_app's own run() — even with no request origin, since
|
|
1972
|
+
// it now bounds that case too via its process-local inline-task
|
|
1973
|
+
// fallback (ask-app-inline-tasks.ts) instead of us awaiting
|
|
1974
|
+
// config.askAgent() here unbounded. This is the shared helper: no
|
|
1975
|
+
// second task map to keep in sync.
|
|
1976
|
+
const hostedAskApp = getBuiltinCrossAppTools(
|
|
1977
|
+
config,
|
|
1978
|
+
requestMeta,
|
|
1979
|
+
).ask_app;
|
|
1980
|
+
const result = await hostedAskApp.run({
|
|
1981
|
+
message,
|
|
1982
|
+
async: isExplicitAsyncAskAgent(args?.async),
|
|
1983
|
+
maxWaitMs: isExplicitAsyncAskAgent(args?.async)
|
|
1984
|
+
? 0
|
|
1985
|
+
: boundedAskAgentWaitMs(args?.maxWaitMs),
|
|
1986
|
+
});
|
|
1987
|
+
return {
|
|
1988
|
+
content: [{ type: "text", text: formatAskAgentResult(result) }],
|
|
1989
|
+
};
|
|
1990
|
+
} catch (err: any) {
|
|
1991
|
+
return {
|
|
1992
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
1993
|
+
isError: true,
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1692
1997
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1998
|
+
// Connector-catalog tier: when active, callableActions === advertisedActions
|
|
1999
|
+
// (the filtered set). Non-listed tools are not callable — mirroring how
|
|
2000
|
+
// compactMcpAppCatalog gates calls on advertisedActions.
|
|
2001
|
+
const callableActions =
|
|
2002
|
+
compactMcpAppCatalog || connectorCatalogActive
|
|
2003
|
+
? advertisedActions
|
|
2004
|
+
: actions;
|
|
2005
|
+
const entry = callableActions[name];
|
|
2006
|
+
if (!entry) {
|
|
1695
2007
|
return {
|
|
1696
2008
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1697
2009
|
isError: true,
|
|
1698
2010
|
};
|
|
1699
2011
|
}
|
|
1700
|
-
if (
|
|
2012
|
+
if (
|
|
2013
|
+
!isActionVisibleForOAuthScope(entry, effectiveIdentity?.oauthScopes)
|
|
2014
|
+
) {
|
|
1701
2015
|
return {
|
|
1702
2016
|
content: [
|
|
1703
2017
|
{
|
|
1704
2018
|
type: "text",
|
|
1705
|
-
text:
|
|
2019
|
+
text: `Forbidden: OAuth scope does not allow tool ${name}`,
|
|
1706
2020
|
},
|
|
1707
2021
|
],
|
|
1708
2022
|
isError: true,
|
|
1709
2023
|
};
|
|
1710
2024
|
}
|
|
1711
|
-
|
|
2025
|
+
|
|
1712
2026
|
try {
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
//
|
|
1722
|
-
|
|
1723
|
-
|
|
2027
|
+
const approvalResult = await requireMcpActionApproval(
|
|
2028
|
+
entry,
|
|
2029
|
+
name,
|
|
2030
|
+
(args as Record<string, unknown>) ?? {},
|
|
2031
|
+
ctx,
|
|
2032
|
+
);
|
|
2033
|
+
if (approvalResult !== undefined) return approvalResult;
|
|
2034
|
+
|
|
2035
|
+
// We're inside `withCallerContext`, so the request-context getters
|
|
2036
|
+
// resolve the verified MCP caller's identity (do NOT inject a dev
|
|
2037
|
+
// fallback). Tag the call as an external-agent MCP dispatch.
|
|
2038
|
+
const result = await entry.run(
|
|
2039
|
+
(args as Record<string, string>) ?? {},
|
|
2040
|
+
{
|
|
2041
|
+
userEmail: getRequestUserEmail(),
|
|
2042
|
+
orgId: getRequestOrgId() ?? null,
|
|
2043
|
+
caller: "mcp",
|
|
2044
|
+
actionName: name,
|
|
2045
|
+
},
|
|
2046
|
+
);
|
|
2047
|
+
const mcpResult = isMcpActionResult(result) ? result : null;
|
|
2048
|
+
const rawResult = mcpResult ? mcpResult.raw : result;
|
|
2049
|
+
const resultForClient = mcpResult ? mcpResult.text : result;
|
|
2050
|
+
const mcpResultIsError =
|
|
2051
|
+
!!mcpResult &&
|
|
2052
|
+
!!mcpResult.raw &&
|
|
2053
|
+
typeof mcpResult.raw === "object" &&
|
|
2054
|
+
(mcpResult.raw as Record<string, unknown>).isError === true;
|
|
2055
|
+
// Render path: only treat the result as an inline embed when the kill
|
|
2056
|
+
// switch is on. When off, `mcpAppResource` is null so every embed
|
|
2057
|
+
// branch below degrades to the plain deep-link artifacts the tool would
|
|
2058
|
+
// otherwise return — no `openai/outputTemplate`, no minted embed-start,
|
|
2059
|
+
// no embed structuredContent — so the host shows a link, not an iframe.
|
|
2060
|
+
const mcpAppResourceCandidate = requestMeta?.inlineMcpApps
|
|
2061
|
+
? await resolveMcpAppResourceSafely(
|
|
2062
|
+
config,
|
|
2063
|
+
name,
|
|
2064
|
+
entry,
|
|
2065
|
+
requestMeta,
|
|
2066
|
+
)
|
|
2067
|
+
: null;
|
|
2068
|
+
const rawResultForClient = mcpAppResourceCandidate
|
|
2069
|
+
? await withServerMintedMcpAppEmbedStart(rawResult, requestMeta)
|
|
2070
|
+
: rawResult;
|
|
2071
|
+
// Only attach the embed widget for a non-error result that has content.
|
|
2072
|
+
const embedHasContent = mcpResultHasContent(rawResultForClient);
|
|
2073
|
+
const mcpAppResource =
|
|
2074
|
+
mcpAppResourceCandidate && !mcpResultIsError && embedHasContent
|
|
2075
|
+
? mcpAppResourceCandidate
|
|
2076
|
+
: null;
|
|
2077
|
+
// `openai/outputTemplate` is declared at the tool level, so the host
|
|
2078
|
+
// renders a widget for every call regardless of result _meta. The only
|
|
2079
|
+
// per-result signal it honors is `isError` (shows error text, no widget),
|
|
2080
|
+
// so treat an embed tool that produced nothing as an error.
|
|
2081
|
+
const embedProducedNothing =
|
|
2082
|
+
!!mcpAppResourceCandidate && !mcpResultIsError && !embedHasContent;
|
|
2083
|
+
const { block, _meta } = buildLinkArtifacts(
|
|
2084
|
+
entry,
|
|
2085
|
+
(args as Record<string, any>) ?? {},
|
|
2086
|
+
rawResultForClient,
|
|
1724
2087
|
requestMeta,
|
|
1725
|
-
)
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2088
|
+
);
|
|
2089
|
+
const responseMeta: Record<string, unknown> = {
|
|
2090
|
+
...(_meta ?? {}),
|
|
2091
|
+
...(mcpAppResource
|
|
2092
|
+
? mcpAppEmbedOpenLinkMeta(
|
|
2093
|
+
rawResultForClient,
|
|
2094
|
+
mcpAppResource,
|
|
2095
|
+
requestMeta,
|
|
2096
|
+
)
|
|
2097
|
+
: {}),
|
|
2098
|
+
...(mcpAppResource ? openAiToolResultMeta(mcpAppResource) : {}),
|
|
2099
|
+
};
|
|
2100
|
+
const toolUiMeta = metadataObject((entry.tool as any)._meta?.ui);
|
|
2101
|
+
const toolVisibility = toolUiMeta.visibility;
|
|
2102
|
+
const isAppOnlyVisibility =
|
|
2103
|
+
Array.isArray(toolVisibility) &&
|
|
2104
|
+
toolVisibility.length > 0 &&
|
|
2105
|
+
toolVisibility.every((v) => v === "app");
|
|
2106
|
+
const readOnlyStructuredResult =
|
|
2107
|
+
entry.readOnly === true &&
|
|
2108
|
+
rawResultForClient &&
|
|
2109
|
+
typeof rawResultForClient === "object"
|
|
2110
|
+
? Array.isArray(rawResultForClient)
|
|
2111
|
+
? { items: rawResultForClient }
|
|
2112
|
+
: rawResultForClient
|
|
2113
|
+
: undefined;
|
|
2114
|
+
const structuredContent = mcpAppResource
|
|
2115
|
+
? mcpAppStructuredContent(rawResultForClient, responseMeta)
|
|
2116
|
+
: isAppOnlyVisibility &&
|
|
2117
|
+
rawResult &&
|
|
2118
|
+
typeof rawResult === "object" &&
|
|
2119
|
+
!Array.isArray(rawResult)
|
|
2120
|
+
? (rawResult as Record<string, unknown>)
|
|
2121
|
+
: readOnlyStructuredResult
|
|
2122
|
+
? mcpAppStructuredContent(
|
|
2123
|
+
readOnlyStructuredResult,
|
|
2124
|
+
responseMeta,
|
|
2125
|
+
)
|
|
2126
|
+
: undefined;
|
|
2127
|
+
const text = mcpAppResource
|
|
2128
|
+
? conciseMcpAppToolText(name, resultForClient, structuredContent!)
|
|
2129
|
+
: conciseToolResultText(name, resultForClient, {
|
|
2130
|
+
preserveObjectResult: entry.readOnly === true,
|
|
2131
|
+
});
|
|
2132
|
+
const content: any[] = [{ type: "text", text }];
|
|
2133
|
+
if (block) content.push(block);
|
|
1733
2134
|
return {
|
|
1734
|
-
content
|
|
2135
|
+
content,
|
|
2136
|
+
...(mcpResultIsError || embedProducedNothing
|
|
2137
|
+
? { isError: true }
|
|
2138
|
+
: {}),
|
|
2139
|
+
...(structuredContent ? { structuredContent } : {}),
|
|
2140
|
+
...(Object.keys(responseMeta).length > 0
|
|
2141
|
+
? { _meta: responseMeta }
|
|
2142
|
+
: {}),
|
|
1735
2143
|
};
|
|
1736
2144
|
} catch (err: any) {
|
|
1737
2145
|
return {
|
|
@@ -1739,146 +2147,12 @@ export async function createMCPServerForRequest(
|
|
|
1739
2147
|
isError: true,
|
|
1740
2148
|
};
|
|
1741
2149
|
}
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
// (the filtered set). Non-listed tools are not callable — mirroring how
|
|
1746
|
-
// compactMcpAppCatalog gates calls on advertisedActions.
|
|
1747
|
-
const callableActions =
|
|
1748
|
-
compactMcpAppCatalog || connectorCatalogActive
|
|
1749
|
-
? advertisedActions
|
|
1750
|
-
: actions;
|
|
1751
|
-
const entry = callableActions[name];
|
|
1752
|
-
if (!entry) {
|
|
1753
|
-
return {
|
|
1754
|
-
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1755
|
-
isError: true,
|
|
1756
|
-
};
|
|
1757
|
-
}
|
|
1758
|
-
if (
|
|
1759
|
-
!isActionVisibleForOAuthScope(entry, effectiveIdentity?.oauthScopes)
|
|
1760
|
-
) {
|
|
1761
|
-
return {
|
|
1762
|
-
content: [
|
|
1763
|
-
{
|
|
1764
|
-
type: "text",
|
|
1765
|
-
text: `Forbidden: OAuth scope does not allow tool ${name}`,
|
|
1766
|
-
},
|
|
1767
|
-
],
|
|
1768
|
-
isError: true,
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
try {
|
|
1773
|
-
// We're inside `withCallerContext`, so the request-context getters
|
|
1774
|
-
// resolve the verified MCP caller's identity (do NOT inject a dev
|
|
1775
|
-
// fallback). Tag the call as an external-agent MCP dispatch.
|
|
1776
|
-
const result = await entry.run((args as Record<string, string>) ?? {}, {
|
|
1777
|
-
userEmail: getRequestUserEmail(),
|
|
1778
|
-
orgId: getRequestOrgId() ?? null,
|
|
1779
|
-
caller: "mcp",
|
|
1780
|
-
actionName: name,
|
|
1781
|
-
});
|
|
1782
|
-
const mcpResult = isMcpActionResult(result) ? result : null;
|
|
1783
|
-
const rawResult = mcpResult ? mcpResult.raw : result;
|
|
1784
|
-
const resultForClient = mcpResult ? mcpResult.text : result;
|
|
1785
|
-
const mcpResultIsError =
|
|
1786
|
-
!!mcpResult &&
|
|
1787
|
-
!!mcpResult.raw &&
|
|
1788
|
-
typeof mcpResult.raw === "object" &&
|
|
1789
|
-
(mcpResult.raw as Record<string, unknown>).isError === true;
|
|
1790
|
-
// Render path: only treat the result as an inline embed when the kill
|
|
1791
|
-
// switch is on. When off, `mcpAppResource` is null so every embed
|
|
1792
|
-
// branch below degrades to the plain deep-link artifacts the tool would
|
|
1793
|
-
// otherwise return — no `openai/outputTemplate`, no minted embed-start,
|
|
1794
|
-
// no embed structuredContent — so the host shows a link, not an iframe.
|
|
1795
|
-
const mcpAppResourceCandidate = requestMeta?.inlineMcpApps
|
|
1796
|
-
? await resolveMcpAppResourceSafely(config, name, entry, requestMeta)
|
|
1797
|
-
: null;
|
|
1798
|
-
const rawResultForClient = mcpAppResourceCandidate
|
|
1799
|
-
? await withServerMintedMcpAppEmbedStart(rawResult, requestMeta)
|
|
1800
|
-
: rawResult;
|
|
1801
|
-
// Only attach the embed widget for a non-error result that has content.
|
|
1802
|
-
const embedHasContent = mcpResultHasContent(rawResultForClient);
|
|
1803
|
-
const mcpAppResource =
|
|
1804
|
-
mcpAppResourceCandidate && !mcpResultIsError && embedHasContent
|
|
1805
|
-
? mcpAppResourceCandidate
|
|
1806
|
-
: null;
|
|
1807
|
-
// `openai/outputTemplate` is declared at the tool level, so the host
|
|
1808
|
-
// renders a widget for every call regardless of result _meta. The only
|
|
1809
|
-
// per-result signal it honors is `isError` (shows error text, no widget),
|
|
1810
|
-
// so treat an embed tool that produced nothing as an error.
|
|
1811
|
-
const embedProducedNothing =
|
|
1812
|
-
!!mcpAppResourceCandidate && !mcpResultIsError && !embedHasContent;
|
|
1813
|
-
const { block, _meta } = buildLinkArtifacts(
|
|
1814
|
-
entry,
|
|
1815
|
-
(args as Record<string, any>) ?? {},
|
|
1816
|
-
rawResultForClient,
|
|
1817
|
-
requestMeta,
|
|
1818
|
-
);
|
|
1819
|
-
const responseMeta: Record<string, unknown> = {
|
|
1820
|
-
...(_meta ?? {}),
|
|
1821
|
-
...(mcpAppResource
|
|
1822
|
-
? mcpAppEmbedOpenLinkMeta(
|
|
1823
|
-
rawResultForClient,
|
|
1824
|
-
mcpAppResource,
|
|
1825
|
-
requestMeta,
|
|
1826
|
-
)
|
|
1827
|
-
: {}),
|
|
1828
|
-
...(mcpAppResource ? openAiToolResultMeta(mcpAppResource) : {}),
|
|
1829
|
-
};
|
|
1830
|
-
const toolUiMeta = metadataObject((entry.tool as any)._meta?.ui);
|
|
1831
|
-
const toolVisibility = toolUiMeta.visibility;
|
|
1832
|
-
const isAppOnlyVisibility =
|
|
1833
|
-
Array.isArray(toolVisibility) &&
|
|
1834
|
-
toolVisibility.length > 0 &&
|
|
1835
|
-
toolVisibility.every((v) => v === "app");
|
|
1836
|
-
const readOnlyStructuredResult =
|
|
1837
|
-
entry.readOnly === true &&
|
|
1838
|
-
rawResultForClient &&
|
|
1839
|
-
typeof rawResultForClient === "object"
|
|
1840
|
-
? Array.isArray(rawResultForClient)
|
|
1841
|
-
? { items: rawResultForClient }
|
|
1842
|
-
: rawResultForClient
|
|
1843
|
-
: undefined;
|
|
1844
|
-
const structuredContent = mcpAppResource
|
|
1845
|
-
? mcpAppStructuredContent(rawResultForClient, responseMeta)
|
|
1846
|
-
: isAppOnlyVisibility &&
|
|
1847
|
-
rawResult &&
|
|
1848
|
-
typeof rawResult === "object" &&
|
|
1849
|
-
!Array.isArray(rawResult)
|
|
1850
|
-
? (rawResult as Record<string, unknown>)
|
|
1851
|
-
: readOnlyStructuredResult
|
|
1852
|
-
? mcpAppStructuredContent(readOnlyStructuredResult, responseMeta)
|
|
1853
|
-
: undefined;
|
|
1854
|
-
const text = mcpAppResource
|
|
1855
|
-
? conciseMcpAppToolText(name, resultForClient, structuredContent!)
|
|
1856
|
-
: conciseToolResultText(name, resultForClient, {
|
|
1857
|
-
preserveObjectResult: entry.readOnly === true,
|
|
1858
|
-
});
|
|
1859
|
-
const content: any[] = [{ type: "text", text }];
|
|
1860
|
-
if (block) content.push(block);
|
|
1861
|
-
return {
|
|
1862
|
-
content,
|
|
1863
|
-
...(mcpResultIsError || embedProducedNothing
|
|
1864
|
-
? { isError: true }
|
|
1865
|
-
: {}),
|
|
1866
|
-
...(structuredContent ? { structuredContent } : {}),
|
|
1867
|
-
...(Object.keys(responseMeta).length > 0
|
|
1868
|
-
? { _meta: responseMeta }
|
|
1869
|
-
: {}),
|
|
1870
|
-
};
|
|
1871
|
-
} catch (err: any) {
|
|
1872
|
-
return {
|
|
1873
|
-
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
1874
|
-
isError: true,
|
|
1875
|
-
};
|
|
1876
|
-
}
|
|
1877
|
-
});
|
|
1878
|
-
});
|
|
2150
|
+
});
|
|
2151
|
+
},
|
|
2152
|
+
);
|
|
1879
2153
|
|
|
1880
2154
|
if (supportsMcpApps) {
|
|
1881
|
-
server.setRequestHandler(
|
|
2155
|
+
server.setRequestHandler("resources/list", async () => {
|
|
1882
2156
|
return withCallerContext(async () => {
|
|
1883
2157
|
const mcpAppResources = await getMcpAppResources(
|
|
1884
2158
|
config,
|
|
@@ -1886,21 +2160,23 @@ export async function createMCPServerForRequest(
|
|
|
1886
2160
|
requestMeta,
|
|
1887
2161
|
);
|
|
1888
2162
|
return {
|
|
1889
|
-
resources: mcpAppResources
|
|
1890
|
-
uri
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
? {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2163
|
+
resources: mcpAppResources
|
|
2164
|
+
.sort((a, b) => compareMcpCatalogValues(a.uri, b.uri))
|
|
2165
|
+
.map((resource) => ({
|
|
2166
|
+
uri: resource.uri,
|
|
2167
|
+
name: resource.name,
|
|
2168
|
+
...(resource.title ? { title: resource.title } : {}),
|
|
2169
|
+
...(resource.description
|
|
2170
|
+
? { description: resource.description }
|
|
2171
|
+
: {}),
|
|
2172
|
+
mimeType: resource.mimeType,
|
|
2173
|
+
...(resource._meta ? { _meta: resource._meta } : {}),
|
|
2174
|
+
})),
|
|
1899
2175
|
};
|
|
1900
2176
|
});
|
|
1901
2177
|
});
|
|
1902
2178
|
|
|
1903
|
-
server.setRequestHandler(
|
|
2179
|
+
server.setRequestHandler("resources/templates/list", async () => {
|
|
1904
2180
|
return withCallerContext(async () => {
|
|
1905
2181
|
const mcpAppResources = await getMcpAppResources(
|
|
1906
2182
|
config,
|
|
@@ -1908,71 +2184,71 @@ export async function createMCPServerForRequest(
|
|
|
1908
2184
|
requestMeta,
|
|
1909
2185
|
);
|
|
1910
2186
|
return {
|
|
1911
|
-
resourceTemplates: mcpAppResources
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
? {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2187
|
+
resourceTemplates: mcpAppResources
|
|
2188
|
+
.sort((a, b) => compareMcpCatalogValues(a.uri, b.uri))
|
|
2189
|
+
.map((resource) => ({
|
|
2190
|
+
uriTemplate: resource.uri,
|
|
2191
|
+
name: resource.name,
|
|
2192
|
+
...(resource.title ? { title: resource.title } : {}),
|
|
2193
|
+
...(resource.description
|
|
2194
|
+
? { description: resource.description }
|
|
2195
|
+
: {}),
|
|
2196
|
+
mimeType: resource.mimeType,
|
|
2197
|
+
...(resource._meta ? { _meta: resource._meta } : {}),
|
|
2198
|
+
})),
|
|
1921
2199
|
};
|
|
1922
2200
|
});
|
|
1923
2201
|
});
|
|
1924
2202
|
|
|
1925
|
-
server.setRequestHandler(
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
if (!resourceUri || !matchesMcpAppResourceUri(resourceUri, uri)) {
|
|
1937
|
-
continue;
|
|
1938
|
-
}
|
|
1939
|
-
const resource = await resolveMcpAppResourceSafely(
|
|
1940
|
-
config,
|
|
1941
|
-
name,
|
|
1942
|
-
entry,
|
|
1943
|
-
requestMeta,
|
|
1944
|
-
);
|
|
1945
|
-
if (resource) {
|
|
1946
|
-
found = { actionName: name, resource };
|
|
1947
|
-
break;
|
|
1948
|
-
}
|
|
1949
|
-
// resolveMcpAppResourceSafely returned null (e.g. an async resolver
|
|
1950
|
-
// threw) — keep scanning the remaining candidates rather than
|
|
1951
|
-
// aborting and reporting the resource as missing.
|
|
2203
|
+
server.setRequestHandler("resources/read", async (request: any) => {
|
|
2204
|
+
return withCallerContext(async () => {
|
|
2205
|
+
const uri = request.params?.uri;
|
|
2206
|
+
let found: {
|
|
2207
|
+
actionName: string;
|
|
2208
|
+
resource: ResolvedMcpAppResource;
|
|
2209
|
+
} | null = null;
|
|
2210
|
+
for (const [name, entry] of Object.entries(advertisedActions)) {
|
|
2211
|
+
const resourceUri = getMcpAppResourceUri(config, name, entry);
|
|
2212
|
+
if (!resourceUri || !matchesMcpAppResourceUri(resourceUri, uri)) {
|
|
2213
|
+
continue;
|
|
1952
2214
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
2215
|
+
const resource = await resolveMcpAppResourceSafely(
|
|
2216
|
+
config,
|
|
2217
|
+
name,
|
|
2218
|
+
entry,
|
|
2219
|
+
requestMeta,
|
|
2220
|
+
);
|
|
2221
|
+
if (resource) {
|
|
2222
|
+
found = { actionName: name, resource };
|
|
2223
|
+
break;
|
|
1955
2224
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2225
|
+
// resolveMcpAppResourceSafely returned null (e.g. an async resolver
|
|
2226
|
+
// threw) — keep scanning the remaining candidates rather than
|
|
2227
|
+
// aborting and reporting the resource as missing.
|
|
2228
|
+
}
|
|
2229
|
+
if (!found) {
|
|
2230
|
+
throw new ResourceNotFoundError(
|
|
2231
|
+
String(uri ?? ""),
|
|
2232
|
+
`MCP App resource not found: ${uri}`,
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
return {
|
|
2236
|
+
contents: [
|
|
2237
|
+
{
|
|
2238
|
+
uri,
|
|
2239
|
+
mimeType: found.resource.mimeType,
|
|
2240
|
+
text: renderMcpAppHtml(
|
|
2241
|
+
found.resource,
|
|
2242
|
+
found.actionName,
|
|
2243
|
+
config,
|
|
2244
|
+
requestMeta,
|
|
2245
|
+
),
|
|
2246
|
+
...(found.resource._meta ? { _meta: found.resource._meta } : {}),
|
|
2247
|
+
},
|
|
2248
|
+
],
|
|
2249
|
+
};
|
|
2250
|
+
});
|
|
2251
|
+
});
|
|
1976
2252
|
}
|
|
1977
2253
|
|
|
1978
2254
|
return server;
|