@devicai/ui 0.47.1 → 0.50.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 (134) hide show
  1. package/dist/cjs/api/assistantInfo.js.map +1 -1
  2. package/dist/cjs/api/client.js +56 -0
  3. package/dist/cjs/api/client.js.map +1 -1
  4. package/dist/cjs/api/types.js.map +1 -1
  5. package/dist/cjs/components/AICommandBar/AICommandBar.js.map +1 -1
  6. package/dist/cjs/components/AICommandBar/useAICommandBar.js.map +1 -1
  7. package/dist/cjs/components/AIElementWrapper/AIElementWrapper.js.map +1 -1
  8. package/dist/cjs/components/AIElementWrapper/activeWrapperRegistry.js.map +1 -1
  9. package/dist/cjs/components/AIElementWrapper/useAIElementWrapper.js.map +1 -1
  10. package/dist/cjs/components/AIGenerationButton/AIGenerationButton.js.map +1 -1
  11. package/dist/cjs/components/AIGenerationButton/useAIGenerationButton.js.map +1 -1
  12. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +50 -4
  13. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  14. package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
  15. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  16. package/dist/cjs/components/ChatDrawer/ConversationSelector.js.map +1 -1
  17. package/dist/cjs/components/ChatDrawer/ErrorBoundary.js.map +1 -1
  18. package/dist/cjs/components/ChatDrawer/HandoffSubagentWidget.js +2 -1
  19. package/dist/cjs/components/ChatDrawer/HandoffSubagentWidget.js.map +1 -1
  20. package/dist/cjs/components/ChatDrawer/LimitBanner.js.map +1 -1
  21. package/dist/cjs/components/ChatDrawer/RecalledMemoriesWidget.js.map +1 -1
  22. package/dist/cjs/components/ChatDrawer/ReferenceChip.js.map +1 -1
  23. package/dist/cjs/components/ChatDrawer/ToolTimeline.js.map +1 -1
  24. package/dist/cjs/components/ChatDrawer/UsageBar.js.map +1 -1
  25. package/dist/cjs/components/ChatDrawer/pastedText.js.map +1 -1
  26. package/dist/cjs/components/CoreMemoryModal/CoreMemoryModal.js.map +1 -1
  27. package/dist/cjs/components/Feedback/FeedbackModal.js.map +1 -1
  28. package/dist/cjs/components/Feedback/MessageActions.js.map +1 -1
  29. package/dist/cjs/components/IntegrationsModal/ConnectFieldsForm.js.map +1 -1
  30. package/dist/cjs/components/IntegrationsModal/IntegrationLogo.js.map +1 -1
  31. package/dist/cjs/components/IntegrationsModal/IntegrationsHint.js.map +1 -1
  32. package/dist/cjs/components/IntegrationsModal/IntegrationsLauncher.js +22 -10
  33. package/dist/cjs/components/IntegrationsModal/IntegrationsLauncher.js.map +1 -1
  34. package/dist/cjs/components/IntegrationsModal/IntegrationsModal.js +36 -5
  35. package/dist/cjs/components/IntegrationsModal/IntegrationsModal.js.map +1 -1
  36. package/dist/cjs/components/IntegrationsModal/IntegrationsToggle.js +43 -18
  37. package/dist/cjs/components/IntegrationsModal/IntegrationsToggle.js.map +1 -1
  38. package/dist/cjs/components/IntegrationsModal/McpConnectForm.js +79 -0
  39. package/dist/cjs/components/IntegrationsModal/McpConnectForm.js.map +1 -0
  40. package/dist/cjs/components/IntegrationsModal/McpServersSection.js +234 -0
  41. package/dist/cjs/components/IntegrationsModal/McpServersSection.js.map +1 -0
  42. package/dist/cjs/components/IntegrationsModal/useIntegrations.js.map +1 -1
  43. package/dist/cjs/components/IntegrationsModal/useTenantMcp.js +107 -0
  44. package/dist/cjs/components/IntegrationsModal/useTenantMcp.js.map +1 -0
  45. package/dist/cjs/components/ThreadStateTag/ThreadStateTag.js.map +1 -1
  46. package/dist/cjs/components/theme.js.map +1 -1
  47. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  48. package/dist/cjs/hooks/useModelInterface.js.map +1 -1
  49. package/dist/cjs/hooks/usePolling.js.map +1 -1
  50. package/dist/cjs/hooks/useSpeechRecording.js.map +1 -1
  51. package/dist/cjs/index.js +6 -0
  52. package/dist/cjs/index.js.map +1 -1
  53. package/dist/cjs/provider/DevicContext.js.map +1 -1
  54. package/dist/cjs/provider/DevicProvider.js.map +1 -1
  55. package/dist/cjs/provider/sharedSession.js.map +1 -1
  56. package/dist/cjs/styles.css +1 -1
  57. package/dist/cjs/utils/avatar.js +60 -0
  58. package/dist/cjs/utils/avatar.js.map +1 -0
  59. package/dist/cjs/utils/index.js.map +1 -1
  60. package/dist/cjs/utils/logger.js.map +1 -1
  61. package/dist/cjs/utils/toolGroups.js.map +1 -1
  62. package/dist/esm/api/assistantInfo.js.map +1 -1
  63. package/dist/esm/api/client.d.ts +41 -1
  64. package/dist/esm/api/client.js +56 -0
  65. package/dist/esm/api/client.js.map +1 -1
  66. package/dist/esm/api/types.d.ts +103 -0
  67. package/dist/esm/api/types.js.map +1 -1
  68. package/dist/esm/components/AICommandBar/AICommandBar.js.map +1 -1
  69. package/dist/esm/components/AICommandBar/useAICommandBar.js.map +1 -1
  70. package/dist/esm/components/AIElementWrapper/AIElementWrapper.js.map +1 -1
  71. package/dist/esm/components/AIElementWrapper/activeWrapperRegistry.js.map +1 -1
  72. package/dist/esm/components/AIElementWrapper/useAIElementWrapper.js.map +1 -1
  73. package/dist/esm/components/AIGenerationButton/AIGenerationButton.js.map +1 -1
  74. package/dist/esm/components/AIGenerationButton/useAIGenerationButton.js.map +1 -1
  75. package/dist/esm/components/ChatDrawer/ChatDrawer.js +50 -4
  76. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  77. package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
  78. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  79. package/dist/esm/components/ChatDrawer/ConversationSelector.js.map +1 -1
  80. package/dist/esm/components/ChatDrawer/ErrorBoundary.js.map +1 -1
  81. package/dist/esm/components/ChatDrawer/HandoffSubagentWidget.js +2 -1
  82. package/dist/esm/components/ChatDrawer/HandoffSubagentWidget.js.map +1 -1
  83. package/dist/esm/components/ChatDrawer/LimitBanner.js.map +1 -1
  84. package/dist/esm/components/ChatDrawer/RecalledMemoriesWidget.js.map +1 -1
  85. package/dist/esm/components/ChatDrawer/ReferenceChip.js.map +1 -1
  86. package/dist/esm/components/ChatDrawer/ToolTimeline.js.map +1 -1
  87. package/dist/esm/components/ChatDrawer/UsageBar.js.map +1 -1
  88. package/dist/esm/components/ChatDrawer/pastedText.js.map +1 -1
  89. package/dist/esm/components/CoreMemoryModal/CoreMemoryModal.js.map +1 -1
  90. package/dist/esm/components/Feedback/FeedbackModal.js.map +1 -1
  91. package/dist/esm/components/Feedback/MessageActions.js.map +1 -1
  92. package/dist/esm/components/IntegrationsModal/ConnectFieldsForm.js.map +1 -1
  93. package/dist/esm/components/IntegrationsModal/IntegrationLogo.js.map +1 -1
  94. package/dist/esm/components/IntegrationsModal/IntegrationsHint.js.map +1 -1
  95. package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.d.ts +17 -1
  96. package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.js +22 -10
  97. package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.js.map +1 -1
  98. package/dist/esm/components/IntegrationsModal/IntegrationsModal.d.ts +8 -1
  99. package/dist/esm/components/IntegrationsModal/IntegrationsModal.js +36 -5
  100. package/dist/esm/components/IntegrationsModal/IntegrationsModal.js.map +1 -1
  101. package/dist/esm/components/IntegrationsModal/IntegrationsToggle.d.ts +22 -3
  102. package/dist/esm/components/IntegrationsModal/IntegrationsToggle.js +44 -19
  103. package/dist/esm/components/IntegrationsModal/IntegrationsToggle.js.map +1 -1
  104. package/dist/esm/components/IntegrationsModal/McpConnectForm.d.ts +42 -0
  105. package/dist/esm/components/IntegrationsModal/McpConnectForm.js +77 -0
  106. package/dist/esm/components/IntegrationsModal/McpConnectForm.js.map +1 -0
  107. package/dist/esm/components/IntegrationsModal/McpServersSection.d.ts +25 -0
  108. package/dist/esm/components/IntegrationsModal/McpServersSection.js +232 -0
  109. package/dist/esm/components/IntegrationsModal/McpServersSection.js.map +1 -0
  110. package/dist/esm/components/IntegrationsModal/index.d.ts +6 -0
  111. package/dist/esm/components/IntegrationsModal/useIntegrations.js.map +1 -1
  112. package/dist/esm/components/IntegrationsModal/useTenantMcp.d.ts +40 -0
  113. package/dist/esm/components/IntegrationsModal/useTenantMcp.js +105 -0
  114. package/dist/esm/components/IntegrationsModal/useTenantMcp.js.map +1 -0
  115. package/dist/esm/components/ThreadStateTag/ThreadStateTag.js.map +1 -1
  116. package/dist/esm/components/theme.js.map +1 -1
  117. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  118. package/dist/esm/hooks/useModelInterface.js.map +1 -1
  119. package/dist/esm/hooks/usePolling.js.map +1 -1
  120. package/dist/esm/hooks/useSpeechRecording.js.map +1 -1
  121. package/dist/esm/index.d.ts +3 -3
  122. package/dist/esm/index.js +3 -0
  123. package/dist/esm/index.js.map +1 -1
  124. package/dist/esm/provider/DevicContext.js.map +1 -1
  125. package/dist/esm/provider/DevicProvider.js.map +1 -1
  126. package/dist/esm/provider/sharedSession.js.map +1 -1
  127. package/dist/esm/styles.css +1 -1
  128. package/dist/esm/utils/avatar.d.ts +18 -0
  129. package/dist/esm/utils/avatar.js +58 -0
  130. package/dist/esm/utils/avatar.js.map +1 -0
  131. package/dist/esm/utils/index.js.map +1 -1
  132. package/dist/esm/utils/logger.js.map +1 -1
  133. package/dist/esm/utils/toolGroups.js.map +1 -1
  134. package/package.json +2 -1
