@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.
Files changed (350) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +91 -0
  3. package/corpus/core/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
  4. package/corpus/core/package.json +6 -3
  5. package/corpus/core/src/a2a/client.ts +18 -4
  6. package/corpus/core/src/a2a/server.ts +19 -3
  7. package/corpus/core/src/a2a/types.ts +7 -0
  8. package/corpus/core/src/action.ts +43 -16
  9. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -10
  10. package/corpus/core/src/agent/production-agent.ts +41 -3
  11. package/corpus/core/src/agent/thread-data-builder.ts +5 -0
  12. package/corpus/core/src/chat-threads/migrations.ts +53 -0
  13. package/corpus/core/src/cli/create.ts +4 -0
  14. package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
  15. package/corpus/core/src/cli/skills.ts +16 -0
  16. package/corpus/core/src/client/AgentPanel.tsx +9 -5
  17. package/corpus/core/src/client/AssistantChat.tsx +138 -46
  18. package/corpus/core/src/client/MultiTabAssistantChat.tsx +16 -10
  19. package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
  20. package/corpus/core/src/client/analytics.ts +159 -102
  21. package/corpus/core/src/client/chat/legacy-chart-shorthand.tsx +479 -0
  22. package/corpus/core/src/client/chat/markdown-renderer.tsx +23 -3
  23. package/corpus/core/src/client/chat/message-components.tsx +12 -5
  24. package/corpus/core/src/client/chat/runtime.ts +2 -1
  25. package/corpus/core/src/client/chat/tool-call-display.tsx +25 -2
  26. package/corpus/core/src/client/chat-model-groups.ts +3 -0
  27. package/corpus/core/src/client/client-status-requests.ts +161 -0
  28. package/corpus/core/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
  29. package/corpus/core/src/client/sse-event-processor.ts +15 -3
  30. package/corpus/core/src/client/use-agent-engine-configured.ts +54 -50
  31. package/corpus/core/src/client/use-chat-models.ts +16 -11
  32. package/corpus/core/src/client/use-chat-threads.ts +32 -9
  33. package/corpus/core/src/client/use-session.ts +4 -5
  34. package/corpus/core/src/db/client.ts +83 -8
  35. package/corpus/core/src/db/ensure-additive-columns.ts +31 -20
  36. package/corpus/core/src/db/migrations.ts +22 -2
  37. package/corpus/core/src/extensions/actions.ts +1 -1
  38. package/corpus/core/src/mcp/approval-store.ts +99 -0
  39. package/corpus/core/src/mcp/build-server.ts +579 -303
  40. package/corpus/core/src/mcp/embed-app.ts +1 -1
  41. package/corpus/core/src/mcp/oauth-client-metadata.ts +380 -0
  42. package/corpus/core/src/mcp/oauth-route.ts +57 -52
  43. package/corpus/core/src/mcp/screen-memory-stdio.ts +400 -378
  44. package/corpus/core/src/mcp/server.ts +29 -145
  45. package/corpus/core/src/mcp/stdio.ts +72 -80
  46. package/corpus/core/src/mcp-client/manager.ts +6 -10
  47. package/corpus/core/src/mcp-client/oauth-client.ts +127 -36
  48. package/corpus/core/src/mcp-client/oauth-routes.ts +16 -6
  49. package/corpus/core/src/mcp-client/routes.ts +6 -5
  50. package/corpus/core/src/server/agent-capabilities.ts +20 -0
  51. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +27 -0
  52. package/corpus/core/src/server/agent-chat-plugin.ts +18 -0
  53. package/corpus/core/src/server/transcribe-voice.ts +14 -5
  54. package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +13 -2
  55. package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
  56. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
  57. package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
  58. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +12 -0
  59. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +13 -2
  60. package/corpus/templates/analytics/actions/generate-chart.ts +2 -2
  61. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -10
  62. package/corpus/templates/analytics/app/global.css +0 -31
  63. package/corpus/templates/analytics/app/lib/sql-query.ts +46 -20
  64. package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboard-loading-states-now-use-a-simpler-standar.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-07-28-analytics-dashboards-now-load-first-party-charts-reliably-in.md +6 -0
  66. package/corpus/templates/analytics/server/lib/first-party-analytics-cache.ts +42 -16
  67. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +4 -2
  68. package/corpus/templates/analytics/shared/sql-query-limits.ts +1 -0
  69. package/corpus/templates/assets/.agents/skills/security/SKILL.md +13 -2
  70. package/corpus/templates/brain/.agents/skills/security/SKILL.md +13 -2
  71. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +13 -2
  72. package/corpus/templates/chat/.agents/skills/security/SKILL.md +13 -2
  73. package/corpus/templates/clips/.agents/skills/security/SKILL.md +13 -2
  74. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -6
  75. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +6 -2
  76. package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -1
  77. package/corpus/templates/clips/changelog/2026-07-28-copied-share-links-no-longer-leave-an-unnecessary-confirmati.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-07-28-playback-skip-controls-no-longer-show-overlapping-numbers.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-28-public-clips-share-buttons-now-use-a-clean-text-only-label.md +6 -0
  80. package/corpus/templates/clips/desktop/src/app.tsx +14 -17
  81. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +3 -1
  82. package/corpus/templates/clips/desktop/src/styles.css +13 -10
  83. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +2 -2
  84. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +36 -0
  85. package/corpus/templates/content/.agents/skills/security/SKILL.md +13 -2
  86. package/corpus/templates/content/actions/_builder-cms-read-client.ts +155 -34
  87. package/corpus/templates/content/actions/_builder-docs-client.ts +151 -30
  88. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +5 -2
  89. package/corpus/templates/content/app/root.tsx +35 -14
  90. package/corpus/templates/content/changelog/2026-07-28-builder-backed-files-media-fields-now-accept-direct-file-url.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-28-builder-content-reads-now-negotiate-the-newest-mcp-protocol-.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-28-public-content-pages-load-faster-before-the-optional-ask-sid.md +6 -0
  93. package/corpus/templates/design/.agents/skills/security/SKILL.md +13 -2
  94. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +36 -16
  95. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +42 -3
  96. package/corpus/templates/design/actions/open-visual-edit.ts +2 -1
  97. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
  98. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -15
  99. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +314 -0
  100. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +88 -3
  101. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +1 -1
  102. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +7 -6
  103. package/corpus/templates/design/app/i18n-data.ts +56 -11
  104. package/corpus/templates/design/app/i18n-responsive-interact.ts +183 -0
  105. package/corpus/templates/design/app/lib/agent-chat.ts +60 -0
  106. package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +89 -0
  107. package/corpus/templates/design/changelog/2026-07-28-design-edits-can-be-applied-through-the-host-coding-agent-fr.md +6 -0
  108. package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +6 -0
  109. package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +6 -0
  110. package/corpus/templates/design/changelog/2026-07-28-show-or-hide-design-editing-chrome-with-cmd-on-apple-devices.md +2 -2
  111. package/corpus/templates/design/changelog/2026-07-28-visual-edit-now-always-shows-the-live-running-app-in-the-can.md +6 -0
  112. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +13 -2
  113. package/corpus/templates/forms/.agents/skills/security/SKILL.md +13 -2
  114. package/corpus/templates/macros/.agents/skills/security/SKILL.md +13 -2
  115. package/corpus/templates/mail/.agents/skills/security/SKILL.md +13 -2
  116. package/corpus/templates/plan/.agents/skills/security/SKILL.md +13 -2
  117. package/corpus/templates/slides/.agents/skills/security/SKILL.md +13 -2
  118. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +13 -2
  119. package/corpus/toolkit/CHANGELOG.md +13 -0
  120. package/corpus/toolkit/package.json +1 -1
  121. package/corpus/toolkit/src/chat-history.css +1 -2
  122. package/corpus/toolkit/src/composer/TiptapComposer.tsx +45 -0
  123. package/dist/a2a/client.d.ts +7 -0
  124. package/dist/a2a/client.d.ts.map +1 -1
  125. package/dist/a2a/client.js +8 -3
  126. package/dist/a2a/client.js.map +1 -1
  127. package/dist/a2a/server.d.ts.map +1 -1
  128. package/dist/a2a/server.js +18 -3
  129. package/dist/a2a/server.js.map +1 -1
  130. package/dist/a2a/types.d.ts +7 -0
  131. package/dist/a2a/types.d.ts.map +1 -1
  132. package/dist/a2a/types.js.map +1 -1
  133. package/dist/action.d.ts +15 -1
  134. package/dist/action.d.ts.map +1 -1
  135. package/dist/action.js +39 -16
  136. package/dist/action.js.map +1 -1
  137. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  138. package/dist/agent/engine/ai-sdk-engine.js +14 -10
  139. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  140. package/dist/agent/production-agent.d.ts.map +1 -1
  141. package/dist/agent/production-agent.js +35 -7
  142. package/dist/agent/production-agent.js.map +1 -1
  143. package/dist/agent/thread-data-builder.d.ts +2 -0
  144. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  145. package/dist/agent/thread-data-builder.js +3 -0
  146. package/dist/agent/thread-data-builder.js.map +1 -1
  147. package/dist/chat-threads/migrations.d.ts +21 -0
  148. package/dist/chat-threads/migrations.d.ts.map +1 -0
  149. package/dist/chat-threads/migrations.js +45 -0
  150. package/dist/chat-threads/migrations.js.map +1 -0
  151. package/dist/cli/create.d.ts.map +1 -1
  152. package/dist/cli/create.js +4 -0
  153. package/dist/cli/create.js.map +1 -1
  154. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  155. package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
  156. package/dist/cli/skills-content/design-visual-edit-skill.js +36 -16
  157. package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
  158. package/dist/cli/skills.d.ts.map +1 -1
  159. package/dist/cli/skills.js +15 -0
  160. package/dist/cli/skills.js.map +1 -1
  161. package/dist/client/AgentPanel.d.ts.map +1 -1
  162. package/dist/client/AgentPanel.js +6 -5
  163. package/dist/client/AgentPanel.js.map +1 -1
  164. package/dist/client/AssistantChat.d.ts +37 -0
  165. package/dist/client/AssistantChat.d.ts.map +1 -1
  166. package/dist/client/AssistantChat.js +41 -8
  167. package/dist/client/AssistantChat.js.map +1 -1
  168. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  169. package/dist/client/MultiTabAssistantChat.js +11 -10
  170. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  171. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  172. package/dist/client/agent-chat-adapter.js +19 -3
  173. package/dist/client/agent-chat-adapter.js.map +1 -1
  174. package/dist/client/analytics.d.ts.map +1 -1
  175. package/dist/client/analytics.js +150 -101
  176. package/dist/client/analytics.js.map +1 -1
  177. package/dist/client/chat/legacy-chart-shorthand.d.ts +29 -0
  178. package/dist/client/chat/legacy-chart-shorthand.d.ts.map +1 -0
  179. package/dist/client/chat/legacy-chart-shorthand.js +330 -0
  180. package/dist/client/chat/legacy-chart-shorthand.js.map +1 -0
  181. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  182. package/dist/client/chat/markdown-renderer.js +8 -2
  183. package/dist/client/chat/markdown-renderer.js.map +1 -1
  184. package/dist/client/chat/message-components.d.ts +2 -1
  185. package/dist/client/chat/message-components.d.ts.map +1 -1
  186. package/dist/client/chat/message-components.js +11 -4
  187. package/dist/client/chat/message-components.js.map +1 -1
  188. package/dist/client/chat/runtime.d.ts.map +1 -1
  189. package/dist/client/chat/runtime.js +2 -1
  190. package/dist/client/chat/runtime.js.map +1 -1
  191. package/dist/client/chat/tool-call-display.d.ts +4 -1
  192. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  193. package/dist/client/chat/tool-call-display.js +14 -8
  194. package/dist/client/chat/tool-call-display.js.map +1 -1
  195. package/dist/client/chat-model-groups.d.ts.map +1 -1
  196. package/dist/client/chat-model-groups.js +3 -0
  197. package/dist/client/chat-model-groups.js.map +1 -1
  198. package/dist/client/client-status-requests.d.ts +14 -0
  199. package/dist/client/client-status-requests.d.ts.map +1 -0
  200. package/dist/client/client-status-requests.js +126 -0
  201. package/dist/client/client-status-requests.js.map +1 -0
  202. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  203. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
  204. package/dist/client/sse-event-processor.d.ts +7 -0
  205. package/dist/client/sse-event-processor.d.ts.map +1 -1
  206. package/dist/client/sse-event-processor.js +8 -3
  207. package/dist/client/sse-event-processor.js.map +1 -1
  208. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  209. package/dist/client/use-agent-engine-configured.js +29 -43
  210. package/dist/client/use-agent-engine-configured.js.map +1 -1
  211. package/dist/client/use-chat-models.d.ts.map +1 -1
  212. package/dist/client/use-chat-models.js +11 -11
  213. package/dist/client/use-chat-models.js.map +1 -1
  214. package/dist/client/use-chat-threads.d.ts.map +1 -1
  215. package/dist/client/use-chat-threads.js +23 -7
  216. package/dist/client/use-chat-threads.js.map +1 -1
  217. package/dist/client/use-session.d.ts.map +1 -1
  218. package/dist/client/use-session.js +4 -4
  219. package/dist/client/use-session.js.map +1 -1
  220. package/dist/collab/awareness.d.ts +2 -2
  221. package/dist/collab/awareness.d.ts.map +1 -1
  222. package/dist/db/client.d.ts.map +1 -1
  223. package/dist/db/client.js +48 -4
  224. package/dist/db/client.js.map +1 -1
  225. package/dist/db/ensure-additive-columns.d.ts.map +1 -1
  226. package/dist/db/ensure-additive-columns.js +32 -21
  227. package/dist/db/ensure-additive-columns.js.map +1 -1
  228. package/dist/db/migrations.d.ts +9 -0
  229. package/dist/db/migrations.d.ts.map +1 -1
  230. package/dist/db/migrations.js +14 -3
  231. package/dist/db/migrations.js.map +1 -1
  232. package/dist/extensions/actions.js +1 -1
  233. package/dist/extensions/actions.js.map +1 -1
  234. package/dist/mcp/approval-store.d.ts +16 -0
  235. package/dist/mcp/approval-store.d.ts.map +1 -0
  236. package/dist/mcp/approval-store.js +85 -0
  237. package/dist/mcp/approval-store.js.map +1 -0
  238. package/dist/mcp/build-server.d.ts +1 -34
  239. package/dist/mcp/build-server.d.ts.map +1 -1
  240. package/dist/mcp/build-server.js +220 -23
  241. package/dist/mcp/build-server.js.map +1 -1
  242. package/dist/mcp/embed-app.js +1 -1
  243. package/dist/mcp/embed-app.js.map +1 -1
  244. package/dist/mcp/oauth-client-metadata.d.ts +15 -0
  245. package/dist/mcp/oauth-client-metadata.d.ts.map +1 -0
  246. package/dist/mcp/oauth-client-metadata.js +297 -0
  247. package/dist/mcp/oauth-client-metadata.js.map +1 -0
  248. package/dist/mcp/oauth-route.d.ts.map +1 -1
  249. package/dist/mcp/oauth-route.js +39 -53
  250. package/dist/mcp/oauth-route.js.map +1 -1
  251. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  252. package/dist/mcp/screen-memory-stdio.js +304 -296
  253. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  254. package/dist/mcp/server.d.ts +12 -25
  255. package/dist/mcp/server.d.ts.map +1 -1
  256. package/dist/mcp/server.js +24 -133
  257. package/dist/mcp/server.js.map +1 -1
  258. package/dist/mcp/stdio.d.ts.map +1 -1
  259. package/dist/mcp/stdio.js +36 -35
  260. package/dist/mcp/stdio.js.map +1 -1
  261. package/dist/mcp-client/manager.d.ts.map +1 -1
  262. package/dist/mcp-client/manager.js +6 -6
  263. package/dist/mcp-client/manager.js.map +1 -1
  264. package/dist/mcp-client/oauth-client.d.ts +13 -17
  265. package/dist/mcp-client/oauth-client.d.ts.map +1 -1
  266. package/dist/mcp-client/oauth-client.js +74 -16
  267. package/dist/mcp-client/oauth-client.js.map +1 -1
  268. package/dist/mcp-client/oauth-routes.d.ts +2 -2
  269. package/dist/mcp-client/oauth-routes.d.ts.map +1 -1
  270. package/dist/mcp-client/oauth-routes.js +16 -6
  271. package/dist/mcp-client/oauth-routes.js.map +1 -1
  272. package/dist/mcp-client/routes.js +5 -5
  273. package/dist/mcp-client/routes.js.map +1 -1
  274. package/dist/notifications/routes.d.ts +1 -1
  275. package/dist/progress/routes.d.ts +1 -1
  276. package/dist/resources/handlers.d.ts +1 -1
  277. package/dist/secrets/routes.d.ts +9 -9
  278. package/dist/server/agent-capabilities.d.ts.map +1 -1
  279. package/dist/server/agent-capabilities.js +21 -0
  280. package/dist/server/agent-capabilities.js.map +1 -1
  281. package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -0
  282. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  283. package/dist/server/agent-chat/action-filters-a2a.js +16 -0
  284. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  285. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  286. package/dist/server/agent-chat-plugin.js +11 -1
  287. package/dist/server/agent-chat-plugin.js.map +1 -1
  288. package/dist/server/transcribe-voice.js +12 -6
  289. package/dist/server/transcribe-voice.js.map +1 -1
  290. package/dist/templates/chat/.agents/skills/security/SKILL.md +13 -2
  291. package/dist/templates/default/.agents/skills/security/SKILL.md +13 -2
  292. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
  293. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
  294. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
  295. package/docs/content/template-design-collaboration-and-full-apps.mdx +3 -1
  296. package/package.json +7 -4
  297. package/src/a2a/client.ts +18 -4
  298. package/src/a2a/server.ts +19 -3
  299. package/src/a2a/types.ts +7 -0
  300. package/src/action.ts +43 -16
  301. package/src/agent/engine/ai-sdk-engine.ts +14 -10
  302. package/src/agent/production-agent.ts +41 -3
  303. package/src/agent/thread-data-builder.ts +5 -0
  304. package/src/chat-threads/migrations.ts +53 -0
  305. package/src/cli/create.ts +4 -0
  306. package/src/cli/skills-content/design-visual-edit-skill.ts +36 -16
  307. package/src/cli/skills.ts +16 -0
  308. package/src/client/AgentPanel.tsx +9 -5
  309. package/src/client/AssistantChat.tsx +138 -46
  310. package/src/client/MultiTabAssistantChat.tsx +16 -10
  311. package/src/client/agent-chat-adapter.ts +21 -3
  312. package/src/client/analytics.ts +159 -102
  313. package/src/client/chat/legacy-chart-shorthand.tsx +479 -0
  314. package/src/client/chat/markdown-renderer.tsx +23 -3
  315. package/src/client/chat/message-components.tsx +12 -5
  316. package/src/client/chat/runtime.ts +2 -1
  317. package/src/client/chat/tool-call-display.tsx +25 -2
  318. package/src/client/chat-model-groups.ts +3 -0
  319. package/src/client/client-status-requests.ts +161 -0
  320. package/src/client/settings/VoiceTranscriptionSection.tsx +2 -2
  321. package/src/client/sse-event-processor.ts +15 -3
  322. package/src/client/use-agent-engine-configured.ts +54 -50
  323. package/src/client/use-chat-models.ts +16 -11
  324. package/src/client/use-chat-threads.ts +32 -9
  325. package/src/client/use-session.ts +4 -5
  326. package/src/db/client.ts +83 -8
  327. package/src/db/ensure-additive-columns.ts +31 -20
  328. package/src/db/migrations.ts +22 -2
  329. package/src/extensions/actions.ts +1 -1
  330. package/src/mcp/approval-store.ts +99 -0
  331. package/src/mcp/build-server.ts +579 -303
  332. package/src/mcp/embed-app.ts +1 -1
  333. package/src/mcp/oauth-client-metadata.ts +380 -0
  334. package/src/mcp/oauth-route.ts +57 -52
  335. package/src/mcp/screen-memory-stdio.ts +400 -378
  336. package/src/mcp/server.ts +29 -145
  337. package/src/mcp/stdio.ts +72 -80
  338. package/src/mcp-client/manager.ts +6 -10
  339. package/src/mcp-client/oauth-client.ts +127 -36
  340. package/src/mcp-client/oauth-routes.ts +16 -6
  341. package/src/mcp-client/routes.ts +6 -5
  342. package/src/server/agent-capabilities.ts +20 -0
  343. package/src/server/agent-chat/action-filters-a2a.ts +27 -0
  344. package/src/server/agent-chat-plugin.ts +18 -0
  345. package/src/server/transcribe-voice.ts +14 -5
  346. package/src/templates/chat/.agents/skills/security/SKILL.md +13 -2
  347. package/src/templates/default/.agents/skills/security/SKILL.md +13 -2
  348. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +5 -2
  349. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +18 -0
  350. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +13 -2
