@agent-native/core 0.106.2 → 0.107.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 (240) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/docs/content/external-agents.mdx +8 -0
  4. package/corpus/core/docs/content/integrations.mdx +597 -0
  5. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +7 -0
  6. package/corpus/core/docs/content/locales/ar-SA/mcp-clients.mdx +19 -1
  7. package/corpus/core/docs/content/locales/ar-SA/mcp-protocol.mdx +7 -0
  8. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +9 -0
  9. package/corpus/core/docs/content/locales/de-DE/mcp-clients.mdx +22 -1
  10. package/corpus/core/docs/content/locales/de-DE/mcp-protocol.mdx +9 -0
  11. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +9 -0
  12. package/corpus/core/docs/content/locales/es-ES/mcp-clients.mdx +23 -1
  13. package/corpus/core/docs/content/locales/es-ES/mcp-protocol.mdx +9 -0
  14. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +9 -0
  15. package/corpus/core/docs/content/locales/fr-FR/mcp-clients.mdx +23 -1
  16. package/corpus/core/docs/content/locales/fr-FR/mcp-protocol.mdx +9 -0
  17. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +8 -0
  18. package/corpus/core/docs/content/locales/hi-IN/mcp-clients.mdx +22 -1
  19. package/corpus/core/docs/content/locales/hi-IN/mcp-protocol.mdx +8 -0
  20. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +8 -0
  21. package/corpus/core/docs/content/locales/ja-JP/mcp-clients.mdx +21 -1
  22. package/corpus/core/docs/content/locales/ja-JP/mcp-protocol.mdx +8 -0
  23. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +8 -0
  24. package/corpus/core/docs/content/locales/ko-KR/mcp-clients.mdx +20 -1
  25. package/corpus/core/docs/content/locales/ko-KR/mcp-protocol.mdx +8 -0
  26. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +9 -0
  27. package/corpus/core/docs/content/locales/pt-BR/mcp-clients.mdx +22 -1
  28. package/corpus/core/docs/content/locales/pt-BR/mcp-protocol.mdx +9 -0
  29. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +6 -0
  30. package/corpus/core/docs/content/locales/zh-CN/mcp-clients.mdx +17 -1
  31. package/corpus/core/docs/content/locales/zh-CN/mcp-protocol.mdx +6 -0
  32. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +6 -0
  33. package/corpus/core/docs/content/locales/zh-TW/mcp-clients.mdx +17 -1
  34. package/corpus/core/docs/content/locales/zh-TW/mcp-protocol.mdx +6 -0
  35. package/corpus/core/docs/content/mcp-clients.mdx +51 -1
  36. package/corpus/core/docs/content/mcp-protocol.mdx +8 -0
  37. package/corpus/core/docs/content/organizations-teams-permissions.mdx +413 -0
  38. package/corpus/core/package.json +1 -1
  39. package/corpus/core/src/client/AssistantChat.tsx +13 -1
  40. package/corpus/core/src/client/chat/message-components.tsx +41 -0
  41. package/corpus/core/src/client/composer/TiptapComposer.tsx +45 -8
  42. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +30 -11
  43. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +240 -0
  44. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +159 -40
  45. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +433 -4
  46. package/corpus/core/src/client/resources/mcp-integration-logos.ts +108 -0
  47. package/corpus/core/src/deploy/build.ts +32 -0
  48. package/corpus/core/src/extensions/html-shell.ts +33 -1
  49. package/corpus/core/src/ingestion/office.ts +1 -2
  50. package/corpus/core/src/ingestion/pptx.ts +2 -4
  51. package/corpus/core/src/localization/default-messages.ts +126 -1
  52. package/corpus/core/src/mcp/actions/call-mcp-tool.ts +1 -0
  53. package/corpus/core/src/mcp/builtin-tools.ts +115 -10
  54. package/corpus/core/src/server/index.ts +4 -0
  55. package/corpus/core/src/server/org-admin.ts +28 -0
  56. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +9 -0
  57. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +31 -0
  58. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +9 -0
  59. package/corpus/templates/analytics/.agents/skills/creative-context/SKILL.md +64 -0
  60. package/corpus/templates/analytics/AGENTS.md +5 -0
  61. package/corpus/templates/analytics/actions/clone-creative-context-dashboard.ts +86 -0
  62. package/corpus/templates/analytics/app/components/layout/Layout.tsx +2 -0
  63. package/corpus/templates/analytics/app/i18n/zh-TW.ts +1 -0
  64. package/corpus/templates/analytics/app/i18n-data.ts +1 -0
  65. package/corpus/templates/analytics/app/pages/Ask.tsx +8 -4
  66. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +59 -0
  67. package/corpus/templates/analytics/app/routes/agent.tsx +7 -1
  68. package/corpus/templates/analytics/changelog/2026-07-17-library-now-flags-published-dashboards-with-newer-versions-a.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-17-resources-can-be-added-to-creative-context-from-their-action.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-07-17-you-can-add-exact-approved-artifact-versions-to-governed-cre.md +6 -0
  71. package/corpus/templates/analytics/package.json +1 -0
  72. package/corpus/templates/analytics/server/lib/native-creative-context.ts +158 -0
  73. package/corpus/templates/analytics/server/plugins/creative-context.ts +10 -0
  74. package/corpus/templates/assets/.agents/skills/creative-context/SKILL.md +12 -3
  75. package/corpus/templates/assets/AGENTS.md +5 -0
  76. package/corpus/templates/assets/actions/clone-creative-context-asset.ts +74 -0
  77. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +2 -1
  78. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +308 -178
  79. package/corpus/templates/assets/app/routes/library.tsx +5 -11
  80. package/corpus/templates/assets/changelog/2026-07-17-library-now-flags-published-assets-with-newer-versions-and-l.md +6 -0
  81. package/corpus/templates/assets/changelog/2026-07-17-you-can-add-exact-approved-artifact-versions-to-governed-cre.md +6 -0
  82. package/corpus/templates/assets/server/lib/native-creative-context.ts +198 -0
  83. package/corpus/templates/assets/server/plugins/creative-context.ts +4 -0
  84. package/corpus/templates/brain/actions/provider-api-request.ts +1 -0
  85. package/corpus/templates/calendar/actions/provider-api-request.ts +1 -0
  86. package/corpus/templates/clips/actions/get-recording-insights.ts +7 -4
  87. package/corpus/templates/clips/actions/list-recordings.ts +48 -0
  88. package/corpus/templates/clips/actions/list-viewers.ts +2 -1
  89. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +3 -1
  90. package/corpus/templates/clips/app/components/player/share-dialog.tsx +1 -3
  91. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +11 -6
  92. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +6 -1
  93. package/corpus/templates/clips/app/lib/ffmpeg-export.ts +4 -2
  94. package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -3
  95. package/corpus/templates/clips/changelog/2026-07-17-dictation-keeps-sentence-spacing-and-no-longer-crashes-when-.md +6 -0
  96. package/corpus/templates/clips/changelog/2026-07-17-shared-clips-now-preserve-editor-access-and-show-editor-insi.md +6 -0
  97. package/corpus/templates/clips/changelog/2026-07-17-stitching-clips-no-longer-fails-when-editor-media-metadata-i.md +6 -0
  98. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +14 -8
  99. package/corpus/templates/clips/shared/view-analytics.ts +4 -0
  100. package/corpus/templates/content/.agents/skills/creative-context/SKILL.md +12 -3
  101. package/corpus/templates/content/AGENTS.md +5 -0
  102. package/corpus/templates/content/actions/clone-creative-context-document.ts +61 -0
  103. package/corpus/templates/content/actions/provider-api-request.ts +1 -0
  104. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +21 -0
  105. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +29 -0
  106. package/corpus/templates/content/changelog/2026-07-17-library-now-flags-published-documents-with-newer-versions-an.md +6 -0
  107. package/corpus/templates/content/changelog/2026-07-17-resources-can-be-added-to-creative-context-from-their-action.md +6 -0
  108. package/corpus/templates/content/changelog/2026-07-17-you-can-add-exact-approved-artifact-versions-to-governed-cre.md +6 -0
  109. package/corpus/templates/content/parity/matrix.md +1 -1
  110. package/corpus/templates/content/parity/matrix.ts +1 -0
  111. package/corpus/templates/content/server/lib/native-creative-context.ts +191 -0
  112. package/corpus/templates/content/server/plugins/creative-context.ts +8 -1
  113. package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +11 -3
  114. package/corpus/templates/design/AGENTS.md +5 -0
  115. package/corpus/templates/design/actions/clone-creative-context-design-native.ts +111 -0
  116. package/corpus/templates/design/actions/provider-api-request.ts +1 -0
  117. package/corpus/templates/design/app/pages/Index.tsx +44 -0
  118. package/corpus/templates/design/app/pages/design-editor/types.ts +1 -0
  119. package/corpus/templates/design/changelog/2026-07-17-library-now-flags-published-designs-with-newer-versions-and-.md +6 -0
  120. package/corpus/templates/design/changelog/2026-07-17-resources-can-be-added-to-creative-context-from-their-action.md +6 -0
  121. package/corpus/templates/design/changelog/2026-07-17-you-can-add-exact-approved-artifact-versions-to-governed-cre.md +6 -0
  122. package/corpus/templates/design/server/lib/native-creative-context.ts +265 -0
  123. package/corpus/templates/design/server/plugins/creative-context.ts +8 -1
  124. package/corpus/templates/dispatch/actions/provider-api-request.ts +1 -0
  125. package/corpus/templates/mail/actions/provider-api-request.ts +1 -0
  126. package/corpus/templates/slides/.agents/skills/creative-context/SKILL.md +11 -3
  127. package/corpus/templates/slides/AGENTS.md +5 -0
  128. package/corpus/templates/slides/actions/clone-creative-context-deck.ts +96 -0
  129. package/corpus/templates/slides/actions/extract-pdf.ts +3 -2
  130. package/corpus/templates/slides/actions/import-file.ts +3 -2
  131. package/corpus/templates/slides/actions/provider-api-request.ts +1 -0
  132. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +27 -0
  133. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -0
  134. package/corpus/templates/slides/changelog/2026-07-17-library-now-flags-published-decks-with-newer-versions-and-le.md +6 -0
  135. package/corpus/templates/slides/changelog/2026-07-17-pdf-and-powerpoint-imports-now-work-reliably-in-hosted-decks.md +6 -0
  136. package/corpus/templates/slides/changelog/2026-07-17-resources-can-be-added-to-creative-context-from-their-action.md +6 -0
  137. package/corpus/templates/slides/changelog/2026-07-17-you-can-add-exact-approved-artifact-versions-to-governed-cre.md +6 -0
  138. package/corpus/templates/slides/server/lib/native-creative-context.ts +288 -0
  139. package/corpus/templates/slides/server/plugins/creative-context.ts +4 -0
  140. package/dist/client/AssistantChat.d.ts.map +1 -1
  141. package/dist/client/AssistantChat.js +8 -2
  142. package/dist/client/AssistantChat.js.map +1 -1
  143. package/dist/client/chat/message-components.d.ts.map +1 -1
  144. package/dist/client/chat/message-components.js +35 -1
  145. package/dist/client/chat/message-components.js.map +1 -1
  146. package/dist/client/composer/TiptapComposer.d.ts +7 -0
  147. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  148. package/dist/client/composer/TiptapComposer.js +37 -8
  149. package/dist/client/composer/TiptapComposer.js.map +1 -1
  150. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  151. package/dist/client/integrations/IntegrationsPanel.js +3 -2
  152. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  153. package/dist/client/resources/McpConnectionSuggestion.d.ts +9 -0
  154. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -0
  155. package/dist/client/resources/McpConnectionSuggestion.js +115 -0
  156. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -0
  157. package/dist/client/resources/McpIntegrationDialog.d.ts +2 -1
  158. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  159. package/dist/client/resources/McpIntegrationDialog.js +48 -20
  160. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  161. package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
  162. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  163. package/dist/client/resources/mcp-integration-catalog.js +387 -4
  164. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  165. package/dist/client/resources/mcp-integration-logos.d.ts +2 -0
  166. package/dist/client/resources/mcp-integration-logos.d.ts.map +1 -0
  167. package/dist/client/resources/mcp-integration-logos.js +109 -0
  168. package/dist/client/resources/mcp-integration-logos.js.map +1 -0
  169. package/dist/deploy/build.d.ts.map +1 -1
  170. package/dist/deploy/build.js +23 -0
  171. package/dist/deploy/build.js.map +1 -1
  172. package/dist/extensions/html-shell.d.ts.map +1 -1
  173. package/dist/extensions/html-shell.js +33 -1
  174. package/dist/extensions/html-shell.js.map +1 -1
  175. package/dist/ingestion/office.d.ts.map +1 -1
  176. package/dist/ingestion/office.js +1 -2
  177. package/dist/ingestion/office.js.map +1 -1
  178. package/dist/ingestion/pptx.js +2 -4
  179. package/dist/ingestion/pptx.js.map +1 -1
  180. package/dist/localization/default-messages.d.ts +98 -0
  181. package/dist/localization/default-messages.d.ts.map +1 -1
  182. package/dist/localization/default-messages.js +99 -1
  183. package/dist/localization/default-messages.js.map +1 -1
  184. package/dist/mcp/actions/call-mcp-tool.js +1 -0
  185. package/dist/mcp/actions/call-mcp-tool.js.map +1 -1
  186. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  187. package/dist/mcp/builtin-tools.js +87 -6
  188. package/dist/mcp/builtin-tools.js.map +1 -1
  189. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  190. package/dist/server/index.d.ts +1 -0
  191. package/dist/server/index.d.ts.map +1 -1
  192. package/dist/server/index.js +1 -0
  193. package/dist/server/index.js.map +1 -1
  194. package/dist/server/org-admin.d.ts +3 -0
  195. package/dist/server/org-admin.d.ts.map +1 -0
  196. package/dist/server/org-admin.js +24 -0
  197. package/dist/server/org-admin.js.map +1 -0
  198. package/dist/server/transcribe-voice.d.ts +1 -1
  199. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +9 -0
  200. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +31 -0
  201. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +9 -0
  202. package/docs/content/external-agents.mdx +8 -0
  203. package/docs/content/integrations.mdx +597 -0
  204. package/docs/content/locales/ar-SA/external-agents.mdx +7 -0
  205. package/docs/content/locales/ar-SA/mcp-clients.mdx +19 -1
  206. package/docs/content/locales/ar-SA/mcp-protocol.mdx +7 -0
  207. package/docs/content/locales/de-DE/external-agents.mdx +9 -0
  208. package/docs/content/locales/de-DE/mcp-clients.mdx +22 -1
  209. package/docs/content/locales/de-DE/mcp-protocol.mdx +9 -0
  210. package/docs/content/locales/es-ES/external-agents.mdx +9 -0
  211. package/docs/content/locales/es-ES/mcp-clients.mdx +23 -1
  212. package/docs/content/locales/es-ES/mcp-protocol.mdx +9 -0
  213. package/docs/content/locales/fr-FR/external-agents.mdx +9 -0
  214. package/docs/content/locales/fr-FR/mcp-clients.mdx +23 -1
  215. package/docs/content/locales/fr-FR/mcp-protocol.mdx +9 -0
  216. package/docs/content/locales/hi-IN/external-agents.mdx +8 -0
  217. package/docs/content/locales/hi-IN/mcp-clients.mdx +22 -1
  218. package/docs/content/locales/hi-IN/mcp-protocol.mdx +8 -0
  219. package/docs/content/locales/ja-JP/external-agents.mdx +8 -0
  220. package/docs/content/locales/ja-JP/mcp-clients.mdx +21 -1
  221. package/docs/content/locales/ja-JP/mcp-protocol.mdx +8 -0
  222. package/docs/content/locales/ko-KR/external-agents.mdx +8 -0
  223. package/docs/content/locales/ko-KR/mcp-clients.mdx +20 -1
  224. package/docs/content/locales/ko-KR/mcp-protocol.mdx +8 -0
  225. package/docs/content/locales/pt-BR/external-agents.mdx +9 -0
  226. package/docs/content/locales/pt-BR/mcp-clients.mdx +22 -1
  227. package/docs/content/locales/pt-BR/mcp-protocol.mdx +9 -0
  228. package/docs/content/locales/zh-CN/external-agents.mdx +6 -0
  229. package/docs/content/locales/zh-CN/mcp-clients.mdx +17 -1
  230. package/docs/content/locales/zh-CN/mcp-protocol.mdx +6 -0
  231. package/docs/content/locales/zh-TW/external-agents.mdx +6 -0
  232. package/docs/content/locales/zh-TW/mcp-clients.mdx +17 -1
  233. package/docs/content/locales/zh-TW/mcp-protocol.mdx +6 -0
  234. package/docs/content/mcp-clients.mdx +51 -1
  235. package/docs/content/mcp-protocol.mdx +8 -0
  236. package/docs/content/organizations-teams-permissions.mdx +413 -0
  237. package/package.json +1 -1
  238. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +9 -0
  239. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +31 -0
  240. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +9 -0