@@ -8,6 +8,13 @@ var IntegrationLogo = require('./IntegrationLogo.js');
8
8
  function PlugIcon() {
9
9
  return (jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [jsxRuntime.jsx("path", { d: "M9 2v6" }), jsxRuntime.jsx("path", { d: "M15 2v6" }), jsxRuntime.jsx("path", { d: "M6 8h12v3a6 6 0 0 1-12 0z" }), jsxRuntime.jsx("path", { d: "M12 17v5" })] }));
10
10
  }
11
+ /** A server's own logo when it has one, its initial when it does not. */
12
+ function McpBadge({ server }) {
13
+ if (server.logoUrl) {
14
+ return (jsxRuntime.jsx("img", { className: "devic-int-toggle-mcp-logo", src: server.logoUrl, alt: "", "aria-hidden": "true" }));
15
+ }
16
+ return (jsxRuntime.jsx("span", { className: "devic-int-toggle-mcp-logo devic-int-toggle-mcp-fallback", "aria-hidden": "true", children: server.name.slice(0, 1).toUpperCase() }));
17
+ }
11
18
  /**
12
19
  * Which apps the assistant may reach on the next message.
13
20
  *
@@ -21,10 +28,14 @@ function PlugIcon() {
21
28
  * undo, which is the whole reason this exists next to the composer rather than
22
29
  * inside the apps dialog.
23
30
  */
24
- function IntegrationsToggle({ state, disabled, onChange, onManage, label = "Apps in this chat", dark = false, busy = false, className = "", }) {
31
+ function IntegrationsToggle({ state, mcp, disabled, onChange, onManage, label = "Apps in this chat", dark = false, busy = false, loading = false, className = "", }) {
25
32
  const [open, setOpen] = React.useState(false);
26
33
  const ref = React.useRef(null);
27
34
  const connected = React.useMemo(() => state.integrations.filter((i) => i.connected), [state.integrations]);
35
+ // Connected and usable: one still waiting for its consent screen, or whose
36
+ // last probe failed, contributes no tools, so switching it off would be
37
+ // switching off nothing. It belongs in the panel that can fix it, not here.
38
+ const mcpServers = React.useMemo(() => (mcp?.servers ?? []).filter((s) => s.connection?.status === "active" && !!s.connection.toggleId), [mcp?.servers]);
28
39
  // Closing on an outside click and on Escape, the two ways every popover is
29
40
  // expected to close. Bound only while open so a drawer full of these costs
30
41
  // nothing when they are all shut.
@@ -49,31 +60,45 @@ function IntegrationsToggle({ state, disabled, onChange, onManage, label = "Apps
49
60
  };
50
61
  }, [open]);
51
62
  // An end user with nothing connected has nothing to switch: the button would
52
- // open an empty box. It appears the moment they connect their first app.
53
- if (!state.offered || connected.length === 0)
54
- return null;
63
+ // open an empty box. It appears the moment they connect their first app — or
64
+ // their first MCP server, which is as switchable as an account is.
65
+ const hasApps = state.offered && connected.length > 0;
66
+ const hasMcp = !!mcp?.offered && mcpServers.length > 0;
67
+ if (!hasApps && !hasMcp) {
68
+ if (!loading)
69
+ return null;
70
+ // Still being decided: hold the slot, inert, so the row does not shift.
71
+ return (jsxRuntime.jsx("div", { className: `devic-int-toggle ${className}`.trim(), "data-dark": dark, children: jsxRuntime.jsx("button", { type: "button", className: "devic-input-btn devic-int-toggle-btn devic-int-toggle-btn-loading", disabled: true, "aria-busy": "true", "aria-label": `${label} (loading)`, title: label, children: jsxRuntime.jsx(PlugIcon, {}) }) }));
72
+ }
55
73
  const off = new Set(disabled);
56
- const offCount = connected.filter((i) => off.has(i.app)).length;
57
- const toggle = (integration) => {
74
+ /** Everything still on offer, so a stale entry can never survive a rewrite. */
75
+ const liveIds = [
76
+ ...connected.map((i) => i.app),
77
+ ...mcpServers.map((s) => s.connection.toggleId),
78
+ ];
79
+ const offCount = liveIds.filter((id) => off.has(id)).length;
80
+ const flip = (id) => {
58
81
  const next = new Set(off);
59
- if (next.has(integration.app))
60
- next.delete(integration.app);
82
+ if (next.has(id))
83
+ next.delete(id);
61
84
  else
62
- next.add(integration.app);
63
- // Only apps still on offer are kept: carrying a slug for an app the user
64
- // has since disconnected would silently switch it off again if they ever
65
- // reconnected it.
66
- onChange(connected.filter((i) => next.has(i.app)).map((i) => i.app));
85
+ next.add(id);
86
+ // Only what is still on offer is kept: carrying an entry for an app or a
87
+ // server the user has since disconnected would silently switch it off again
88
+ // if they ever connected it back.
89
+ onChange(liveIds.filter((candidate) => next.has(candidate)));
67
90
  };
68
91
  return (jsxRuntime.jsxs("div", { className: `devic-int-toggle ${className}`.trim(), "data-dark": dark, ref: ref, children: [jsxRuntime.jsxs("button", { type: "button", className: "devic-input-btn devic-int-toggle-btn", onClick: () => setOpen((v) => !v), disabled: busy, title: offCount
69
92
  ? `${label} — ${offCount} switched off`
70
- : label, "aria-label": label, "aria-expanded": open, "aria-haspopup": "dialog", "data-some-off": offCount > 0 || undefined, children: [jsxRuntime.jsx(PlugIcon, {}), offCount > 0 && (jsxRuntime.jsx("span", { className: "devic-int-toggle-badge", "aria-hidden": "true", children: offCount }))] }), open && (jsxRuntime.jsxs("div", { className: "devic-int-toggle-popover", role: "dialog", "aria-label": label, children: [jsxRuntime.jsxs("div", { className: "devic-int-toggle-head", children: [jsxRuntime.jsx("strong", { children: label }), jsxRuntime.jsx("span", { children: "Switched off here, an app sits out your next message. It stays connected." })] }), jsxRuntime.jsx("ul", { className: "devic-int-toggle-list", children: connected.map((integration) => {
71
- const on = !off.has(integration.app);
72
- return (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("label", { className: "devic-int-toggle-row", children: [jsxRuntime.jsxs("span", { className: "devic-int-toggle-app", children: [jsxRuntime.jsx(IntegrationLogo.IntegrationLogo, { integration: integration }), jsxRuntime.jsx("span", { className: "devic-int-toggle-name", children: integration.name })] }), jsxRuntime.jsx("input", { type: "checkbox", className: "devic-int-toggle-switch", checked: on, onChange: () => toggle(integration), "aria-label": `Use ${integration.name} in this chat` })] }) }, integration.app));
73
- }) }), onManage && (jsxRuntime.jsx("button", { type: "button", className: "devic-int-toggle-manage", onClick: () => {
93
+ : label, "aria-label": label, "aria-expanded": open, "aria-haspopup": "dialog", "data-some-off": offCount > 0 || undefined, children: [jsxRuntime.jsx(PlugIcon, {}), offCount > 0 && (jsxRuntime.jsx("span", { className: "devic-int-toggle-badge", "aria-hidden": "true", children: offCount }))] }), open && (jsxRuntime.jsxs("div", { className: "devic-int-toggle-popover", role: "dialog", "aria-label": label, children: [jsxRuntime.jsxs("div", { className: "devic-int-toggle-head", children: [jsxRuntime.jsx("strong", { children: label }), jsxRuntime.jsxs("span", { children: ["Switched off here, ", hasMcp && !hasApps ? "a server" : "an app", " ", "sits out your next message. It stays connected."] })] }), jsxRuntime.jsxs("ul", { className: "devic-int-toggle-list", children: [connected.map((integration) => (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("label", { className: "devic-int-toggle-row", children: [jsxRuntime.jsxs("span", { className: "devic-int-toggle-app", children: [jsxRuntime.jsx(IntegrationLogo.IntegrationLogo, { integration: integration }), jsxRuntime.jsx("span", { className: "devic-int-toggle-name", children: integration.name })] }), jsxRuntime.jsx("input", { type: "checkbox", className: "devic-int-toggle-switch", checked: !off.has(integration.app), onChange: () => flip(integration.app), "aria-label": `Use ${integration.name} in this chat` })] }) }, integration.app))), hasMcp && hasApps && (jsxRuntime.jsx("li", { className: "devic-int-toggle-group", "aria-hidden": "true", children: "MCP servers" })), mcpServers.map((server) => {
94
+ const id = server.connection.toggleId;
95
+ return (jsxRuntime.jsx("li", { children: jsxRuntime.jsxs("label", { className: "devic-int-toggle-row", children: [jsxRuntime.jsxs("span", { className: "devic-int-toggle-app", children: [jsxRuntime.jsx(McpBadge, { server: server }), jsxRuntime.jsx("span", { className: "devic-int-toggle-name", children: server.name })] }), jsxRuntime.jsx("input", { type: "checkbox", className: "devic-int-toggle-switch", checked: !off.has(id), onChange: () => flip(id), "aria-label": `Use ${server.name} in this chat` })] }) }, id));
96
+ })] }), onManage && (jsxRuntime.jsx("button", { type: "button", className: "devic-int-toggle-manage", onClick: () => {
74
97
  setOpen(false);
75
98
  onManage();
76
- }, children: "Manage connected apps" }))] }))] }));
99
+ }, children: hasMcp && !hasApps
100
+ ? "Manage connected servers"
101
+ : "Manage connected apps" }))] }))] }));
77
102
  }