@@ -0,0 +1,297 @@
1
+ import { ssrfSafeFetch } from "../extensions/url-safety.js";
2
+ const CLIENT_METADATA_MAX_BYTES = 64 * 1024;
3
+ const CLIENT_METADATA_TIMEOUT_MS = 5_000;
4
+ const CLIENT_METADATA_MAX_REDIRECTS = 2;
5
+ const CLIENT_METADATA_MAX_CACHE_ENTRIES = 128;
6
+ const CLIENT_METADATA_MAX_CACHE_TTL_MS = 5 * 60_000;
7
+ const DISALLOWED_REDIRECT_SCHEMES = new Set([
8
+ "javascript:",
9
+ "data:",
10
+ "vbscript:",
11
+ "file:",
12
+ "blob:",
13
+ "about:",
14
+ ]);
15
+ const clientMetadataCache = new Map();
16
+ export function isUrlBasedOAuthClientId(clientId) {
17
+ return /^[a-z][a-z0-9+.-]*:/i.test(clientId);
18
+ }
19
+ export function validateOAuthClientMetadataUrl(clientId) {
20
+ if (!clientId || clientId !== clientId.trim() || clientId.length > 2048) {
21
+ throw new Error("Client metadata URL is invalid");
22
+ }
23
+ let url;
24
+ try {
25
+ url = new URL(clientId);
26
+ }
27
+ catch {
28
+ throw new Error("Client metadata URL is invalid");
29
+ }
30
+ if (url.protocol !== "https:" ||
31
+ url.pathname === "/" ||
32
+ !url.pathname ||
33
+ url.hash ||
34
+ url.username ||
35
+ url.password) {
36
+ throw new Error("Client metadata URL must be HTTPS with a non-root path");
37
+ }
38
+ const authorityEnd = clientId.indexOf("/", "https://".length);
39
+ const queryStart = clientId.indexOf("?", authorityEnd);
40
+ const rawPath = clientId.slice(authorityEnd, queryStart === -1 ? undefined : queryStart);
41
+ if (rawPath.split("/").some((segment) => {
42
+ try {
43
+ const decoded = decodeURIComponent(segment);
44
+ return decoded === "." || decoded === "..";
45
+ }
46
+ catch {
47
+ return true;
48
+ }
49
+ })) {
50
+ throw new Error("Client metadata URL must not contain dot path segments");
51
+ }
52
+ return url;
53
+ }
54
+ export function isAllowedOAuthRedirectUri(value) {
55
+ if (typeof value !== "string" || value.length > 2048)
56
+ return false;
57
+ try {
58
+ const url = new URL(value);
59
+ if (url.hash || url.username || url.password)
60
+ return false;
61
+ if (url.protocol === "https:")
62
+ return true;
63
+ if (url.protocol === "http:") {
64
+ return (url.hostname === "localhost" ||
65
+ url.hostname === "127.0.0.1" ||
66
+ url.hostname === "::1" ||
67
+ url.hostname === "[::1]");
68
+ }
69
+ return (!DISALLOWED_REDIRECT_SCHEMES.has(url.protocol) &&
70
+ /^[a-z][a-z0-9+.-]*:$/.test(url.protocol));
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ export function applicationTypeForRedirectUris(redirectUris) {
77
+ return redirectUris.some((value) => {
78
+ const url = new URL(value);
79
+ return (url.protocol !== "https:" ||
80
+ url.hostname === "localhost" ||
81
+ url.hostname === "127.0.0.1" ||
82
+ url.hostname === "::1" ||
83
+ url.hostname === "[::1]");
84
+ })
85
+ ? "native"
86
+ : "web";
87
+ }
88
+ function parseStringArray(value) {
89
+ return Array.isArray(value)
90
+ ? value.filter((item) => typeof item === "string")
91
+ : [];
92
+ }
93
+ function validateClientMetadataDocument(clientId, value) {
94
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
95
+ throw new Error("Client metadata document must be a JSON object");
96
+ }
97
+ const document = value;
98
+ if (document.client_id !== clientId) {
99
+ throw new Error("Client metadata document client_id does not match its URL");
100
+ }
101
+ const clientName = typeof document.client_name === "string" ? document.client_name.trim() : "";
102
+ if (!clientName ||
103
+ clientName.length > 120 ||
104
+ /[\u0000-\u001f\u007f]/.test(clientName)) {
105
+ throw new Error("Client metadata document client_name is invalid");
106
+ }
107
+ const rawRedirectUris = document.redirect_uris;
108
+ const redirectUris = parseStringArray(rawRedirectUris);
109
+ if (!Array.isArray(rawRedirectUris) ||
110
+ redirectUris.length === 0 ||
111
+ redirectUris.length > 20 ||
112
+ redirectUris.length !== rawRedirectUris.length ||
113
+ !redirectUris.every(isAllowedOAuthRedirectUri)) {
114
+ throw new Error("Client metadata document redirect_uris are invalid");
115
+ }
116
+ const tokenEndpointAuthMethod = typeof document.token_endpoint_auth_method === "string"
117
+ ? document.token_endpoint_auth_method
118
+ : "none";
119
+ if (document.token_endpoint_auth_method !== undefined &&
120
+ typeof document.token_endpoint_auth_method !== "string") {
121
+ throw new Error("Client metadata document token_endpoint_auth_method is invalid");
122
+ }
123
+ if (tokenEndpointAuthMethod !== "none") {
124
+ throw new Error("Only public Client ID Metadata clients are supported");
125
+ }
126
+ const grantTypes = parseStringArray(document.grant_types);
127
+ if ((document.grant_types !== undefined &&
128
+ (!Array.isArray(document.grant_types) ||
129
+ grantTypes.length !== document.grant_types.length)) ||
130
+ grantTypes.length > 10 ||
131
+ !grantTypes.every((grant) => grant === "authorization_code" || grant === "refresh_token")) {
132
+ throw new Error("Client metadata document grant_types are unsupported");
133
+ }
134
+ const responseTypes = parseStringArray(document.response_types);
135
+ if ((document.response_types !== undefined &&
136
+ (!Array.isArray(document.response_types) ||
137
+ responseTypes.length !== document.response_types.length)) ||
138
+ responseTypes.length > 10 ||
139
+ !responseTypes.every((responseType) => responseType === "code")) {
140
+ throw new Error("Client metadata document response_types are unsupported");
141
+ }
142
+ const requestedApplicationType = typeof document.application_type === "string"
143
+ ? document.application_type
144
+ : undefined;
145
+ if ((document.application_type !== undefined &&
146
+ typeof document.application_type !== "string") ||
147
+ (requestedApplicationType &&
148
+ requestedApplicationType !== "native" &&
149
+ requestedApplicationType !== "web")) {
150
+ throw new Error("Client metadata document application_type is unsupported");
151
+ }
152
+ const applicationType = requestedApplicationType === "native" || requestedApplicationType === "web"
153
+ ? requestedApplicationType
154
+ : applicationTypeForRedirectUris(redirectUris);
155
+ return {
156
+ clientId,
157
+ clientName,
158
+ redirectUris: [...new Set(redirectUris)],
159
+ grantTypes: grantTypes.length
160
+ ? [...new Set(grantTypes)]
161
+ : ["authorization_code", "refresh_token"],
162
+ responseTypes: responseTypes.length
163
+ ? [...new Set(responseTypes)]
164
+ : ["code"],
165
+ tokenEndpointAuthMethod: "none",
166
+ applicationType,
167
+ };
168
+ }
169
+ async function readBoundedResponseBody(response) {
170
+ const contentLength = Number(response.headers.get("content-length"));
171
+ if (Number.isFinite(contentLength) &&
172
+ contentLength > CLIENT_METADATA_MAX_BYTES) {
173
+ await response.body?.cancel().catch(() => undefined);
174
+ throw new Error("Client metadata document is too large");
175
+ }
176
+ if (!response.body)
177
+ return "";
178
+ const reader = response.body.getReader();
179
+ const decoder = new TextDecoder();
180
+ let bytesRead = 0;
181
+ let text = "";
182
+ try {
183
+ for (;;) {
184
+ const { done, value } = await reader.read();
185
+ if (done)
186
+ break;
187
+ bytesRead += value.byteLength;
188
+ if (bytesRead > CLIENT_METADATA_MAX_BYTES) {
189
+ await reader.cancel().catch(() => undefined);
190
+ throw new Error("Client metadata document is too large");
191
+ }
192
+ text += decoder.decode(value, { stream: true });
193
+ }
194
+ text += decoder.decode();
195
+ return text;
196
+ }
197
+ finally {
198
+ reader.releaseLock();
199
+ }
200
+ }
201
+ function cacheTtlMs(headers, now) {
202
+ const cacheControl = headers.get("cache-control")?.toLowerCase() ?? "";
203
+ if (/(?:^|,)\s*(?:no-store|no-cache|private)(?:\s|,|$)/.test(cacheControl)) {
204
+ return 0;
205
+ }
206
+ const maxAge = cacheControl.match(/(?:^|,)\s*max-age=(\d+)/)?.[1];
207
+ if (maxAge) {
208
+ return Math.min(Number(maxAge) * 1_000, CLIENT_METADATA_MAX_CACHE_TTL_MS);
209
+ }
210
+ const expiresAt = Date.parse(headers.get("expires") ?? "");
211
+ if (Number.isFinite(expiresAt) && expiresAt > now) {
212
+ return Math.min(expiresAt - now, CLIENT_METADATA_MAX_CACHE_TTL_MS);
213
+ }
214
+ return 0;
215
+ }
216
+ function cacheClientMetadata(clientId, metadata, response, now) {
217
+ const ttlMs = cacheTtlMs(response.headers, now);
218
+ if (ttlMs <= 0)
219
+ return;
220
+ clientMetadataCache.delete(clientId);
221
+ clientMetadataCache.set(clientId, {
222
+ expiresAt: now + ttlMs,
223
+ metadata,
224
+ });
225
+ while (clientMetadataCache.size > CLIENT_METADATA_MAX_CACHE_ENTRIES) {
226
+ const oldest = clientMetadataCache.keys().next().value;
227
+ if (typeof oldest !== "string")
228
+ break;
229
+ clientMetadataCache.delete(oldest);
230
+ }
231
+ }
232
+ async function fetchOAuthClientMetadataDocument(clientId, signal) {
233
+ const response = await ssrfSafeFetch(clientId, {
234
+ headers: {
235
+ Accept: "application/json, application/client-metadata+json",
236
+ },
237
+ signal,
238
+ }, {
239
+ maxRedirects: CLIENT_METADATA_MAX_REDIRECTS,
240
+ httpsOnly: true,
241
+ });
242
+ if (!response.ok) {
243
+ await response.body?.cancel().catch(() => undefined);
244
+ throw new Error(`Client metadata document request failed with ${response.status}`);
245
+ }
246
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim() ?? "";
247
+ if (contentType !== "application/json" &&
248
+ contentType !== "application/client-metadata+json") {
249
+ await response.body?.cancel().catch(() => undefined);
250
+ throw new Error("Client metadata document content type is invalid");
251
+ }
252
+ const body = await readBoundedResponseBody(response);
253
+ let parsed;
254
+ try {
255
+ parsed = JSON.parse(body);
256
+ }
257
+ catch {
258
+ throw new Error("Client metadata document is not valid JSON");
259
+ }
260
+ return {
261
+ metadata: validateClientMetadataDocument(clientId, parsed),
262
+ response,
263
+ };
264
+ }
265
+ export async function resolveOAuthClientMetadataDocument(clientId) {
266
+ validateOAuthClientMetadataUrl(clientId);
267
+ const now = Date.now();
268
+ const cached = clientMetadataCache.get(clientId);
269
+ if (cached && cached.expiresAt > now) {
270
+ clientMetadataCache.delete(clientId);
271
+ clientMetadataCache.set(clientId, cached);
272
+ return cached.metadata;
273
+ }
274
+ if (cached)
275
+ clientMetadataCache.delete(clientId);
276
+ const abort = new AbortController();
277
+ let timeout;
278
+ try {
279
+ const timedOut = new Promise((_, reject) => {
280
+ timeout = setTimeout(() => {
281
+ abort.abort();
282
+ reject(new Error("Client metadata document request timed out"));
283
+ }, CLIENT_METADATA_TIMEOUT_MS);
284
+ });
285
+ const { metadata, response } = await Promise.race([
286
+ fetchOAuthClientMetadataDocument(clientId, abort.signal),
287
+ timedOut,
288
+ ]);
289
+ cacheClientMetadata(clientId, metadata, response, now);
290
+ return metadata;
291
+ }
292
+ finally {
293
+ if (timeout)
294
+ clearTimeout(timeout);
295
+ }
296
+ }
297
+ //# sourceMappingURL=oauth-client-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client-metadata.js","sourceRoot":"","sources":["../../src/mcp/oauth-client-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["import { ssrfSafeFetch } from \"../extensions/url-safety.js\";\n\nconst CLIENT_METADATA_MAX_BYTES = 64 * 1024;\nconst CLIENT_METADATA_TIMEOUT_MS = 5_000;\nconst CLIENT_METADATA_MAX_REDIRECTS = 2;\nconst CLIENT_METADATA_MAX_CACHE_ENTRIES = 128;\nconst CLIENT_METADATA_MAX_CACHE_TTL_MS = 5 * 60_000;\n\nconst DISALLOWED_REDIRECT_SCHEMES = new Set([\n \"javascript:\",\n \"data:\",\n \"vbscript:\",\n \"file:\",\n \"blob:\",\n \"about:\",\n]);\n\nexport interface OAuthClientMetadataDocument {\n clientId: string;\n clientName: string;\n redirectUris: string[];\n grantTypes: string[];\n responseTypes: string[];\n tokenEndpointAuthMethod: \"none\";\n applicationType: \"native\" | \"web\";\n}\n\ninterface CachedClientMetadata {\n expiresAt: number;\n metadata: OAuthClientMetadataDocument;\n}\n\nconst clientMetadataCache = new Map<string, CachedClientMetadata>();\n\nexport function isUrlBasedOAuthClientId(clientId: string): boolean {\n return /^[a-z][a-z0-9+.-]*:/i.test(clientId);\n}\n\nexport function validateOAuthClientMetadataUrl(clientId: string): URL {\n if (!clientId || clientId !== clientId.trim() || clientId.length > 2048) {\n throw new Error(\"Client metadata URL is invalid\");\n }\n let url: URL;\n try {\n url = new URL(clientId);\n } catch {\n throw new Error(\"Client metadata URL is invalid\");\n }\n if (\n url.protocol !== \"https:\" ||\n url.pathname === \"/\" ||\n !url.pathname ||\n url.hash ||\n url.username ||\n url.password\n ) {\n throw new Error(\"Client metadata URL must be HTTPS with a non-root path\");\n }\n const authorityEnd = clientId.indexOf(\"/\", \"https://\".length);\n const queryStart = clientId.indexOf(\"?\", authorityEnd);\n const rawPath = clientId.slice(\n authorityEnd,\n queryStart === -1 ? undefined : queryStart,\n );\n if (\n rawPath.split(\"/\").some((segment) => {\n try {\n const decoded = decodeURIComponent(segment);\n return decoded === \".\" || decoded === \"..\";\n } catch {\n return true;\n }\n })\n ) {\n throw new Error(\"Client metadata URL must not contain dot path segments\");\n }\n return url;\n}\n\nexport function isAllowedOAuthRedirectUri(value: unknown): value is string {\n if (typeof value !== \"string\" || value.length > 2048) return false;\n try {\n const url = new URL(value);\n if (url.hash || url.username || url.password) return false;\n if (url.protocol === \"https:\") return true;\n if (url.protocol === \"http:\") {\n return (\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\" ||\n url.hostname === \"[::1]\"\n );\n }\n return (\n !DISALLOWED_REDIRECT_SCHEMES.has(url.protocol) &&\n /^[a-z][a-z0-9+.-]*:$/.test(url.protocol)\n );\n } catch {\n return false;\n }\n}\n\nexport function applicationTypeForRedirectUris(\n redirectUris: string[],\n): \"native\" | \"web\" {\n return redirectUris.some((value) => {\n const url = new URL(value);\n return (\n url.protocol !== \"https:\" ||\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\" ||\n url.hostname === \"[::1]\"\n );\n })\n ? \"native\"\n : \"web\";\n}\n\nfunction parseStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : [];\n}\n\nfunction validateClientMetadataDocument(\n clientId: string,\n value: unknown,\n): OAuthClientMetadataDocument {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"Client metadata document must be a JSON object\");\n }\n const document = value as Record<string, unknown>;\n if (document.client_id !== clientId) {\n throw new Error(\n \"Client metadata document client_id does not match its URL\",\n );\n }\n const clientName =\n typeof document.client_name === \"string\" ? document.client_name.trim() : \"\";\n if (\n !clientName ||\n clientName.length > 120 ||\n /[\\u0000-\\u001f\\u007f]/.test(clientName)\n ) {\n throw new Error(\"Client metadata document client_name is invalid\");\n }\n\n const rawRedirectUris = document.redirect_uris;\n const redirectUris = parseStringArray(rawRedirectUris);\n if (\n !Array.isArray(rawRedirectUris) ||\n redirectUris.length === 0 ||\n redirectUris.length > 20 ||\n redirectUris.length !== rawRedirectUris.length ||\n !redirectUris.every(isAllowedOAuthRedirectUri)\n ) {\n throw new Error(\"Client metadata document redirect_uris are invalid\");\n }\n\n const tokenEndpointAuthMethod =\n typeof document.token_endpoint_auth_method === \"string\"\n ? document.token_endpoint_auth_method\n : \"none\";\n if (\n document.token_endpoint_auth_method !== undefined &&\n typeof document.token_endpoint_auth_method !== \"string\"\n ) {\n throw new Error(\n \"Client metadata document token_endpoint_auth_method is invalid\",\n );\n }\n if (tokenEndpointAuthMethod !== \"none\") {\n throw new Error(\"Only public Client ID Metadata clients are supported\");\n }\n\n const grantTypes = parseStringArray(document.grant_types);\n if (\n (document.grant_types !== undefined &&\n (!Array.isArray(document.grant_types) ||\n grantTypes.length !== document.grant_types.length)) ||\n grantTypes.length > 10 ||\n !grantTypes.every(\n (grant) => grant === \"authorization_code\" || grant === \"refresh_token\",\n )\n ) {\n throw new Error(\"Client metadata document grant_types are unsupported\");\n }\n\n const responseTypes = parseStringArray(document.response_types);\n if (\n (document.response_types !== undefined &&\n (!Array.isArray(document.response_types) ||\n responseTypes.length !== document.response_types.length)) ||\n responseTypes.length > 10 ||\n !responseTypes.every((responseType) => responseType === \"code\")\n ) {\n throw new Error(\"Client metadata document response_types are unsupported\");\n }\n\n const requestedApplicationType =\n typeof document.application_type === \"string\"\n ? document.application_type\n : undefined;\n if (\n (document.application_type !== undefined &&\n typeof document.application_type !== \"string\") ||\n (requestedApplicationType &&\n requestedApplicationType !== \"native\" &&\n requestedApplicationType !== \"web\")\n ) {\n throw new Error(\"Client metadata document application_type is unsupported\");\n }\n const applicationType: \"native\" | \"web\" =\n requestedApplicationType === \"native\" || requestedApplicationType === \"web\"\n ? requestedApplicationType\n : applicationTypeForRedirectUris(redirectUris);\n\n return {\n clientId,\n clientName,\n redirectUris: [...new Set(redirectUris)],\n grantTypes: grantTypes.length\n ? [...new Set(grantTypes)]\n : [\"authorization_code\", \"refresh_token\"],\n responseTypes: responseTypes.length\n ? [...new Set(responseTypes)]\n : [\"code\"],\n tokenEndpointAuthMethod: \"none\",\n applicationType,\n };\n}\n\nasync function readBoundedResponseBody(response: Response): Promise<string> {\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (\n Number.isFinite(contentLength) &&\n contentLength > CLIENT_METADATA_MAX_BYTES\n ) {\n await response.body?.cancel().catch(() => undefined);\n throw new Error(\"Client metadata document is too large\");\n }\n if (!response.body) return \"\";\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let bytesRead = 0;\n let text = \"\";\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > CLIENT_METADATA_MAX_BYTES) {\n await reader.cancel().catch(() => undefined);\n throw new Error(\"Client metadata document is too large\");\n }\n text += decoder.decode(value, { stream: true });\n }\n text += decoder.decode();\n return text;\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction cacheTtlMs(headers: Headers, now: number): number {\n const cacheControl = headers.get(\"cache-control\")?.toLowerCase() ?? \"\";\n if (/(?:^|,)\\s*(?:no-store|no-cache|private)(?:\\s|,|$)/.test(cacheControl)) {\n return 0;\n }\n const maxAge = cacheControl.match(/(?:^|,)\\s*max-age=(\\d+)/)?.[1];\n if (maxAge) {\n return Math.min(Number(maxAge) * 1_000, CLIENT_METADATA_MAX_CACHE_TTL_MS);\n }\n const expiresAt = Date.parse(headers.get(\"expires\") ?? \"\");\n if (Number.isFinite(expiresAt) && expiresAt > now) {\n return Math.min(expiresAt - now, CLIENT_METADATA_MAX_CACHE_TTL_MS);\n }\n return 0;\n}\n\nfunction cacheClientMetadata(\n clientId: string,\n metadata: OAuthClientMetadataDocument,\n response: Response,\n now: number,\n): void {\n const ttlMs = cacheTtlMs(response.headers, now);\n if (ttlMs <= 0) return;\n clientMetadataCache.delete(clientId);\n clientMetadataCache.set(clientId, {\n expiresAt: now + ttlMs,\n metadata,\n });\n while (clientMetadataCache.size > CLIENT_METADATA_MAX_CACHE_ENTRIES) {\n const oldest = clientMetadataCache.keys().next().value;\n if (typeof oldest !== \"string\") break;\n clientMetadataCache.delete(oldest);\n }\n}\n\nasync function fetchOAuthClientMetadataDocument(\n clientId: string,\n signal: AbortSignal,\n): Promise<{ metadata: OAuthClientMetadataDocument; response: Response }> {\n const response = await ssrfSafeFetch(\n clientId,\n {\n headers: {\n Accept: \"application/json, application/client-metadata+json\",\n },\n signal,\n },\n {\n maxRedirects: CLIENT_METADATA_MAX_REDIRECTS,\n httpsOnly: true,\n },\n );\n if (!response.ok) {\n await response.body?.cancel().catch(() => undefined);\n throw new Error(\n `Client metadata document request failed with ${response.status}`,\n );\n }\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\", 1)[0]?.trim() ?? \"\";\n if (\n contentType !== \"application/json\" &&\n contentType !== \"application/client-metadata+json\"\n ) {\n await response.body?.cancel().catch(() => undefined);\n throw new Error(\"Client metadata document content type is invalid\");\n }\n\n const body = await readBoundedResponseBody(response);\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n throw new Error(\"Client metadata document is not valid JSON\");\n }\n return {\n metadata: validateClientMetadataDocument(clientId, parsed),\n response,\n };\n}\n\nexport async function resolveOAuthClientMetadataDocument(\n clientId: string,\n): Promise<OAuthClientMetadataDocument> {\n validateOAuthClientMetadataUrl(clientId);\n const now = Date.now();\n const cached = clientMetadataCache.get(clientId);\n if (cached && cached.expiresAt > now) {\n clientMetadataCache.delete(clientId);\n clientMetadataCache.set(clientId, cached);\n return cached.metadata;\n }\n if (cached) clientMetadataCache.delete(clientId);\n\n const abort = new AbortController();\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n const timedOut = new Promise<never>((_, reject) => {\n timeout = setTimeout(() => {\n abort.abort();\n reject(new Error(\"Client metadata document request timed out\"));\n }, CLIENT_METADATA_TIMEOUT_MS);\n });\n const { metadata, response } = await Promise.race([\n fetchOAuthClientMetadataDocument(clientId, abort.signal),\n timedOut,\n ]);\n cacheClientMetadata(clientId, metadata, response, now);\n return metadata;\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-route.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AA2BlC,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4HD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIpE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAItE;AAWD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAuB7D;AAED,wBAAgB,uCAAuC,CACrD,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,SAAS,CAIpB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAM7D;AAqBD,wBAAgB,uCAAuC,CACrD,KAAK,EAAE,OAAO,GACb,QAAQ,CAeV;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,OAAO,GACb,QAAQ,CAsBV;AAyrBD,wBAAsB,cAAc,CAClC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAenB"}
1
+ {"version":3,"file":"oauth-route.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAgClC,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIpE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAItE;AAWD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAuB7D;AAED,wBAAgB,uCAAuC,CACrD,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,SAAS,CAIpB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAM7D;AAqBD,wBAAgB,uCAAuC,CACrD,KAAK,EAAE,OAAO,GACb,QAAQ,CAeV;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,OAAO,GACb,QAAQ,CAwBV;AAkrBD,wBAAsB,cAAc,CAClC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAenB"}
@@ -13,9 +13,12 @@ import { getOrgDomain } from "../org/context.js";
13
13
  import { getConfiguredLoginHtml, getSession } from "../server/auth.js";
14
14
  import { getAuthSecret } from "../server/better-auth-instance.js";
15
15
  import { readBody } from "../server/h3-helpers.js";
16
+ import { applicationTypeForRedirectUris, isAllowedOAuthRedirectUri, isUrlBasedOAuthClientId, resolveOAuthClientMetadataDocument, } from "./oauth-client-metadata.js";
16
17
  import { createOAuthCode, createOAuthRefreshToken, consumeOAuthCode, generateOpaqueToken, getOAuthClient, getOAuthCode, getOAuthRefreshToken, registerOAuthClient, touchOAuthRefreshToken, } from "./oauth-store.js";
17
- import { MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS, MCP_OAUTH_DEFAULT_SCOPE, MCP_OAUTH_SCOPES, normalizeOAuthScope, signMcpOAuthAccessToken, } from "./oauth-token.js";
18
+ import { MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS, MCP_OAUTH_SCOPES, normalizeOAuthScope, signMcpOAuthAccessToken, } from "./oauth-token.js";
18
19
  import { MCP_PUBLIC_ROUTE_PREFIX, MCP_ROUTE_PREFIXES } from "./route-paths.js";
20
+ const MCP_OAUTH_RESOURCE_SCOPES = MCP_OAUTH_SCOPES.filter((scope) => scope !== "offline_access");
21
+ const MCP_OAUTH_RESOURCE_SCOPE = MCP_OAUTH_RESOURCE_SCOPES.join(" ");
19
22
  function json(body, status = 200) {
20
23
  return new Response(JSON.stringify(body), {
21
24
  status,
@@ -187,7 +190,7 @@ export function getMcpOAuthProtectedResourceMetadataUrl(event) {
187
190
  }
188
191
  export function buildMcpOAuthChallenge(event) {
189
192
  const metadata = getMcpOAuthProtectedResourceMetadataUrl(event);
190
- const scope = MCP_OAUTH_DEFAULT_SCOPE;
193
+ const scope = MCP_OAUTH_RESOURCE_SCOPE;
191
194
  return metadata
192
195
  ? `Bearer resource_metadata="${metadata}", scope="${scope}"`
193
196
  : `Bearer scope="${scope}"`;
@@ -220,7 +223,7 @@ export function handleMcpOAuthProtectedResourceMetadata(event) {
220
223
  return json({
221
224
  resource,
222
225
  authorization_servers: [issuer],
223
- scopes_supported: MCP_OAUTH_SCOPES,
226
+ scopes_supported: MCP_OAUTH_RESOURCE_SCOPES,
224
227
  resource_documentation: issuer,
225
228
  });
226
229
  }
@@ -245,54 +248,10 @@ export function handleMcpOAuthAuthorizationServerMetadata(event) {
245
248
  code_challenge_methods_supported: ["S256"],
246
249
  token_endpoint_auth_methods_supported: ["none"],
247
250
  scopes_supported: MCP_OAUTH_SCOPES,
251
+ authorization_response_iss_parameter_supported: true,
252
+ client_id_metadata_document_supported: true,
248
253
  });
249
254
  }
250
- // Schemes that must never be accepted as a redirect target: they can execute
251
- // script or read local resources if a redirect is ever rendered in a browser
252
- // or webview context.
253
- const DISALLOWED_REDIRECT_SCHEMES = new Set([
254
- "javascript:",
255
- "data:",
256
- "vbscript:",
257
- "file:",
258
- "blob:",
259
- "about:",
260
- ]);
261
- // Native/desktop IDE clients (Cursor, VS Code, …) register a private-use URI
262
- // scheme callback such as `cursor://` or `vscode://` (RFC 8252 §7.1) instead of
263
- // an https/loopback URL. The authorization code is bound by PKCE (S256), so
264
- // delivering it through a client-registered app scheme is safe.
265
- function isPrivateUseRedirectScheme(protocol) {
266
- if (DISALLOWED_REDIRECT_SCHEMES.has(protocol))
267
- return false;
268
- // RFC 3986 scheme grammar: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ), with a
269
- // trailing ":" from URL.protocol. Require a non-http(s) custom scheme here;
270
- // http/https are handled explicitly above.
271
- return /^[a-z][a-z0-9+.-]*:$/.test(protocol);
272
- }
273
- function isAllowedRedirectUri(value) {
274
- if (typeof value !== "string" || value.length > 2048)
275
- return false;
276
- try {
277
- const url = new URL(value);
278
- if (url.hash)
279
- return false;
280
- if (url.username || url.password)
281
- return false;
282
- if (url.protocol === "https:")
283
- return true;
284
- if (url.protocol === "http:") {
285
- return (url.hostname === "localhost" ||
286
- url.hostname === "127.0.0.1" ||
287
- url.hostname === "::1" ||
288
- url.hostname === "[::1]");
289
- }
290
- return isPrivateUseRedirectScheme(url.protocol);
291
- }
292
- catch {
293
- return false;
294
- }
295
- }
296
255
  function parseStringArray(value) {
297
256
  return Array.isArray(value)
298
257
  ? value.filter((item) => typeof item === "string")
@@ -306,7 +265,7 @@ async function handleRegister(event) {
306
265
  const redirectUris = parseStringArray(body.redirect_uris);
307
266
  if (redirectUris.length === 0 ||
308
267
  redirectUris.length > 20 ||
309
- !redirectUris.every(isAllowedRedirectUri)) {
268
+ !redirectUris.every(isAllowedOAuthRedirectUri)) {
310
269
  return oauthError("invalid_client_metadata", "redirect_uris must contain valid HTTPS or localhost callback URLs");
311
270
  }
312
271
  const grantTypes = parseStringArray(body.grant_types);
@@ -324,6 +283,15 @@ async function handleRegister(event) {
324
283
  if (method !== "none") {
325
284
  return oauthError("invalid_client_metadata", "Only public OAuth clients are supported");
326
285
  }
286
+ const requestedApplicationType = typeof body.application_type === "string"
287
+ ? body.application_type
288
+ : undefined;
289
+ if (requestedApplicationType &&
290
+ requestedApplicationType !== "native" &&
291
+ requestedApplicationType !== "web") {
292
+ return oauthError("invalid_client_metadata", "application_type must be native or web");
293
+ }
294
+ const applicationType = requestedApplicationType ?? applicationTypeForRedirectUris(redirectUris);
327
295
  const clientName = typeof body.client_name === "string"
328
296
  ? body.client_name.trim().slice(0, 120)
329
297
  : null;
@@ -351,11 +319,13 @@ async function handleRegister(event) {
351
319
  grant_types: client.grantTypes,
352
320
  response_types: client.responseTypes,
353
321
  token_endpoint_auth_method: client.tokenEndpointAuthMethod,
322
+ application_type: applicationType,
354
323
  }, 201);
355
324
  }
356
325
  function redirectWithOAuthError(params) {
357
326
  const url = new URL(params.redirectUri);
358
327
  url.searchParams.set("error", params.error);
328
+ url.searchParams.set("iss", params.issuer);
359
329
  if (params.description) {
360
330
  url.searchParams.set("error_description", params.description);
361
331
  }
@@ -366,6 +336,7 @@ function redirectWithOAuthError(params) {
366
336
  function buildCodeRedirectUrl(params) {
367
337
  const url = new URL(params.redirectUri);
368
338
  url.searchParams.set("code", params.code);
339
+ url.searchParams.set("iss", params.issuer);
369
340
  if (params.state)
370
341
  url.searchParams.set("state", params.state);
371
342
  return url.toString();
@@ -467,6 +438,7 @@ function renderConsentPage(params) {
467
438
  <h1>Authorize ${escapeHtml(params.clientName)}</h1>
468
439
  <p>${escapeHtml(params.appName)} will let this MCP client act as ${escapeHtml(params.email)} for these scopes:</p>
469
440
  <ul>${scopes}</ul>
441
+ <p>After authorization, your browser will return to <code>${escapeHtml(params.redirectUri)}</code>.</p>
470
442
  <form method="post">
471
443
  ${hidden}
472
444
  <div class="actions">
@@ -561,6 +533,7 @@ async function handleAuthorize(event, options) {
561
533
  return oauthError("invalid_request", "Cross-origin authorize POST rejected", 403);
562
534
  }
563
535
  const params = await readOAuthParams(event);
536
+ const issuer = getMcpOAuthIssuer(event);
564
537
  const state = params.state;
565
538
  const clientId = params.client_id;
566
539
  const redirectUri = params.redirect_uri;
@@ -571,6 +544,7 @@ async function handleAuthorize(event, options) {
571
544
  }
572
545
  if (!clientId ||
573
546
  !redirectUri ||
547
+ !issuer ||
574
548
  !resource ||
575
549
  !expectedResources.includes(resource)) {
576
550
  return oauthError("invalid_request", "Invalid OAuth authorization request");
@@ -578,7 +552,9 @@ async function handleAuthorize(event, options) {
578
552
  if (params.code_challenge_method !== "S256" || !params.code_challenge) {
579
553
  return oauthError("invalid_request", "PKCE S256 is required");
580
554
  }
581
- const client = await getOAuthClient(clientId);
555
+ const client = isUrlBasedOAuthClientId(clientId)
556
+ ? await resolveOAuthClientMetadataDocument(clientId).catch(() => null)
557
+ : await getOAuthClient(clientId);
582
558
  if (!client || !client.redirectUris.includes(redirectUri)) {
583
559
  return oauthError("invalid_client", "Unknown client or redirect_uri");
584
560
  }
@@ -587,6 +563,7 @@ async function handleAuthorize(event, options) {
587
563
  if (params.prompt === "none") {
588
564
  return redirectWithOAuthError({
589
565
  redirectUri,
566
+ issuer,
590
567
  state,
591
568
  error: "login_required",
592
569
  });
@@ -600,6 +577,7 @@ async function handleAuthorize(event, options) {
600
577
  if (!scope) {
601
578
  return redirectWithOAuthError({
602
579
  redirectUri,
580
+ issuer,
603
581
  state,
604
582
  error: "invalid_scope",
605
583
  });
@@ -609,6 +587,7 @@ async function handleAuthorize(event, options) {
609
587
  appName: options.appName || options.appId || "Agent Native",
610
588
  email: session.email,
611
589
  clientName: client.clientName || client.clientId,
590
+ redirectUri,
612
591
  scopes: scope.split(/\s+/),
613
592
  fields: {
614
593
  response_type: "code",
@@ -643,6 +622,7 @@ async function handleAuthorize(event, options) {
643
622
  if (params.decision !== "approve") {
644
623
  return redirectWithOAuthError({
645
624
  redirectUri,
625
+ issuer,
646
626
  state,
647
627
  error: "access_denied",
648
628
  });
@@ -670,7 +650,7 @@ async function handleAuthorize(event, options) {
670
650
  isDeepLinkRedirect =
671
651
  protocol !== "http:" &&
672
652
  protocol !== "https:" &&
673
- isPrivateUseRedirectScheme(protocol);
653
+ /^[a-z][a-z0-9+.-]*:$/.test(protocol);
674
654
  }
675
655
  catch {
676
656
  isDeepLinkRedirect = false;
@@ -681,12 +661,18 @@ async function handleAuthorize(event, options) {
681
661
  clientName: client.clientName ?? null,
682
662
  redirectUrl: buildCodeRedirectUrl({
683
663
  redirectUri,
664
+ issuer,
684
665
  state,
685
666
  code: code.code,
686
667
  }),
687
668
  }));
688
669
  }
689
- return redirectWithCode({ redirectUri, state, code: code.code });
670
+ return redirectWithCode({
671
+ redirectUri,
672
+ issuer,
673
+ state,
674
+ code: code.code,
675
+ });
690
676
  }
691
677
  async function issueTokenSet(params) {
692
678
  const refreshToken = generateOpaqueToken();