@@ -110,6 +110,28 @@ export function canSubmitComposerContent(options: {
110
110
  );
111
111
  }
112
112
 
113
+ const VOICE_TERMINAL_PUNCTUATION = /[.!?…。!?:;](?:["'”’»)\]}]*)$/;
114
+
115
+ export function formatVoiceTranscriptForComposer(text: string): string {
116
+ const trimmed = text.trim();
117
+ if (!trimmed) return "";
118
+ return `${trimmed}${VOICE_TERMINAL_PUNCTUATION.test(trimmed) ? "" : "."} `;
119
+ }
120
+
121
+ export function canRemoveVoicePreview(options: {
122
+ documentSize: number;
123
+ anchor: number;
124
+ previewText: string;
125
+ currentText: string;
126
+ }): boolean {
127
+ if (!options.previewText) return false;
128
+ if (options.anchor < 1) return false;
129
+ if (options.anchor + options.previewText.length > options.documentSize) {
130
+ return false;
131
+ }
132
+ return options.currentText === options.previewText;
133
+ }
134
+
113
135
  export function resolveComposerPrimaryAction(options: {
114
136
  canSubmit: boolean;
115
137
  hasStopButton: boolean;
@@ -2082,15 +2104,16 @@ export function TiptapComposer({
2082
2104
  (text: string) => {
2083
2105
  const ed = editor;
2084
2106
  if (!isComposerEditorUsable(ed)) return;
2107
+ const formattedText = formatVoiceTranscriptForComposer(text);
2085
2108
 
2086
2109
  const anchor = voiceAnchorRef.current;
2087
2110
  if (anchor != null) {
2088
2111
  const prevLen = prevVoiceInsertRef.current.length;
2089
- if (text) {
2112
+ if (formattedText) {
2090
2113
  ed.chain()
2091
2114
  .focus()
2092
2115
  .deleteRange({ from: anchor, to: anchor + prevLen })
2093
- .insertContentAt(anchor, text + " ")
2116
+ .insertContentAt(anchor, formattedText)
2094
2117
  .run();
2095
2118
  } else if (prevLen > 0) {
2096
2119
  ed.chain()
@@ -2099,14 +2122,14 @@ export function TiptapComposer({
2099
2122
  }
2100
2123
  voiceAnchorRef.current = null;
2101
2124
  prevVoiceInsertRef.current = "";
2102
- } else if (text) {
2125
+ } else if (formattedText) {
2103
2126
  const { from } = ed.state.selection;
2104
2127
  const prevChar =
2105
2128
  from > 1 ? ed.state.doc.textBetween(from - 1, from) : "";
2106
2129
  const needsLead = prevChar && !/\s/.test(prevChar);
2107
2130
  ed.chain()
2108
2131
  .focus()
2109
- .insertContent((needsLead ? " " : "") + text + " ")
2132
+ .insertContent((needsLead ? " " : "") + formattedText)
2110
2133
  .run();
2111
2134
  }
2112
2135
  },
@@ -2172,10 +2195,24 @@ export function TiptapComposer({
2172
2195
  const anchor = voiceAnchorRef.current;
2173
2196
  const prevLen = prevVoiceInsertRef.current.length;
2174
2197
  if (isComposerEditorUsable(editor) && prevLen > 0) {
2175
- editor
2176
- .chain()
2177
- .deleteRange({ from: anchor, to: anchor + prevLen })
2178
- .run();
2198
+ const documentSize = editor.state.doc.content.size;
2199
+ const currentText =
2200
+ anchor >= 1 && anchor + prevLen <= documentSize
2201
+ ? editor.state.doc.textBetween(anchor, anchor + prevLen, "", "\n")
2202
+ : "";
2203
+ if (
2204
+ canRemoveVoicePreview({
2205
+ documentSize,
2206
+ anchor,
2207
+ previewText: prevVoiceInsertRef.current,
2208
+ currentText,
2209
+ })
2210
+ ) {
2211
+ editor
2212
+ .chain()
2213
+ .deleteRange({ from: anchor, to: anchor + prevLen })
2214
+ .run();
2215
+ }
2179
2216
  }
2180
2217
  voiceAnchorRef.current = null;
2181
2218
  prevVoiceInsertRef.current = "";
@@ -14,6 +14,7 @@ import {
14
14
  } from "@tabler/icons-react";
15
15
  import React, { useState, useCallback, useEffect } from "react";
16
16
 
17
+ import { AgentAskPopover } from "../AgentAskPopover.js";
17
18
  import { agentNativePath } from "../api-path.js";
18
19
  import {
19
20
  Tooltip,
@@ -511,6 +512,14 @@ export function IntegrationsPanel() {
511
512
  <div className="text-[10px] font-medium text-muted-foreground mb-1.5">
512
513
  {t("integrations.addChatIntegration")}
513
514
  </div>
515
+ <AgentAskPopover
516
+ label={t("integrations.addSomething")}
517
+ title={t("integrations.addSomethingTitle")}
518
+ placeholder={t("integrations.addSomethingPlaceholder")}
519
+ prompt=""
520
+ context="The user wants to add a chat or workspace integration that is not in the current directory. Research the provider's official OAuth or MCP setup, explain any app registration or allowlist requirements, and add a reusable integration preset when it is supported. Never ask the user to paste credentials into a prompt."
521
+ className="mb-2 h-7 w-full justify-center border-dashed px-2 text-[10px]"
522
+ />
514
523
  <AddIntegrationPicker
515
524
  connectedIds={connectedIds}
516
525
  onSelect={(p) => {
@@ -533,17 +542,27 @@ export function IntegrationsPanel() {
533
542
  {t("integrations.chatIntegrationsDescription")}
534
543
  </div>
535
544
  </div>
536
- <Tooltip>
537
- <TooltipTrigger asChild>
538
- <button
539
- onClick={() => setShowPicker(true)}
540
- className="flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50"
541
- >
542
- <IconPlus size={12} />
543
- </button>
544
- </TooltipTrigger>
545
- <TooltipContent>{t("integrations.addIntegration")}</TooltipContent>
546
- </Tooltip>
545
+ <div className="flex items-center gap-1">
546
+ <Tooltip>
547
+ <TooltipTrigger asChild>
548
+ <button
549
+ onClick={() => setShowPicker(true)}
550
+ className="flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:bg-accent/50 hover:text-foreground"
551
+ >
552
+ <IconPlus size={12} />
553
+ </button>
554
+ </TooltipTrigger>
555
+ <TooltipContent>{t("integrations.addIntegration")}</TooltipContent>
556
+ </Tooltip>
557
+ <AgentAskPopover
558
+ label={t("integrations.addSomething")}
559
+ title={t("integrations.addSomethingTitle")}
560
+ placeholder={t("integrations.addSomethingPlaceholder")}
561
+ prompt=""
562
+ context="The user wants to add a chat or workspace integration that is not in the current directory. Research the provider's official OAuth or MCP setup, explain any app registration or allowlist requirements, and add a reusable integration preset when it is supported. Never ask the user to paste credentials into a prompt."
563
+ className="h-7 px-2 text-[10px]"
564
+ />
565
+ </div>
547
566
  </div>
548
567
 
549
568
  {loading ? (
@@ -0,0 +1,240 @@
1
+ import {
2
+ IconArrowUpRight,
3
+ IconLoader2,
4
+ IconPlugConnected,
5
+ IconX,
6
+ } from "@tabler/icons-react";
7
+ import { useEffect, useMemo, useState } from "react";
8
+
9
+ import { agentNativePath } from "../api-path.js";
10
+ import { useT } from "../i18n.js";
11
+ import {
12
+ buildMcpOAuthStartUrl,
13
+ findMcpIntegrationForText,
14
+ getDefaultMcpIntegrations,
15
+ isMcpConnectionFailureText,
16
+ type DefaultMcpIntegration,
17
+ } from "./mcp-integration-catalog.js";
18
+ import { McpIntegrationDialog } from "./McpIntegrationDialog.js";
19
+ import {
20
+ useCreateMcpServer,
21
+ useMcpServers,
22
+ type McpServer,
23
+ } from "./use-mcp-servers.js";
24
+
25
+ export type McpConnectionSuggestionVariant = "composer" | "response";
26
+
27
+ interface McpConnectionSuggestionProps {
28
+ text: string;
29
+ contextText?: string;
30
+ variant?: McpConnectionSuggestionVariant;
31
+ }
32
+
33
+ function compareUrl(value: string): string {
34
+ try {
35
+ const url = new URL(value.trim());
36
+ url.hash = "";
37
+ return url.toString().replace(/\/+$/, "");
38
+ } catch {
39
+ return value.trim().replace(/\/+$/, "");
40
+ }
41
+ }
42
+
43
+ function isConnected(
44
+ integration: DefaultMcpIntegration,
45
+ servers: McpServer[],
46
+ ): boolean {
47
+ const targetUrl = compareUrl(integration.url);
48
+ return servers.some(
49
+ (server) =>
50
+ server.status.state === "connected" &&
51
+ compareUrl(server.url) === targetUrl,
52
+ );
53
+ }
54
+
55
+ function returnUrl(): string {
56
+ if (typeof window === "undefined") return "/";
57
+ return (
58
+ window.location.pathname + window.location.search + window.location.hash
59
+ );
60
+ }
61
+
62
+ function canStartOAuth(integration: DefaultMcpIntegration): boolean {
63
+ return (
64
+ integration.authMode === "oauth" &&
65
+ integration.connectionMode === "oauth" &&
66
+ integration.availability === "ready"
67
+ );
68
+ }
69
+
70
+ export function McpConnectionSuggestion({
71
+ text,
72
+ contextText = "",
73
+ variant = "composer",
74
+ }: McpConnectionSuggestionProps) {
75
+ const t = useT();
76
+ const mcpServersQuery = useMcpServers();
77
+ const createMcpServer = useCreateMcpServer();
78
+ const [dialogOpen, setDialogOpen] = useState(false);
79
+ const [dismissedId, setDismissedId] = useState<string | null>(null);
80
+ const [connecting, setConnecting] = useState(false);
81
+ const [error, setError] = useState<string | null>(null);
82
+ const integrations = useMemo(() => getDefaultMcpIntegrations(), []);
83
+ const textIntegration = useMemo(
84
+ () => findMcpIntegrationForText(text, integrations),
85
+ [integrations, text],
86
+ );
87
+ const contextIntegration = useMemo(
88
+ () =>
89
+ contextText ? findMcpIntegrationForText(contextText, integrations) : null,
90
+ [contextText, integrations],
91
+ );
92
+ const integration =
93
+ variant === "response" &&
94
+ textIntegration &&
95
+ contextIntegration &&
96
+ textIntegration.id !== contextIntegration.id
97
+ ? null
98
+ : (textIntegration ?? contextIntegration);
99
+ const servers = useMemo(
100
+ () => [
101
+ ...(mcpServersQuery.data?.user ?? []),
102
+ ...(mcpServersQuery.data?.org ?? []),
103
+ ],
104
+ [mcpServersQuery.data],
105
+ );
106
+ const connected = integration ? isConnected(integration, servers) : false;
107
+ const shouldSuggest =
108
+ mcpServersQuery.isSuccess &&
109
+ integration &&
110
+ !connected &&
111
+ dismissedId !== integration.id &&
112
+ (variant === "composer" || isMcpConnectionFailureText(text));
113
+
114
+ useEffect(() => {
115
+ setError(null);
116
+ setConnecting(false);
117
+ }, [integration?.id, variant]);
118
+
119
+ if (!shouldSuggest) return null;
120
+
121
+ const connect = async () => {
122
+ if (!integration || connecting) return;
123
+ setError(null);
124
+
125
+ if (canStartOAuth(integration)) {
126
+ window.location.assign(
127
+ agentNativePath(
128
+ buildMcpOAuthStartUrl({
129
+ name: integration.name,
130
+ url: integration.url,
131
+ description: integration.description,
132
+ scope: "user",
133
+ returnUrl: returnUrl(),
134
+ }),
135
+ ),
136
+ );
137
+ return;
138
+ }
139
+
140
+ if (
141
+ integration.authMode === "none" &&
142
+ integration.connectionMode === "direct"
143
+ ) {
144
+ setConnecting(true);
145
+ try {
146
+ await createMcpServer.mutateAsync({
147
+ scope: "user",
148
+ name: integration.name,
149
+ url: integration.url,
150
+ description: integration.description,
151
+ });
152
+ setDismissedId(integration.id);
153
+ } catch (cause) {
154
+ setError(
155
+ cause instanceof Error ? cause.message : t("mcpIntegrations.failed"),
156
+ );
157
+ } finally {
158
+ setConnecting(false);
159
+ }
160
+ return;
161
+ }
162
+
163
+ setDialogOpen(true);
164
+ };
165
+
166
+ const actionLabel = canStartOAuth(integration)
167
+ ? t("mcpIntegrations.connectWithOAuth")
168
+ : integration.availability === "client-restricted" ||
169
+ integration.availability === "provider-setup"
170
+ ? t("mcpIntegrations.viewSetup")
171
+ : t("mcpIntegrations.connect");
172
+
173
+ return (
174
+ <>
175
+ <div
176
+ className={
177
+ variant === "response"
178
+ ? "mt-3 flex max-w-[520px] items-center gap-2 rounded-lg border border-primary/20 bg-primary/5 px-3 py-2 text-[12px]"
179
+ : "mx-auto mb-2 flex w-[min(100%,680px)] items-center gap-2 rounded-lg border border-primary/20 bg-primary/5 px-3 py-2 text-[12px]"
180
+ }
181
+ data-mcp-connection-suggestion={integration.id}
182
+ >
183
+ <div className="relative flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-md border border-border bg-background text-[10px] font-semibold text-muted-foreground">
184
+ <span aria-hidden="true">{integration.name.slice(0, 1)}</span>
185
+ {integration.logoUrl && (
186
+ <img
187
+ src={integration.logoUrl}
188
+ alt=""
189
+ className="absolute h-5 w-5 object-contain"
190
+ onError={(event) => {
191
+ event.currentTarget.hidden = true;
192
+ }}
193
+ />
194
+ )}
195
+ </div>
196
+ <span className="min-w-0 flex-1 leading-snug text-foreground">
197
+ {t("mcpIntegrations.connectSuggestion", { name: integration.name })}
198
+ </span>
199
+ <button
200
+ type="button"
201
+ onClick={() => void connect()}
202
+ disabled={connecting}
203
+ className="inline-flex shrink-0 items-center gap-1 rounded-md bg-primary px-2.5 py-1.5 text-[11px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-60"
204
+ >
205
+ {connecting && <IconLoader2 className="h-3 w-3 animate-spin" />}
206
+ {!connecting && canStartOAuth(integration) && (
207
+ <IconPlugConnected className="h-3 w-3" />
208
+ )}
209
+ {actionLabel}
210
+ {!connecting && !canStartOAuth(integration) && (
211
+ <IconArrowUpRight className="h-3 w-3" />
212
+ )}
213
+ </button>
214
+ <button
215
+ type="button"
216
+ onClick={() => setDismissedId(integration.id)}
217
+ className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-background hover:text-foreground"
218
+ aria-label={t("mcpIntegrations.dismissSuggestion")}
219
+ >
220
+ <IconX className="h-3.5 w-3.5" />
221
+ </button>
222
+ </div>
223
+ {error && (
224
+ <div className="mx-auto mb-2 w-[min(100%,680px)] text-[11px] text-destructive">
225
+ {error}
226
+ </div>
227
+ )}
228
+ <McpIntegrationDialog
229
+ open={dialogOpen}
230
+ onOpenChange={setDialogOpen}
231
+ initialIntegrationId={integration.id}
232
+ defaultScope="user"
233
+ canCreateOrgMcp={false}
234
+ hasOrg={Boolean(mcpServersQuery.data?.orgId)}
235
+ onCreateMcpServer={(args) => createMcpServer.mutateAsync(args)}
236
+ onCreated={() => setDismissedId(integration.id)}
237
+ />
238
+ </>
239
+ );
240
+ }
@@ -7,6 +7,7 @@ import {
7
7
  } from "@tabler/icons-react";
8
8
  import { useEffect, useMemo, useRef, useState } from "react";
9
9
 
10
+ import { AgentAskPopover } from "../AgentAskPopover.js";
10
11
  import { agentNativePath } from "../api-path.js";
11
12
  import {
12
13
  Dialog,
@@ -45,6 +46,7 @@ type DialogMode = "catalog" | "form";
45
46
  interface McpIntegrationDialogProps {
46
47
  open: boolean;
47
48
  onOpenChange: (open: boolean) => void;
49
+ initialIntegrationId?: string | null;
48
50
  defaultScope: McpServerScope;
49
51
  canCreateOrgMcp: boolean;
50
52
  hasOrg: boolean;
@@ -85,6 +87,7 @@ function compareUrl(value: string): string {
85
87
  export function McpIntegrationDialog({
86
88
  open,
87
89
  onOpenChange,
90
+ initialIntegrationId = null,
88
91
  defaultScope,
89
92
  canCreateOrgMcp,
90
93
  hasOrg,
@@ -138,21 +141,41 @@ export function McpIntegrationDialog({
138
141
  [defaultIntegrations, query],
139
142
  );
140
143
 
144
+ const selectedRequiresSetup = Boolean(
145
+ selected &&
146
+ (selected.connectionMode === "manual" ||
147
+ selected.availability === "provider-setup" ||
148
+ selected.availability === "client-restricted"),
149
+ );
150
+
141
151
  useEffect(() => {
142
152
  if (!open) return;
143
- setMode(showCatalog ? "catalog" : "form");
153
+ const initialIntegration = initialIntegrationId
154
+ ? defaultIntegrations.find(
155
+ (integration) => integration.id === initialIntegrationId,
156
+ )
157
+ : null;
158
+ const initialDefaults =
159
+ createMcpIntegrationFormDefaults(initialIntegration);
160
+ setMode(initialIntegration || !showCatalog ? "form" : "catalog");
144
161
  setQuery("");
145
- setSelected(null);
162
+ setSelected(initialIntegration ?? null);
146
163
  setScope(safeDefaultScope);
147
- setName("");
148
- setUrl("");
149
- setDescription("");
150
- setHeadersText("");
164
+ setName(initialDefaults.name);
165
+ setUrl(initialDefaults.url);
166
+ setDescription(initialDefaults.description);
167
+ setHeadersText(initialDefaults.headersText);
151
168
  setBusy(false);
152
169
  setQuickBusyId(null);
153
170
  setError(null);
154
171
  setTestResult(null);
155
- }, [open, safeDefaultScope, showCatalog]);
172
+ }, [
173
+ defaultIntegrations,
174
+ initialIntegrationId,
175
+ open,
176
+ safeDefaultScope,
177
+ showCatalog,
178
+ ]);
156
179
 
157
180
  useEffect(() => {
158
181
  if (open && mode === "form") {
@@ -268,6 +291,12 @@ export function McpIntegrationDialog({
268
291
  };
269
292
 
270
293
  const quickConnect = (integration: DefaultMcpIntegration) => {
294
+ if (
295
+ integration.connectionMode === "manual" ||
296
+ integration.availability === "provider-setup"
297
+ ) {
298
+ return;
299
+ }
271
300
  if (integration.authMode === "oauth") {
272
301
  connectWithOAuth(integration);
273
302
  return;
@@ -399,6 +428,14 @@ export function McpIntegrationDialog({
399
428
  >
400
429
  {t("mcpIntegrations.addYourOwn")}
401
430
  </button>
431
+ <AgentAskPopover
432
+ label={t("mcpIntegrations.addSomething")}
433
+ title={t("mcpIntegrations.addSomethingTitle")}
434
+ placeholder={t("mcpIntegrations.addSomethingPlaceholder")}
435
+ prompt=""
436
+ context="The user wants to add an MCP or provider integration that is not in the current directory. Research the provider's official remote MCP endpoint and OAuth, client-registration, or allowlist requirements. Prefer Streamable HTTP endpoints over legacy SSE, reuse an existing provider OAuth connector when appropriate, and never ask the user to paste credentials into a prompt. If this should become a reusable preset, update the integration catalog, official docs link, bundled logo, localization, and tests."
437
+ className="h-9 whitespace-nowrap border-dashed px-3 text-[12px] font-medium"
438
+ />
402
439
  </div>
403
440
  <div className="min-h-0 flex-1 overflow-y-auto px-7 pb-7">
404
441
  {error && (
@@ -412,42 +449,101 @@ export function McpIntegrationDialog({
412
449
  compareUrl(integration.url),
413
450
  );
414
451
  const requiresHeaders = integration.authMode === "headers";
452
+ const setupOnly =
453
+ integration.connectionMode === "manual" ||
454
+ integration.availability === "provider-setup" ||
455
+ integration.availability === "client-restricted";
415
456
  return (
416
457
  <article
417
458
  key={integration.id}
418
459
  className="flex min-h-[128px] flex-col rounded-md border border-border bg-card p-4 transition-colors hover:border-border/80 hover:bg-accent/20"
419
460
  >
420
- <h3 className="truncate text-[13px] font-semibold text-foreground">
421
- {integration.name}
422
- </h3>
461
+ <div className="flex items-center gap-3">
462
+ <div className="relative flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md border border-border bg-background text-[11px] font-semibold text-muted-foreground">
463
+ <span aria-hidden="true">
464
+ {integration.name.slice(0, 1)}
465
+ </span>
466
+ <img
467
+ src={integration.logoUrl}
468
+ alt=""
469
+ loading="lazy"
470
+ decoding="async"
471
+ className="absolute inset-1 h-6 w-6 object-contain"
472
+ onError={(event) => {
473
+ event.currentTarget.hidden = true;
474
+ }}
475
+ />
476
+ </div>
477
+ <div className="min-w-0">
478
+ <h3 className="truncate text-[13px] font-semibold text-foreground">
479
+ {integration.name}
480
+ </h3>
481
+ {integration.availability !== "ready" && (
482
+ <span className="mt-0.5 inline-flex rounded-full border border-amber-500/20 bg-amber-500/5 px-1.5 py-0.5 text-[9px] font-medium text-amber-700 dark:text-amber-300">
483
+ {integration.availability === "beta"
484
+ ? t("mcpIntegrations.status.beta")
485
+ : integration.availability ===
486
+ "client-restricted"
487
+ ? t("mcpIntegrations.status.clientRestricted")
488
+ : t("mcpIntegrations.status.setupRequired")}
489
+ </span>
490
+ )}
491
+ <span className="ms-1 mt-0.5 inline-flex rounded-full border border-border/70 bg-muted/40 px-1.5 py-0.5 text-[9px] font-medium text-muted-foreground">
492
+ {integration.verification === "verified"
493
+ ? t("mcpIntegrations.status.verified")
494
+ : integration.verification === "restricted"
495
+ ? t("mcpIntegrations.status.restricted")
496
+ : t("mcpIntegrations.status.preflightOnly")}
497
+ </span>
498
+ </div>
499
+ </div>
423
500
  <p className="mt-1 line-clamp-2 flex-1 text-[12px] leading-relaxed text-muted-foreground">
424
501
  {t(integration.descriptionKey)}
425
502
  </p>
503
+ {integration.setupNoteKey && (
504
+ <p className="mt-2 line-clamp-3 text-[10px] leading-relaxed text-amber-700 dark:text-amber-300">
505
+ {t(integration.setupNoteKey)}
506
+ </p>
507
+ )}
426
508
  <div className="mt-3 flex items-center gap-2">
427
- <button
428
- type="button"
429
- onClick={() => quickConnect(integration)}
430
- disabled={busy || connected}
431
- className={cn(
432
- "inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md px-2.5 text-[12px] font-medium",
433
- connected
434
- ? "border border-border bg-muted text-muted-foreground"
435
- : "bg-primary text-primary-foreground hover:bg-primary/90",
436
- (busy || connected) &&
437
- "cursor-not-allowed opacity-70",
438
- )}
439
- >
440
- {quickBusyId === integration.id ? (
441
- <IconLoader2 className="h-3.5 w-3.5 animate-spin" />
442
- ) : null}
443
- {connected
444
- ? t("mcpIntegrations.connected")
445
- : integration.authMode === "oauth"
509
+ {connected ? (
510
+ <button
511
+ type="button"
512
+ disabled
513
+ className="inline-flex h-8 flex-1 cursor-not-allowed items-center justify-center gap-1.5 rounded-md border border-border bg-muted px-2.5 text-[12px] font-medium text-muted-foreground opacity-70"
514
+ >
515
+ {t("mcpIntegrations.connected")}
516
+ </button>
517
+ ) : setupOnly ? (
518
+ <a
519
+ href={integration.docsUrl}
520
+ target="_blank"
521
+ rel="noopener noreferrer"
522
+ className="inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-[12px] font-medium text-foreground hover:bg-accent"
523
+ >
524
+ {t("mcpIntegrations.viewSetup")}
525
+ <IconExternalLink className="h-3.5 w-3.5" />
526
+ </a>
527
+ ) : (
528
+ <button
529
+ type="button"
530
+ onClick={() => quickConnect(integration)}
531
+ disabled={busy}
532
+ className={cn(
533
+ "inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md bg-primary px-2.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90",
534
+ busy && "cursor-not-allowed opacity-70",
535
+ )}
536
+ >
537
+ {quickBusyId === integration.id ? (
538
+ <IconLoader2 className="h-3.5 w-3.5 animate-spin" />
539
+ ) : null}
540
+ {integration.authMode === "oauth"
446
541
  ? t("mcpIntegrations.connectWithOAuth")
447
542
  : requiresHeaders
448
543
  ? t("mcpIntegrations.configure")
449
544
  : t("mcpIntegrations.connect")}
450
- </button>
545
+ </button>
546
+ )}
451
547
  {integration.docsUrl && (
452
548
  <Tooltip>
453
549
  <TooltipTrigger asChild>
@@ -638,7 +734,16 @@ export function McpIntegrationDialog({
638
734
  >
639
735
  {t("mcpIntegrations.test")}
640
736
  </button>
641
- {!selected && (
737
+ {selected?.authMode === "oauth" && !selectedRequiresSetup ? (
738
+ <button
739
+ type="button"
740
+ onClick={() => connectWithOAuth(selected)}
741
+ disabled={!name.trim() || !url.trim() || busy}
742
+ className="rounded-md border border-border bg-background px-3 py-1.5 text-[12px] font-medium text-foreground hover:bg-accent disabled:pointer-events-none disabled:opacity-40"
743
+ >
744
+ {t("mcpIntegrations.connectWithOAuth")}
745
+ </button>
746
+ ) : !selected ? (
642
747
  <button
643
748
  type="button"
644
749
  onClick={connectCustomWithOAuth}
@@ -647,16 +752,30 @@ export function McpIntegrationDialog({
647
752
  >
648
753
  {t("mcpIntegrations.connectWithOAuth")}
649
754
  </button>
755
+ ) : null}
756
+ {selectedRequiresSetup ? (
757
+ selected?.docsUrl ? (
758
+ <a
759
+ href={selected.docsUrl}
760
+ target="_blank"
761
+ rel="noopener noreferrer"
762
+ className="inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90"
763
+ >
764
+ {t("mcpIntegrations.viewSetup")}
765
+ <IconExternalLink className="h-3 w-3" />
766
+ </a>
767
+ ) : null
768
+ ) : (
769
+ <button
770
+ type="button"
771
+ onClick={submitForm}
772
+ disabled={!name.trim() || !url.trim() || busy}
773
+ className="inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40"
774
+ >
775
+ {busy && <IconLoader2 className="h-3.5 w-3.5 animate-spin" />}
776
+ {t("mcpIntegrations.connect")}
777
+ </button>
650
778
  )}
651
- <button
652
- type="button"
653
- onClick={submitForm}
654
- disabled={!name.trim() || !url.trim() || busy}
655
- className="inline-flex min-w-[92px] items-center justify-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-40"
656
- >
657
- {busy && <IconLoader2 className="h-3.5 w-3.5 animate-spin" />}
658
- {t("mcpIntegrations.connect")}
659
- </button>
660
779
  </div>
661
780
  </>
662
781
  )}