78
103
 
79
104
  exports.IntegrationsToggle = IntegrationsToggle;
@@ -1 +1 @@
1
- {"version":3,"file":"IntegrationsToggle.js","sources":["../../../../../src/components/IntegrationsModal/IntegrationsToggle.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type JSX } from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsToggleProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Slugs currently switched off. */\n disabled: string[];\n /** Called with the full new list of switched-off slugs. */\n onChange: (disabled: string[]) => void;\n /** Opens the connected-apps modal, to connect one more. */\n onManage?: () => void;\n /** Tooltip and accessible name. @default \"Apps in this chat\" */\n label?: string;\n /** Light-on-dark, for a dark composer. */\n dark?: boolean;\n /** Disables the control while a message is in flight. */\n busy?: boolean;\n className?: string;\n}\n\n/** A plug, matching the weight of the attach and mic icons beside it. */\nfunction PlugIcon(): JSX.Element {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M9 2v6\" />\n <path d=\"M15 2v6\" />\n <path d=\"M6 8h12v3a6 6 0 0 1-12 0z\" />\n <path d=\"M12 17v5\" />\n </svg>\n );\n}\n\n/**\n * Which apps the assistant may reach on the next message.\n *\n * Only apps the end user has actually connected appear: an app they have not\n * set up is not something they can switch off, and listing it here would turn\n * this into a second, worse version of the connect dialog.\n *\n * The choice lives in the caller and travels with each message. Nothing is\n * remembered on the server and nothing is disconnected — switching Gmail off\n * for a question about a spreadsheet must not cost an OAuth round trip to\n * undo, which is the whole reason this exists next to the composer rather than\n * inside the apps dialog.\n */\nexport function IntegrationsToggle({\n state,\n disabled,\n onChange,\n onManage,\n label = \"Apps in this chat\",\n dark = false,\n busy = false,\n className = \"\",\n}: IntegrationsToggleProps): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n const connected = useMemo(\n () => state.integrations.filter((i) => i.connected),\n [state.integrations]\n );\n\n // Closing on an outside click and on Escape, the two ways every popover is\n // expected to close. Bound only while open so a drawer full of these costs\n // nothing when they are all shut.\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n if (!ref.current?.contains(e.target as Node)) setOpen(false);\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n document.addEventListener(\"keydown\", onKeyDown, true);\n return () => {\n document.removeEventListener(\"mousedown\", onPointerDown);\n document.removeEventListener(\"keydown\", onKeyDown, true);\n };\n }, [open]);\n\n // An end user with nothing connected has nothing to switch: the button would\n // open an empty box. It appears the moment they connect their first app.\n if (!state.offered || connected.length === 0) return null;\n\n const off = new Set(disabled);\n const offCount = connected.filter((i) => off.has(i.app)).length;\n\n const toggle = (integration: Integration) => {\n const next = new Set(off);\n if (next.has(integration.app)) next.delete(integration.app);\n else next.add(integration.app);\n // Only apps still on offer are kept: carrying a slug for an app the user\n // has since disconnected would silently switch it off again if they ever\n // reconnected it.\n onChange(connected.filter((i) => next.has(i.app)).map((i) => i.app));\n };\n\n return (\n <div\n className={`devic-int-toggle ${className}`.trim()}\n data-dark={dark}\n ref={ref}\n >\n <button\n type=\"button\"\n className=\"devic-input-btn devic-int-toggle-btn\"\n onClick={() => setOpen((v) => !v)}\n disabled={busy}\n title={\n offCount\n ? `${label} — ${offCount} switched off`\n : label\n }\n aria-label={label}\n aria-expanded={open}\n aria-haspopup=\"dialog\"\n data-some-off={offCount > 0 || undefined}\n >\n <PlugIcon />\n {offCount > 0 && (\n <span className=\"devic-int-toggle-badge\" aria-hidden=\"true\">\n {offCount}\n </span>\n )}\n </button>\n\n {open && (\n <div className=\"devic-int-toggle-popover\" role=\"dialog\" aria-label={label}>\n <div className=\"devic-int-toggle-head\">\n <strong>{label}</strong>\n <span>\n Switched off here, an app sits out your next message. It stays\n connected.\n </span>\n </div>\n\n <ul className=\"devic-int-toggle-list\">\n {connected.map((integration) => {\n const on = !off.has(integration.app);\n return (\n <li key={integration.app}>\n <label className=\"devic-int-toggle-row\">\n <span className=\"devic-int-toggle-app\">\n <IntegrationLogo integration={integration} />\n <span className=\"devic-int-toggle-name\">\n {integration.name}\n </span>\n </span>\n <input\n type=\"checkbox\"\n className=\"devic-int-toggle-switch\"\n checked={on}\n onChange={() => toggle(integration)}\n aria-label={`Use ${integration.name} in this chat`}\n />\n </label>\n </li>\n );\n })}\n </ul>\n\n {onManage && (\n <button\n type=\"button\"\n className=\"devic-int-toggle-manage\"\n onClick={() => {\n setOpen(false);\n onManage();\n }}\n >\n Manage connected apps\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default IntegrationsToggle;\n"],"names":["_jsxs","_jsx","useState","useRef","useMemo","useEffect","IntegrationLogo"],"mappings":"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"}
1
+ {"version":3,"file":"IntegrationsToggle.js","sources":["../../../../../src/components/IntegrationsModal/IntegrationsToggle.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, type JSX } from \"react\";\nimport type { Integration, TenantMcpServer } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport type { TenantMcpState } from \"./useTenantMcp\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsToggleProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /**\n * The tenant's own MCP servers, when the assistant offers them (see\n * `useTenantMcp`). Their connected servers are listed under the apps and\n * switch off the same way — a server the end user connected is as much\n * theirs to leave out of a message as an account is.\n */\n mcp?: TenantMcpState;\n /**\n * What is switched off right now. App slugs, and the `toggleId` of a\n * connected MCP server — one list, because that is what the message carries\n * and what the server keeps for the rest of the turn.\n */\n disabled: string[];\n /** Called with the full new list of switched-off entries. */\n onChange: (disabled: string[]) => void;\n /** Opens the connected-apps modal, to connect one more. */\n onManage?: () => void;\n /** Tooltip and accessible name. @default \"Apps in this chat\" */\n label?: string;\n /** Light-on-dark, for a dark composer. */\n dark?: boolean;\n /** Disables the control while a message is in flight. */\n busy?: boolean;\n /**\n * Whether the requests that decide if this control exists are still running.\n *\n * Holds an inert plug in the row rather than letting the attach and mic\n * buttons shift sideways the moment the answer lands.\n */\n loading?: boolean;\n className?: string;\n}\n\n/** A plug, matching the weight of the attach and mic icons beside it. */\nfunction PlugIcon(): JSX.Element {\n return (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M9 2v6\" />\n <path d=\"M15 2v6\" />\n <path d=\"M6 8h12v3a6 6 0 0 1-12 0z\" />\n <path d=\"M12 17v5\" />\n </svg>\n );\n}\n\n/** A server's own logo when it has one, its initial when it does not. */\nfunction McpBadge({ server }: { server: TenantMcpServer }): JSX.Element {\n if (server.logoUrl) {\n return (\n <img\n className=\"devic-int-toggle-mcp-logo\"\n src={server.logoUrl}\n alt=\"\"\n aria-hidden=\"true\"\n />\n );\n }\n return (\n <span\n className=\"devic-int-toggle-mcp-logo devic-int-toggle-mcp-fallback\"\n aria-hidden=\"true\"\n >\n {server.name.slice(0, 1).toUpperCase()}\n </span>\n );\n}\n\n/**\n * Which apps the assistant may reach on the next message.\n *\n * Only apps the end user has actually connected appear: an app they have not\n * set up is not something they can switch off, and listing it here would turn\n * this into a second, worse version of the connect dialog.\n *\n * The choice lives in the caller and travels with each message. Nothing is\n * remembered on the server and nothing is disconnected — switching Gmail off\n * for a question about a spreadsheet must not cost an OAuth round trip to\n * undo, which is the whole reason this exists next to the composer rather than\n * inside the apps dialog.\n */\nexport function IntegrationsToggle({\n state,\n mcp,\n disabled,\n onChange,\n onManage,\n label = \"Apps in this chat\",\n dark = false,\n busy = false,\n loading = false,\n className = \"\",\n}: IntegrationsToggleProps): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n const connected = useMemo(\n () => state.integrations.filter((i) => i.connected),\n [state.integrations]\n );\n\n // Connected and usable: one still waiting for its consent screen, or whose\n // last probe failed, contributes no tools, so switching it off would be\n // switching off nothing. It belongs in the panel that can fix it, not here.\n const mcpServers = useMemo(\n () =>\n (mcp?.servers ?? []).filter(\n (s) => s.connection?.status === \"active\" && !!s.connection.toggleId\n ),\n [mcp?.servers]\n );\n\n // Closing on an outside click and on Escape, the two ways every popover is\n // expected to close. Bound only while open so a drawer full of these costs\n // nothing when they are all shut.\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: MouseEvent) => {\n if (!ref.current?.contains(e.target as Node)) setOpen(false);\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onPointerDown);\n document.addEventListener(\"keydown\", onKeyDown, true);\n return () => {\n document.removeEventListener(\"mousedown\", onPointerDown);\n document.removeEventListener(\"keydown\", onKeyDown, true);\n };\n }, [open]);\n\n // An end user with nothing connected has nothing to switch: the button would\n // open an empty box. It appears the moment they connect their first app — or\n // their first MCP server, which is as switchable as an account is.\n const hasApps = state.offered && connected.length > 0;\n const hasMcp = !!mcp?.offered && mcpServers.length > 0;\n\n if (!hasApps && !hasMcp) {\n if (!loading) return null;\n // Still being decided: hold the slot, inert, so the row does not shift.\n return (\n <div className={`devic-int-toggle ${className}`.trim()} data-dark={dark}>\n <button\n type=\"button\"\n className=\"devic-input-btn devic-int-toggle-btn devic-int-toggle-btn-loading\"\n disabled\n aria-busy=\"true\"\n aria-label={`${label} (loading)`}\n title={label}\n >\n <PlugIcon />\n </button>\n </div>\n );\n }\n\n const off = new Set(disabled);\n\n /** Everything still on offer, so a stale entry can never survive a rewrite. */\n const liveIds = [\n ...connected.map((i) => i.app),\n ...mcpServers.map((s) => s.connection!.toggleId as string),\n ];\n const offCount = liveIds.filter((id) => off.has(id)).length;\n\n const flip = (id: string) => {\n const next = new Set(off);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n // Only what is still on offer is kept: carrying an entry for an app or a\n // server the user has since disconnected would silently switch it off again\n // if they ever connected it back.\n onChange(liveIds.filter((candidate) => next.has(candidate)));\n };\n\n return (\n <div\n className={`devic-int-toggle ${className}`.trim()}\n data-dark={dark}\n ref={ref}\n >\n <button\n type=\"button\"\n className=\"devic-input-btn devic-int-toggle-btn\"\n onClick={() => setOpen((v) => !v)}\n disabled={busy}\n title={\n offCount\n ? `${label} — ${offCount} switched off`\n : label\n }\n aria-label={label}\n aria-expanded={open}\n aria-haspopup=\"dialog\"\n data-some-off={offCount > 0 || undefined}\n >\n <PlugIcon />\n {offCount > 0 && (\n <span className=\"devic-int-toggle-badge\" aria-hidden=\"true\">\n {offCount}\n </span>\n )}\n </button>\n\n {open && (\n <div className=\"devic-int-toggle-popover\" role=\"dialog\" aria-label={label}>\n <div className=\"devic-int-toggle-head\">\n <strong>{label}</strong>\n <span>\n Switched off here, {hasMcp && !hasApps ? \"a server\" : \"an app\"}{\" \"}\n sits out your next message. It stays connected.\n </span>\n </div>\n\n <ul className=\"devic-int-toggle-list\">\n {connected.map((integration) => (\n <li key={integration.app}>\n <label className=\"devic-int-toggle-row\">\n <span className=\"devic-int-toggle-app\">\n <IntegrationLogo integration={integration} />\n <span className=\"devic-int-toggle-name\">\n {integration.name}\n </span>\n </span>\n <input\n type=\"checkbox\"\n className=\"devic-int-toggle-switch\"\n checked={!off.has(integration.app)}\n onChange={() => flip(integration.app)}\n aria-label={`Use ${integration.name} in this chat`}\n />\n </label>\n </li>\n ))}\n\n {/* Named apart only when both kinds are present: with servers\n alone the heading would label the entire list. */}\n {hasMcp && hasApps && (\n <li className=\"devic-int-toggle-group\" aria-hidden=\"true\">\n MCP servers\n </li>\n )}\n\n {mcpServers.map((server) => {\n const id = server.connection!.toggleId as string;\n return (\n <li key={id}>\n <label className=\"devic-int-toggle-row\">\n <span className=\"devic-int-toggle-app\">\n <McpBadge server={server} />\n <span className=\"devic-int-toggle-name\">\n {server.name}\n </span>\n </span>\n <input\n type=\"checkbox\"\n className=\"devic-int-toggle-switch\"\n checked={!off.has(id)}\n onChange={() => flip(id)}\n aria-label={`Use ${server.name} in this chat`}\n />\n </label>\n </li>\n );\n })}\n </ul>\n\n {onManage && (\n <button\n type=\"button\"\n className=\"devic-int-toggle-manage\"\n onClick={() => {\n setOpen(false);\n onManage();\n }}\n >\n {hasMcp && !hasApps\n ? \"Manage connected servers\"\n : \"Manage connected apps\"}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default IntegrationsToggle;\n"],"names":["_jsxs","_jsx","useState","useRef","useMemo","useEffect","IntegrationLogo"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var React = require('react');
5
+ var reactDom = require('react-dom');
6
+ var theme = require('../theme.js');
7
+
8
+ /**
9
+ * What an MCP server asks the end user for.
10
+ *
11
+ * Both ways in end here — a server the developer listed and one the user
12
+ * brings — because the difference between them is only who typed the url.
13
+ * Everything after that is the same question: a key, or a trip to a consent
14
+ * screen.
15
+ *
16
+ * A dialog rather than something inside a card: the OAuth-application fields
17
+ * carry a redirect URI the user has to copy somewhere else, which does not fit
18
+ * in a column of a grid without becoming a stack of clipped text.
19
+ *
20
+ * Values go straight into the connection and are never kept here.
21
+ */
22
+ function McpConnectForm({ server, submitting, error, callbackUrl, needsClientCredentials, theme: theme$1, onCancel, onSubmit, }) {
23
+ const custom = !server;
24
+ const [url, setUrl] = React.useState(server?.url ?? "");
25
+ const [name, setName] = React.useState(server?.name ?? "");
26
+ const [key, setKey] = React.useState("");
27
+ const [clientId, setClientId] = React.useState("");
28
+ const [clientSecret, setClientSecret] = React.useState("");
29
+ const [showClient, setShowClient] = React.useState(!!needsClientCredentials);
30
+ const [copied, setCopied] = React.useState(false);
31
+ const [touched, setTouched] = React.useState(false);
32
+ // A server the user brings could be anything, so the form offers both ways
33
+ // and lets them say. One the developer listed already declared how it
34
+ // authenticates, and asking again would only invite a wrong answer.
35
+ const [mode, setMode] = React.useState(server?.authMode ?? "oauth");
36
+ const headerLabel = server?.headerName
37
+ ? `${server.headerName} value`
38
+ : "API key";
39
+ const urlInvalid = React.useMemo(() => custom && touched && !/^https:\/\/.+/i.test(url.trim()), [custom, touched, url]);
40
+ const keyInvalid = touched && mode === "header" && !key.trim();
41
+ const handleSubmit = (event) => {
42
+ event.preventDefault();
43
+ setTouched(true);
44
+ if (submitting)
45
+ return;
46
+ if (custom && !/^https:\/\/.+/i.test(url.trim()))
47
+ return;
48
+ if (mode === "header" && !key.trim())
49
+ return;
50
+ onSubmit({
51
+ url: custom ? url.trim() : undefined,
52
+ name: name.trim() || undefined,
53
+ auth: {
54
+ mode,
55
+ headerValue: mode === "header" ? key.trim() : undefined,
56
+ upstreamOAuth: mode === "oauth" && clientId.trim()
57
+ ? {
58
+ clientId: clientId.trim(),
59
+ clientSecret: clientSecret.trim() || undefined,
60
+ }
61
+ : undefined,
62
+ },
63
+ });
64
+ };
65
+ const title = custom ? "Add an MCP server" : `Connect ${server.name}`;
66
+ return reactDom.createPortal(jsxRuntime.jsx("div", { className: "devic-int-overlay devic-int-overlay-stacked", style: theme.themeVars(theme$1), "data-dark": theme.isDarkTheme(theme$1), onClick: onCancel, children: jsxRuntime.jsxs("form", { className: "devic-int-connect-dialog", role: "dialog", "aria-modal": "true", "aria-label": title, onClick: (e) => e.stopPropagation(), onSubmit: handleSubmit, children: [jsxRuntime.jsxs("div", { className: "devic-int-connect-header", children: [server?.logoUrl ? (jsxRuntime.jsx("img", { className: "devic-int-mcp-logo", src: server.logoUrl, alt: "", "aria-hidden": "true" })) : (jsxRuntime.jsx("span", { className: "devic-int-mcp-logo devic-int-mcp-logo-fallback", children: (server?.name ?? "M").slice(0, 1).toUpperCase() })), jsxRuntime.jsxs("div", { className: "devic-int-connect-heading", children: [jsxRuntime.jsx("strong", { children: title }), jsxRuntime.jsx("span", { children: mode === "oauth"
67
+ ? "You'll be sent to the server to finish authorising."
68
+ : mode === "header"
69
+ ? "Your key goes straight to the server — only you can use it."
70
+ : "This server needs no credentials." })] }), jsxRuntime.jsx("button", { type: "button", className: "devic-int-close", onClick: onCancel, "aria-label": "Close", children: "\u00D7" })] }), jsxRuntime.jsxs("div", { className: "devic-int-connect-body", children: [error && jsxRuntime.jsx("div", { className: "devic-int-error", children: error }), custom && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsx("span", { className: "devic-int-connect-label", children: "Server URL" }), jsxRuntime.jsx("input", { autoFocus: true, type: "url", value: url, onChange: (e) => setUrl(e.target.value), placeholder: "https://mcp.example.com/sse", spellCheck: false, autoComplete: "off", "aria-invalid": urlInvalid || undefined, "data-invalid": urlInvalid || undefined }), jsxRuntime.jsx("small", { className: "devic-int-connect-hint", children: "Must be reachable over https on a public address." })] }), jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsxs("span", { className: "devic-int-connect-label", children: ["Name ", jsxRuntime.jsx("em", { children: "(optional)" })] }), jsxRuntime.jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "What you want to call it", autoComplete: "off" })] }), jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsx("span", { className: "devic-int-connect-label", children: "Authentication" }), jsxRuntime.jsxs("select", { value: mode, onChange: (e) => setMode(e.target.value), children: [jsxRuntime.jsx("option", { value: "oauth", children: "Sign in (OAuth)" }), jsxRuntime.jsx("option", { value: "header", children: "API key" }), jsxRuntime.jsx("option", { value: "none", children: "None" })] })] })] })), mode === "header" && (jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsx("span", { className: "devic-int-connect-label", children: headerLabel }), jsxRuntime.jsx("input", { autoFocus: !custom, type: "password", value: key, onChange: (e) => setKey(e.target.value), autoComplete: "new-password", spellCheck: false, "aria-invalid": keyInvalid || undefined, "data-invalid": keyInvalid || undefined })] })), mode === "oauth" && (custom || server?.allowClientCredentials) && (jsxRuntime.jsxs("div", { className: "devic-int-mcp-advanced", children: [needsClientCredentials && (jsxRuntime.jsx("div", { className: "devic-int-notice", children: "This server needs its own OAuth application. Register one with it and paste the details below." })), jsxRuntime.jsxs("button", { type: "button", className: "devic-int-mcp-disclosure", onClick: () => setShowClient((prev) => !prev), "aria-expanded": showClient, children: [showClient ? "▾" : "▸", " Use my own OAuth application"] }), showClient && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsxs("span", { className: "devic-int-connect-label", children: ["Client ID ", jsxRuntime.jsx("em", { children: "(optional)" })] }), jsxRuntime.jsx("input", { type: "text", value: clientId, onChange: (e) => setClientId(e.target.value), autoComplete: "off", spellCheck: false })] }), jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsxs("span", { className: "devic-int-connect-label", children: ["Client secret ", jsxRuntime.jsx("em", { children: "(optional)" })] }), jsxRuntime.jsx("input", { type: "password", value: clientSecret, onChange: (e) => setClientSecret(e.target.value), autoComplete: "new-password", spellCheck: false })] }), callbackUrl && (jsxRuntime.jsxs("label", { className: "devic-int-connect-field", children: [jsxRuntime.jsx("span", { className: "devic-int-connect-label", children: "Authorised redirect URI" }), jsxRuntime.jsx("input", { readOnly: true, value: callbackUrl, spellCheck: false }), jsxRuntime.jsxs("small", { className: "devic-int-connect-hint", children: ["Add this to your OAuth application, exactly as shown.", " ", jsxRuntime.jsx("button", { type: "button", className: "devic-int-mcp-copy", onClick: () => {
71
+ void navigator.clipboard
72
+ ?.writeText(callbackUrl)
73
+ .then(() => setCopied(true))
74
+ .catch(() => undefined);
75
+ }, children: copied ? "Copied" : "Copy" })] })] }))] }))] }))] }), jsxRuntime.jsxs("div", { className: "devic-int-connect-actions", children: [jsxRuntime.jsx("button", { type: "button", className: "devic-int-btn", onClick: onCancel, disabled: submitting, children: "Cancel" }), jsxRuntime.jsx("button", { type: "submit", className: "devic-int-btn devic-int-btn-primary", disabled: submitting, children: submitting ? "Connecting…" : mode === "oauth" ? "Continue" : "Connect" })] })] }) }), document.body);
76
+ }
77
+
78
+ exports.McpConnectForm = McpConnectForm;
79
+ //# sourceMappingURL=McpConnectForm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpConnectForm.js","sources":["../../../../../src/components/IntegrationsModal/McpConnectForm.tsx"],"sourcesContent":["import { useMemo, useState, type FormEvent, type JSX } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { TenantMcpAuthInput, TenantMcpServer } from \"../../api/types\";\nimport { isDarkTheme, themeVars, type DevicTheme } from \"../theme\";\n\nexport interface McpConnectFormProps {\n /**\n * The server being connected. `null` is the \"add your own\" case, where the\n * url and the name are the end user's to type.\n */\n server: TenantMcpServer | null;\n submitting?: boolean;\n error?: string | null;\n /**\n * The redirect URI the end user must authorise on their own OAuth\n * application, when the server told us it has no dynamic registration.\n */\n callbackUrl?: string;\n /** Open the client-credentials fields already expanded. */\n needsClientCredentials?: boolean;\n theme?: DevicTheme;\n onCancel: () => void;\n onSubmit: (values: {\n url?: string;\n name?: string;\n auth: TenantMcpAuthInput;\n }) => void;\n}\n\n/**\n * What an MCP server asks the end user for.\n *\n * Both ways in end here — a server the developer listed and one the user\n * brings — because the difference between them is only who typed the url.\n * Everything after that is the same question: a key, or a trip to a consent\n * screen.\n *\n * A dialog rather than something inside a card: the OAuth-application fields\n * carry a redirect URI the user has to copy somewhere else, which does not fit\n * in a column of a grid without becoming a stack of clipped text.\n *\n * Values go straight into the connection and are never kept here.\n */\nexport function McpConnectForm({\n server,\n submitting,\n error,\n callbackUrl,\n needsClientCredentials,\n theme,\n onCancel,\n onSubmit,\n}: McpConnectFormProps): JSX.Element {\n const custom = !server;\n const [url, setUrl] = useState(server?.url ?? \"\");\n const [name, setName] = useState(server?.name ?? \"\");\n const [key, setKey] = useState(\"\");\n const [clientId, setClientId] = useState(\"\");\n const [clientSecret, setClientSecret] = useState(\"\");\n const [showClient, setShowClient] = useState(!!needsClientCredentials);\n const [copied, setCopied] = useState(false);\n const [touched, setTouched] = useState(false);\n\n // A server the user brings could be anything, so the form offers both ways\n // and lets them say. One the developer listed already declared how it\n // authenticates, and asking again would only invite a wrong answer.\n const [mode, setMode] = useState<TenantMcpAuthInput[\"mode\"]>(\n server?.authMode ?? \"oauth\"\n );\n\n const headerLabel = server?.headerName\n ? `${server.headerName} value`\n : \"API key\";\n\n const urlInvalid = useMemo(\n () => custom && touched && !/^https:\\/\\/.+/i.test(url.trim()),\n [custom, touched, url]\n );\n const keyInvalid = touched && mode === \"header\" && !key.trim();\n\n const handleSubmit = (event: FormEvent) => {\n event.preventDefault();\n setTouched(true);\n if (submitting) return;\n if (custom && !/^https:\\/\\/.+/i.test(url.trim())) return;\n if (mode === \"header\" && !key.trim()) return;\n\n onSubmit({\n url: custom ? url.trim() : undefined,\n name: name.trim() || undefined,\n auth: {\n mode,\n headerValue: mode === \"header\" ? key.trim() : undefined,\n upstreamOAuth:\n mode === \"oauth\" && clientId.trim()\n ? {\n clientId: clientId.trim(),\n clientSecret: clientSecret.trim() || undefined,\n }\n : undefined,\n },\n });\n };\n\n const title = custom ? \"Add an MCP server\" : `Connect ${server.name}`;\n\n return createPortal(\n <div\n className=\"devic-int-overlay devic-int-overlay-stacked\"\n style={themeVars(theme)}\n data-dark={isDarkTheme(theme)}\n onClick={onCancel}\n >\n <form\n className=\"devic-int-connect-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n onClick={(e) => e.stopPropagation()}\n onSubmit={handleSubmit}\n >\n <div className=\"devic-int-connect-header\">\n {server?.logoUrl ? (\n <img\n className=\"devic-int-mcp-logo\"\n src={server.logoUrl}\n alt=\"\"\n aria-hidden=\"true\"\n />\n ) : (\n <span className=\"devic-int-mcp-logo devic-int-mcp-logo-fallback\">\n {(server?.name ?? \"M\").slice(0, 1).toUpperCase()}\n </span>\n )}\n <div className=\"devic-int-connect-heading\">\n <strong>{title}</strong>\n <span>\n {mode === \"oauth\"\n ? \"You'll be sent to the server to finish authorising.\"\n : mode === \"header\"\n ? \"Your key goes straight to the server — only you can use it.\"\n : \"This server needs no credentials.\"}\n </span>\n </div>\n <button\n type=\"button\"\n className=\"devic-int-close\"\n onClick={onCancel}\n aria-label=\"Close\"\n >\n ×\n </button>\n </div>\n\n <div className=\"devic-int-connect-body\">\n {error && <div className=\"devic-int-error\">{error}</div>}\n\n {custom && (\n <>\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">Server URL</span>\n <input\n autoFocus\n type=\"url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n placeholder=\"https://mcp.example.com/sse\"\n spellCheck={false}\n autoComplete=\"off\"\n aria-invalid={urlInvalid || undefined}\n data-invalid={urlInvalid || undefined}\n />\n <small className=\"devic-int-connect-hint\">\n Must be reachable over https on a public address.\n </small>\n </label>\n\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">\n Name <em>(optional)</em>\n </span>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"What you want to call it\"\n autoComplete=\"off\"\n />\n </label>\n\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">Authentication</span>\n <select\n value={mode}\n onChange={(e) =>\n setMode(e.target.value as TenantMcpAuthInput[\"mode\"])\n }\n >\n <option value=\"oauth\">Sign in (OAuth)</option>\n <option value=\"header\">API key</option>\n <option value=\"none\">None</option>\n </select>\n </label>\n </>\n )}\n\n {mode === \"header\" && (\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">{headerLabel}</span>\n <input\n autoFocus={!custom}\n type=\"password\"\n value={key}\n onChange={(e) => setKey(e.target.value)}\n autoComplete=\"new-password\"\n spellCheck={false}\n aria-invalid={keyInvalid || undefined}\n data-invalid={keyInvalid || undefined}\n />\n </label>\n )}\n\n {mode === \"oauth\" && (custom || server?.allowClientCredentials) && (\n <div className=\"devic-int-mcp-advanced\">\n {/* Not a detail to bury: a server whose authorization server has\n no dynamic client registration cannot be connected at all\n without these, and the message that says so arrives only\n after the first attempt. */}\n {needsClientCredentials && (\n <div className=\"devic-int-notice\">\n This server needs its own OAuth application. Register one with\n it and paste the details below.\n </div>\n )}\n <button\n type=\"button\"\n className=\"devic-int-mcp-disclosure\"\n onClick={() => setShowClient((prev) => !prev)}\n aria-expanded={showClient}\n >\n {showClient ? \"▾\" : \"▸\"} Use my own OAuth application\n </button>\n\n {showClient && (\n <>\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">\n Client ID <em>(optional)</em>\n </span>\n <input\n type=\"text\"\n value={clientId}\n onChange={(e) => setClientId(e.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </label>\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">\n Client secret <em>(optional)</em>\n </span>\n <input\n type=\"password\"\n value={clientSecret}\n onChange={(e) => setClientSecret(e.target.value)}\n autoComplete=\"new-password\"\n spellCheck={false}\n />\n </label>\n {callbackUrl && (\n <label className=\"devic-int-connect-field\">\n <span className=\"devic-int-connect-label\">\n Authorised redirect URI\n </span>\n <input readOnly value={callbackUrl} spellCheck={false} />\n <small className=\"devic-int-connect-hint\">\n Add this to your OAuth application, exactly as shown.{\" \"}\n <button\n type=\"button\"\n className=\"devic-int-mcp-copy\"\n onClick={() => {\n void navigator.clipboard\n ?.writeText(callbackUrl)\n .then(() => setCopied(true))\n .catch(() => undefined);\n }}\n >\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </small>\n </label>\n )}\n </>\n )}\n </div>\n )}\n </div>\n\n <div className=\"devic-int-connect-actions\">\n <button\n type=\"button\"\n className=\"devic-int-btn\"\n onClick={onCancel}\n disabled={submitting}\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n className=\"devic-int-btn devic-int-btn-primary\"\n disabled={submitting}\n >\n {submitting ? \"Connecting…\" : mode === \"oauth\" ? \"Continue\" : \"Connect\"}\n </button>\n </div>\n </form>\n </div>,\n document.body\n );\n}\n\nexport default McpConnectForm;\n"],"names":["theme","useState","useMemo","createPortal","_jsx","themeVars","isDarkTheme","_jsxs","_Fragment"],"mappings":"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"}
@@ -0,0 +1,234 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var React = require('react');
5
+ var McpConnectForm = require('./McpConnectForm.js');
6
+
7
+ /** A random value tying an OAuth round trip to the window that started it. */
8
+ function newNonce() {
9
+ const c = typeof crypto !== "undefined" ? crypto : undefined;
10
+ if (c?.randomUUID)
11
+ return c.randomUUID();
12
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
13
+ }
14
+ function stateOf(server) {
15
+ const status = server.connection?.status;
16
+ if (status === "active")
17
+ return { key: "connected", label: "Connected" };
18
+ if (status === "error")
19
+ return { key: "reconnect", label: "Needs attention" };
20
+ if (status === "pending_auth")
21
+ return { key: "reconnect", label: "Waiting for sign-in" };
22
+ return { key: "disconnected", label: "Not connected" };
23
+ }
24
+ function matches(server, query) {
25
+ const q = query.trim().toLowerCase();
26
+ if (!q)
27
+ return true;
28
+ return (server.name.toLowerCase().includes(q) ||
29
+ server.url.toLowerCase().includes(q) ||
30
+ (server.description ?? "").toLowerCase().includes(q));
31
+ }
32
+ /** Identity of a row, stable whether or not it has been connected yet. */
33
+ function keyOf(server) {
34
+ return server.connection?.id ?? server.templateId ?? server.url;
35
+ }
36
+ /**
37
+ * The MCP servers section of the connected-apps panel.
38
+ *
39
+ * The end user sees one list: the servers their developer left ready to
40
+ * connect, the ones they added themselves, and — unless it was switched off —
41
+ * a button to add another. Both routes open the same form, because the only
42
+ * difference between them is who typed the url.
43
+ *
44
+ * Connecting opens a consent screen in a popup. The popup is opened empty
45
+ * *before* the request that produces its URL, because browsers only honour
46
+ * `window.open` inside the gesture that triggered it; opening it after the
47
+ * round trip is what gets it blocked. Two endings are watched, not one — a
48
+ * cancelled consent screen posts nothing back, so the close is watched too.
49
+ */
50
+ function McpServersSection({ state, theme, query = "", }) {
51
+ const { servers, allowCustom, limits, loading, client, scope, refresh } = state;
52
+ const [actionError, setActionError] = React.useState(null);
53
+ const [busyKey, setBusyKey] = React.useState(null);
54
+ const [blockedUrl, setBlockedUrl] = React.useState(null);
55
+ /** The row whose form is open. `null` inside the object is "add your own". */
56
+ const [prompt, setPrompt] = React.useState(null);
57
+ const [formError, setFormError] = React.useState(null);
58
+ const pendingRef = React.useRef(null);
59
+ const popupRef = React.useRef(null);
60
+ const pollRef = React.useRef(null);
61
+ const finish = React.useCallback(() => {
62
+ if (pollRef.current !== null) {
63
+ window.clearInterval(pollRef.current);
64
+ pollRef.current = null;
65
+ }
66
+ pendingRef.current = null;
67
+ popupRef.current = null;
68
+ setBusyKey(null);
69
+ void refresh();
70
+ }, [refresh]);
71
+ // The callback page says it is done. A nudge, never the result: what is shown
72
+ // comes from re-reading the server, so a forged message costs one extra fetch
73
+ // and nothing else.
74
+ React.useEffect(() => {
75
+ const onMessage = (event) => {
76
+ const data = event.data;
77
+ if (!data || data.source !== "devic")
78
+ return;
79
+ if (data.type !== "mcp-connected")
80
+ return;
81
+ const pending = pendingRef.current;
82
+ if (!pending || data.returnTo !== pending.returnTo)
83
+ return;
84
+ popupRef.current?.close();
85
+ finish();
86
+ };
87
+ window.addEventListener("message", onMessage);
88
+ return () => window.removeEventListener("message", onMessage);
89
+ }, [finish]);
90
+ React.useEffect(() => () => {
91
+ if (pollRef.current !== null)
92
+ window.clearInterval(pollRef.current);
93
+ }, []);
94
+ const openPrompt = (server) => {
95
+ setFormError(null);
96
+ setActionError(null);
97
+ setBlockedUrl(null);
98
+ setPrompt({ server });
99
+ };
100
+ const submit = async (values) => {
101
+ if (!client || !prompt)
102
+ return;
103
+ const server = prompt.server;
104
+ const rowKey = server ? keyOf(server) : "new";
105
+ setBusyKey(rowKey);
106
+ setFormError(null);
107
+ const nonce = newNonce();
108
+ const returnTo = `${window.location.origin}/?devic_mcp=${nonce}`;
109
+ // Opened empty inside the submit gesture and navigated once the URL is
110
+ // known. Skipped when no browser is involved at all.
111
+ const popup = values.auth.mode === "oauth"
112
+ ? window.open("", "devic-mcp-oauth", "width=520,height=680,menubar=no,toolbar=no")
113
+ : null;
114
+ try {
115
+ const existing = server?.connection;
116
+ const result = existing
117
+ ? await client.reconnectMcpServer(existing.id, {
118
+ ...scope,
119
+ returnTo,
120
+ auth: values.auth,
121
+ })
122
+ : await client.connectMcpServer({
123
+ ...scope,
124
+ returnTo,
125
+ templateId: server?.templateId,
126
+ url: values.url,
127
+ name: values.name,
128
+ auth: values.auth,
129
+ });
130
+ if (result.requiresClientCredentials) {
131
+ // Not a failure the user can do nothing about: they own the server, so
132
+ // the form reopens with the fields and the redirect URI to authorise.
133
+ popup?.close();
134
+ setBusyKey(null);
135
+ setPrompt({
136
+ server,
137
+ callbackUrl: result.callbackUrl,
138
+ needsClientCredentials: true,
139
+ });
140
+ setFormError(result.error ?? null);
141
+ return;
142
+ }
143
+ if (result.status === "error") {
144
+ popup?.close();
145
+ setBusyKey(null);
146
+ setFormError(result.error ?? "The server could not be reached.");
147
+ return;
148
+ }
149
+ if (!result.authorizationUrl) {
150
+ // Connected outright: the key the user typed is the credential.
151
+ popup?.close();
152
+ setPrompt(null);
153
+ finish();
154
+ return;
155
+ }
156
+ pendingRef.current = { returnTo };
157
+ setPrompt(null);
158
+ if (popup && !popup.closed) {
159
+ popupRef.current = popup;
160
+ popup.location.href = result.authorizationUrl;
161
+ pollRef.current = window.setInterval(() => {
162
+ if (popup.closed)
163
+ finish();
164
+ }, 700);
165
+ }
166
+ else {
167
+ // Blocked (Safari, in-app browsers, extensions): hand over the URL.
168
+ setBlockedUrl({ url: result.authorizationUrl });
169
+ setBusyKey(null);
170
+ }
171
+ }
172
+ catch (err) {
173
+ popup?.close();
174
+ pendingRef.current = null;
175
+ setBusyKey(null);
176
+ setFormError(err instanceof Error ? err.message : String(err));
177
+ }
178
+ };
179
+ const disconnect = async (server) => {
180
+ const connection = server.connection;
181
+ if (!client || !connection || busyKey)
182
+ return;
183
+ setBusyKey(keyOf(server));
184
+ setActionError(null);
185
+ try {
186
+ await client.disconnectMcpServer(connection.id, scope);
187
+ await refresh();
188
+ }
189
+ catch (err) {
190
+ setActionError(err instanceof Error ? err.message : String(err));
191
+ }
192
+ finally {
193
+ setBusyKey(null);
194
+ }
195
+ };
196
+ const visible = servers.filter((s) => matches(s, query));
197
+ const atLimit = limits.used >= limits.maxServers;
198
+ // Nothing offered and nothing addable: the whole section would be a heading
199
+ // over an empty box, which says less than saying nothing.
200
+ if (!servers.length && !allowCustom)
201
+ return null;
202
+ return (jsxRuntime.jsxs("div", { className: "devic-int-mcp-section", children: [jsxRuntime.jsxs("div", { className: "devic-int-mcp-heading", children: [jsxRuntime.jsx("h4", { children: "MCP servers" }), jsxRuntime.jsx("span", { children: "Connect a server and its tools become available in this chat." })] }), actionError && jsxRuntime.jsx("div", { className: "devic-int-error", children: actionError }), blockedUrl && (jsxRuntime.jsxs("div", { className: "devic-int-notice", children: ["Your browser blocked the pop-up.", " ", jsxRuntime.jsx("a", { href: blockedUrl.url, target: "_blank", rel: "noopener noreferrer", onClick: () => {
203
+ pendingRef.current = null;
204
+ setBlockedUrl(null);
205
+ }, children: "Open the authorisation page" }), " ", "and come back \u2014 then use Refresh."] })), visible.length > 0 && (jsxRuntime.jsx("div", { className: "devic-int-grid", children: visible.map((server) => {
206
+ const cardState = stateOf(server);
207
+ const rowKey = keyOf(server);
208
+ const busy = busyKey === rowKey;
209
+ const connection = server.connection;
210
+ return (jsxRuntime.jsxs("div", { className: "devic-int-card", "data-state": cardState.key, children: [jsxRuntime.jsxs("div", { className: "devic-int-card-head", children: [server.logoUrl ? (jsxRuntime.jsx("img", { className: "devic-int-mcp-logo", src: server.logoUrl, alt: "", "aria-hidden": "true" })) : (jsxRuntime.jsx("span", { className: "devic-int-mcp-logo devic-int-mcp-logo-fallback", children: server.name.slice(0, 1).toUpperCase() })), jsxRuntime.jsxs("span", { className: "devic-int-state", children: [jsxRuntime.jsx("span", { className: "devic-int-dot", "data-ok": cardState.key === "connected", "data-off": cardState.key === "disconnected", "aria-hidden": "true" }), cardState.label] })] }), jsxRuntime.jsx("div", { className: "devic-int-name", title: server.name, children: server.name }), jsxRuntime.jsx("div", { className: "devic-int-description", title: server.description ?? server.url, children: server.description ?? server.url }), connection?.shared && (
211
+ // Worth saying before they wonder why they cannot remove it.
212
+ jsxRuntime.jsx("div", { className: "devic-int-mcp-note", children: "Connected for everyone on this account" })), connection?.status === "active" &&
213
+ typeof connection.toolCount === "number" && (jsxRuntime.jsxs("div", { className: "devic-int-mcp-note", children: [connection.toolCount, " tool", connection.toolCount === 1 ? "" : "s", " available"] })), connection?.lastProbeError && (jsxRuntime.jsx("div", { className: "devic-int-mcp-note devic-int-mcp-note-warn", title: connection.lastProbeError, children: connection.lastProbeError })), jsxRuntime.jsx("button", { type: "button", className: `devic-int-btn devic-int-btn-block${cardState.key === "connected" ? "" : " devic-int-btn-primary"}`, onClick: () => openPrompt(server), disabled: busy ||
214
+ !!busyKey ||
215
+ connection?.readOnly ||
216
+ (!connection && atLimit), title: connection?.readOnly
217
+ ? "This server was connected for the whole account."
218
+ : !connection && atLimit
219
+ ? `You can have ${limits.maxServers} connected at a time.`
220
+ : undefined, children: busy
221
+ ? "Waiting…"
222
+ : cardState.key === "disconnected"
223
+ ? "Connect"
224
+ : "Reconnect" }), connection && !connection.readOnly && (jsxRuntime.jsx("button", { type: "button", className: "devic-int-btn devic-int-btn-block devic-int-mcp-remove", onClick: () => void disconnect(server), disabled: !!busyKey, children: "Disconnect" }))] }, rowKey));
225
+ }) })), allowCustom && (jsxRuntime.jsx("button", { type: "button", className: "devic-int-btn devic-int-mcp-add", onClick: () => openPrompt(null), disabled: loading || !!busyKey || atLimit, title: atLimit
226
+ ? `You can have ${limits.maxServers} MCP server${limits.maxServers === 1 ? "" : "s"} connected at a time.`
227
+ : undefined, children: "+ Add your own MCP" })), prompt && (jsxRuntime.jsx(McpConnectForm.McpConnectForm, { server: prompt.server, callbackUrl: prompt.callbackUrl, needsClientCredentials: prompt.needsClientCredentials, submitting: busyKey === (prompt.server ? keyOf(prompt.server) : "new"), error: formError, theme: theme, onCancel: () => {
228
+ setPrompt(null);
229
+ setFormError(null);
230
+ }, onSubmit: submit }))] }));
231
+ }
232
+
233
+ exports.McpServersSection = McpServersSection;
234
+ //# sourceMappingURL=McpServersSection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpServersSection.js","sources":["../../../../../src/components/IntegrationsModal/McpServersSection.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type JSX } from \"react\";\nimport type {\n TenantMcpAuthInput,\n TenantMcpConnectResult,\n TenantMcpServer,\n} from \"../../api/types\";\nimport type { DevicTheme } from \"../theme\";\nimport { McpConnectForm } from \"./McpConnectForm\";\nimport type { TenantMcpState } from \"./useTenantMcp\";\n\nexport interface McpServersSectionProps {\n state: TenantMcpState;\n theme?: DevicTheme;\n /** Filter typed in the modal's search box. */\n query?: string;\n}\n\n/** Message the OAuth callback page posts back when an MCP connection is done. */\ninterface McpCallbackMessage {\n source?: string;\n type?: string;\n status?: string;\n returnTo?: string;\n}\n\n/** A random value tying an OAuth round trip to the window that started it. */\nfunction newNonce(): string {\n const c = typeof crypto !== \"undefined\" ? crypto : undefined;\n if (c?.randomUUID) return c.randomUUID();\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction stateOf(server: TenantMcpServer): {\n key: \"connected\" | \"reconnect\" | \"disconnected\";\n label: string;\n} {\n const status = server.connection?.status;\n if (status === \"active\") return { key: \"connected\", label: \"Connected\" };\n if (status === \"error\") return { key: \"reconnect\", label: \"Needs attention\" };\n if (status === \"pending_auth\")\n return { key: \"reconnect\", label: \"Waiting for sign-in\" };\n return { key: \"disconnected\", label: \"Not connected\" };\n}\n\nfunction matches(server: TenantMcpServer, query: string): boolean {\n const q = query.trim().toLowerCase();\n if (!q) return true;\n return (\n server.name.toLowerCase().includes(q) ||\n server.url.toLowerCase().includes(q) ||\n (server.description ?? \"\").toLowerCase().includes(q)\n );\n}\n\n/** Identity of a row, stable whether or not it has been connected yet. */\nfunction keyOf(server: TenantMcpServer): string {\n return server.connection?.id ?? server.templateId ?? server.url;\n}\n\n/**\n * The MCP servers section of the connected-apps panel.\n *\n * The end user sees one list: the servers their developer left ready to\n * connect, the ones they added themselves, and — unless it was switched off —\n * a button to add another. Both routes open the same form, because the only\n * difference between them is who typed the url.\n *\n * Connecting opens a consent screen in a popup. The popup is opened empty\n * *before* the request that produces its URL, because browsers only honour\n * `window.open` inside the gesture that triggered it; opening it after the\n * round trip is what gets it blocked. Two endings are watched, not one — a\n * cancelled consent screen posts nothing back, so the close is watched too.\n */\nexport function McpServersSection({\n state,\n theme,\n query = \"\",\n}: McpServersSectionProps): JSX.Element | null {\n const { servers, allowCustom, limits, loading, client, scope, refresh } = state;\n\n const [actionError, setActionError] = useState<string | null>(null);\n const [busyKey, setBusyKey] = useState<string | null>(null);\n const [blockedUrl, setBlockedUrl] = useState<{ url: string } | null>(null);\n /** The row whose form is open. `null` inside the object is \"add your own\". */\n const [prompt, setPrompt] = useState<{\n server: TenantMcpServer | null;\n callbackUrl?: string;\n needsClientCredentials?: boolean;\n } | null>(null);\n const [formError, setFormError] = useState<string | null>(null);\n\n const pendingRef = useRef<{ returnTo: string } | null>(null);\n const popupRef = useRef<Window | null>(null);\n const pollRef = useRef<number | null>(null);\n\n const finish = useCallback(() => {\n if (pollRef.current !== null) {\n window.clearInterval(pollRef.current);\n pollRef.current = null;\n }\n pendingRef.current = null;\n popupRef.current = null;\n setBusyKey(null);\n void refresh();\n }, [refresh]);\n\n // The callback page says it is done. A nudge, never the result: what is shown\n // comes from re-reading the server, so a forged message costs one extra fetch\n // and nothing else.\n useEffect(() => {\n const onMessage = (event: MessageEvent) => {\n const data = event.data as McpCallbackMessage | undefined;\n if (!data || data.source !== \"devic\") return;\n if (data.type !== \"mcp-connected\") return;\n const pending = pendingRef.current;\n if (!pending || data.returnTo !== pending.returnTo) return;\n popupRef.current?.close();\n finish();\n };\n window.addEventListener(\"message\", onMessage);\n return () => window.removeEventListener(\"message\", onMessage);\n }, [finish]);\n\n useEffect(\n () => () => {\n if (pollRef.current !== null) window.clearInterval(pollRef.current);\n },\n []\n );\n\n const openPrompt = (server: TenantMcpServer | null) => {\n setFormError(null);\n setActionError(null);\n setBlockedUrl(null);\n setPrompt({ server });\n };\n\n const submit = async (values: {\n url?: string;\n name?: string;\n auth: TenantMcpAuthInput;\n }) => {\n if (!client || !prompt) return;\n const server = prompt.server;\n const rowKey = server ? keyOf(server) : \"new\";\n setBusyKey(rowKey);\n setFormError(null);\n\n const nonce = newNonce();\n const returnTo = `${window.location.origin}/?devic_mcp=${nonce}`;\n // Opened empty inside the submit gesture and navigated once the URL is\n // known. Skipped when no browser is involved at all.\n const popup =\n values.auth.mode === \"oauth\"\n ? window.open(\n \"\",\n \"devic-mcp-oauth\",\n \"width=520,height=680,menubar=no,toolbar=no\"\n )\n : null;\n\n try {\n const existing = server?.connection;\n const result: TenantMcpConnectResult = existing\n ? await client.reconnectMcpServer(existing.id, {\n ...scope,\n returnTo,\n auth: values.auth,\n })\n : await client.connectMcpServer({\n ...scope,\n returnTo,\n templateId: server?.templateId,\n url: values.url,\n name: values.name,\n auth: values.auth,\n });\n\n if (result.requiresClientCredentials) {\n // Not a failure the user can do nothing about: they own the server, so\n // the form reopens with the fields and the redirect URI to authorise.\n popup?.close();\n setBusyKey(null);\n setPrompt({\n server,\n callbackUrl: result.callbackUrl,\n needsClientCredentials: true,\n });\n setFormError(result.error ?? null);\n return;\n }\n\n if (result.status === \"error\") {\n popup?.close();\n setBusyKey(null);\n setFormError(result.error ?? \"The server could not be reached.\");\n return;\n }\n\n if (!result.authorizationUrl) {\n // Connected outright: the key the user typed is the credential.\n popup?.close();\n setPrompt(null);\n finish();\n return;\n }\n\n pendingRef.current = { returnTo };\n setPrompt(null);\n if (popup && !popup.closed) {\n popupRef.current = popup;\n popup.location.href = result.authorizationUrl;\n pollRef.current = window.setInterval(() => {\n if (popup.closed) finish();\n }, 700);\n } else {\n // Blocked (Safari, in-app browsers, extensions): hand over the URL.\n setBlockedUrl({ url: result.authorizationUrl });\n setBusyKey(null);\n }\n } catch (err) {\n popup?.close();\n pendingRef.current = null;\n setBusyKey(null);\n setFormError(err instanceof Error ? err.message : String(err));\n }\n };\n\n const disconnect = async (server: TenantMcpServer) => {\n const connection = server.connection;\n if (!client || !connection || busyKey) return;\n setBusyKey(keyOf(server));\n setActionError(null);\n try {\n await client.disconnectMcpServer(connection.id, scope);\n await refresh();\n } catch (err) {\n setActionError(err instanceof Error ? err.message : String(err));\n } finally {\n setBusyKey(null);\n }\n };\n\n const visible = servers.filter((s) => matches(s, query));\n const atLimit = limits.used >= limits.maxServers;\n\n // Nothing offered and nothing addable: the whole section would be a heading\n // over an empty box, which says less than saying nothing.\n if (!servers.length && !allowCustom) return null;\n\n return (\n <div className=\"devic-int-mcp-section\">\n <div className=\"devic-int-mcp-heading\">\n <h4>MCP servers</h4>\n <span>Connect a server and its tools become available in this chat.</span>\n </div>\n\n {actionError && <div className=\"devic-int-error\">{actionError}</div>}\n\n {blockedUrl && (\n <div className=\"devic-int-notice\">\n Your browser blocked the pop-up.{\" \"}\n <a\n href={blockedUrl.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={() => {\n pendingRef.current = null;\n setBlockedUrl(null);\n }}\n >\n Open the authorisation page\n </a>{\" \"}\n and come back — then use Refresh.\n </div>\n )}\n\n {visible.length > 0 && (\n <div className=\"devic-int-grid\">\n {visible.map((server) => {\n const cardState = stateOf(server);\n const rowKey = keyOf(server);\n const busy = busyKey === rowKey;\n const connection = server.connection;\n return (\n <div\n key={rowKey}\n className=\"devic-int-card\"\n data-state={cardState.key}\n >\n <div className=\"devic-int-card-head\">\n {server.logoUrl ? (\n <img\n className=\"devic-int-mcp-logo\"\n src={server.logoUrl}\n alt=\"\"\n aria-hidden=\"true\"\n />\n ) : (\n <span className=\"devic-int-mcp-logo devic-int-mcp-logo-fallback\">\n {server.name.slice(0, 1).toUpperCase()}\n </span>\n )}\n <span className=\"devic-int-state\">\n <span\n className=\"devic-int-dot\"\n data-ok={cardState.key === \"connected\"}\n data-off={cardState.key === \"disconnected\"}\n aria-hidden=\"true\"\n />\n {cardState.label}\n </span>\n </div>\n\n <div className=\"devic-int-name\" title={server.name}>\n {server.name}\n </div>\n\n <div\n className=\"devic-int-description\"\n title={server.description ?? server.url}\n >\n {server.description ?? server.url}\n </div>\n\n {connection?.shared && (\n // Worth saying before they wonder why they cannot remove it.\n <div className=\"devic-int-mcp-note\">\n Connected for everyone on this account\n </div>\n )}\n\n {connection?.status === \"active\" &&\n typeof connection.toolCount === \"number\" && (\n <div className=\"devic-int-mcp-note\">\n {connection.toolCount} tool\n {connection.toolCount === 1 ? \"\" : \"s\"} available\n </div>\n )}\n\n {connection?.lastProbeError && (\n <div\n className=\"devic-int-mcp-note devic-int-mcp-note-warn\"\n title={connection.lastProbeError}\n >\n {connection.lastProbeError}\n </div>\n )}\n\n <button\n type=\"button\"\n className={`devic-int-btn devic-int-btn-block${\n cardState.key === \"connected\" ? \"\" : \" devic-int-btn-primary\"\n }`}\n onClick={() => openPrompt(server)}\n disabled={\n busy ||\n !!busyKey ||\n connection?.readOnly ||\n (!connection && atLimit)\n }\n title={\n connection?.readOnly\n ? \"This server was connected for the whole account.\"\n : !connection && atLimit\n ? `You can have ${limits.maxServers} connected at a time.`\n : undefined\n }\n >\n {busy\n ? \"Waiting…\"\n : cardState.key === \"disconnected\"\n ? \"Connect\"\n : \"Reconnect\"}\n </button>\n\n {connection && !connection.readOnly && (\n <button\n type=\"button\"\n className=\"devic-int-btn devic-int-btn-block devic-int-mcp-remove\"\n onClick={() => void disconnect(server)}\n disabled={!!busyKey}\n >\n Disconnect\n </button>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n {allowCustom && (\n <button\n type=\"button\"\n className=\"devic-int-btn devic-int-mcp-add\"\n onClick={() => openPrompt(null)}\n disabled={loading || !!busyKey || atLimit}\n title={\n atLimit\n ? `You can have ${limits.maxServers} MCP server${\n limits.maxServers === 1 ? \"\" : \"s\"\n } connected at a time.`\n : undefined\n }\n >\n + Add your own MCP\n </button>\n )}\n\n {prompt && (\n <McpConnectForm\n server={prompt.server}\n callbackUrl={prompt.callbackUrl}\n needsClientCredentials={prompt.needsClientCredentials}\n submitting={\n busyKey === (prompt.server ? keyOf(prompt.server) : \"new\")\n }\n error={formError}\n theme={theme}\n onCancel={() => {\n setPrompt(null);\n setFormError(null);\n }}\n onSubmit={submit}\n />\n )}\n </div>\n );\n}\n\nexport default McpServersSection;\n"],"names":["useState","useRef","useCallback","useEffect","_jsxs","_jsx","McpConnectForm"],"mappings":"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"}