@iblai/web-containers 1.57.0 → 1.58.1

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 (130) hide show
  1. package/dist/cjs/_virtual/index2.js +2 -2
  2. package/dist/cjs/_virtual/index3.js +2 -2
  3. package/dist/cjs/components/chat/chat-download-dialog.js.map +1 -1
  4. package/dist/cjs/components/chat/ui/button.js +4 -4
  5. package/dist/cjs/components/chat/ui/button.js.map +1 -1
  6. package/dist/cjs/components/chat-input-form/composer-plus-menu.js +94 -17
  7. package/dist/cjs/components/chat-input-form/composer-plus-menu.js.map +1 -1
  8. package/dist/cjs/components/chat-input-form/mcp-connect-explainer-dialog.js +29 -0
  9. package/dist/cjs/components/chat-input-form/mcp-connect-explainer-dialog.js.map +1 -0
  10. package/dist/cjs/components/chat-input-form/mcp-servers-menu.js +199 -0
  11. package/dist/cjs/components/chat-input-form/mcp-servers-menu.js.map +1 -0
  12. package/dist/cjs/components/chat-input-form/use-mcp-active-count.js +56 -0
  13. package/dist/cjs/components/chat-input-form/use-mcp-active-count.js.map +1 -0
  14. package/dist/cjs/components/chat-input-form.js +17 -4
  15. package/dist/cjs/components/chat-input-form.js.map +1 -1
  16. package/dist/cjs/components/modals/edit-mentor-modal/tabs/api-tab/create-api-modal.js +29 -13
  17. package/dist/cjs/components/modals/edit-mentor-modal/tabs/api-tab/create-api-modal.js.map +1 -1
  18. package/dist/cjs/components/modals/edit-mentor-modal/tabs/api-tab/labels.js +8 -0
  19. package/dist/cjs/components/modals/edit-mentor-modal/tabs/api-tab/labels.js.map +1 -1
  20. package/dist/cjs/components/modals/edit-mentor-modal/tabs/api-tab.js +23 -13
  21. package/dist/cjs/components/modals/edit-mentor-modal/tabs/api-tab.js.map +1 -1
  22. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js +755 -90
  23. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js.map +1 -1
  24. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js +386 -984
  25. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js.map +1 -1
  26. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js +16 -0
  27. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js.map +1 -0
  28. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js +6 -3
  29. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js.map +1 -1
  30. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js +463 -0
  31. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js.map +1 -0
  32. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js +329 -0
  33. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js.map +1 -0
  34. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js +20 -0
  35. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js.map +1 -0
  36. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js +744 -0
  37. package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js.map +1 -0
  38. package/dist/cjs/components/modals/user-profile-modal.js +2 -0
  39. package/dist/cjs/components/modals/user-profile-modal.js.map +1 -1
  40. package/dist/cjs/components/profile/integration/api-tab/create-api-modal.js +17 -10
  41. package/dist/cjs/components/profile/integration/api-tab/create-api-modal.js.map +1 -1
  42. package/dist/cjs/components/ui/accordion.js +40 -0
  43. package/dist/cjs/components/ui/accordion.js.map +1 -0
  44. package/dist/cjs/i18n/messages/en.json.js +156 -19
  45. package/dist/cjs/i18n/messages/en.json.js.map +1 -1
  46. package/dist/cjs/i18n/messages/es.json.js +156 -19
  47. package/dist/cjs/i18n/messages/es.json.js.map +1 -1
  48. package/dist/cjs/i18n/messages/fr.json.js +156 -19
  49. package/dist/cjs/i18n/messages/fr.json.js.map +1 -1
  50. package/dist/cjs/i18n/messages/zh.json.js +156 -19
  51. package/dist/cjs/i18n/messages/zh.json.js.map +1 -1
  52. package/dist/cjs/index.js +2 -0
  53. package/dist/cjs/index.js.map +1 -1
  54. package/dist/cjs/node_modules/.pnpm/@radix-ui_react-accordion@1.2.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types_re_b91476821b9fe8f172a947f727875267/node_modules/@radix-ui/react-accordion/dist/index.js +317 -0
  55. package/dist/cjs/node_modules/.pnpm/@radix-ui_react-accordion@1.2.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types_re_b91476821b9fe8f172a947f727875267/node_modules/@radix-ui/react-accordion/dist/index.js.map +1 -0
  56. package/dist/cjs/node_modules/.pnpm/@radix-ui_react-collapsible@1.1.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types__d84e28844e8116e61b459b615fab358a/node_modules/@radix-ui/react-collapsible/dist/index.js +173 -0
  57. package/dist/cjs/node_modules/.pnpm/@radix-ui_react-collapsible@1.1.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types__d84e28844e8116e61b459b615fab358a/node_modules/@radix-ui/react-collapsible/dist/index.js.map +1 -0
  58. package/dist/cjs/node_modules/.pnpm/@radix-ui_react-collection@1.1.4_@types_react-dom@19.2.1_@types_react@19.1.17__@types_r_7047fbfe61c39c77a2126486502acdb6/node_modules/@radix-ui/react-collection/dist/index.js +73 -0
  59. package/dist/cjs/node_modules/.pnpm/@radix-ui_react-collection@1.1.4_@types_react-dom@19.2.1_@types_react@19.1.17__@types_r_7047fbfe61c39c77a2126486502acdb6/node_modules/@radix-ui/react-collection/dist/index.js.map +1 -0
  60. package/dist/cjs/node_modules/.pnpm/css-mediaquery@0.1.2/node_modules/css-mediaquery/index.js +1 -1
  61. package/dist/cjs/node_modules/.pnpm/matchmediaquery@0.4.2/node_modules/matchmediaquery/index.js +1 -1
  62. package/dist/cjs/node_modules/.pnpm/prop-types@15.8.1/node_modules/prop-types/index.js +1 -1
  63. package/dist/cjs/web-containers/tsconfig.build.tsbuildinfo +1 -0
  64. package/dist/esm/_virtual/index2.js +2 -2
  65. package/dist/esm/_virtual/index3.js +2 -2
  66. package/dist/esm/components/chat/chat-download-dialog.js.map +1 -1
  67. package/dist/esm/components/chat-input-form/composer-plus-menu.js +94 -17
  68. package/dist/esm/components/chat-input-form/composer-plus-menu.js.map +1 -1
  69. package/dist/esm/components/chat-input-form/mcp-connect-explainer-dialog.js +27 -0
  70. package/dist/esm/components/chat-input-form/mcp-connect-explainer-dialog.js.map +1 -0
  71. package/dist/esm/components/chat-input-form/mcp-servers-menu.js +197 -0
  72. package/dist/esm/components/chat-input-form/mcp-servers-menu.js.map +1 -0
  73. package/dist/esm/components/chat-input-form/use-mcp-active-count.js +54 -0
  74. package/dist/esm/components/chat-input-form/use-mcp-active-count.js.map +1 -0
  75. package/dist/esm/components/chat-input-form.js +17 -4
  76. package/dist/esm/components/chat-input-form.js.map +1 -1
  77. package/dist/esm/components/modals/edit-mentor-modal/tabs/api-tab/create-api-modal.js +30 -14
  78. package/dist/esm/components/modals/edit-mentor-modal/tabs/api-tab/create-api-modal.js.map +1 -1
  79. package/dist/esm/components/modals/edit-mentor-modal/tabs/api-tab/labels.js +8 -0
  80. package/dist/esm/components/modals/edit-mentor-modal/tabs/api-tab/labels.js.map +1 -1
  81. package/dist/esm/components/modals/edit-mentor-modal/tabs/api-tab.js +25 -15
  82. package/dist/esm/components/modals/edit-mentor-modal/tabs/api-tab.js.map +1 -1
  83. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js +755 -92
  84. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js.map +1 -1
  85. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js +361 -968
  86. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js.map +1 -1
  87. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js +14 -0
  88. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js.map +1 -0
  89. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js +6 -3
  90. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js.map +1 -1
  91. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js +431 -0
  92. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js.map +1 -0
  93. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js +313 -0
  94. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js.map +1 -0
  95. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js +18 -0
  96. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js.map +1 -0
  97. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js +742 -0
  98. package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js.map +1 -0
  99. package/dist/esm/components/modals/user-profile-modal.js +2 -0
  100. package/dist/esm/components/modals/user-profile-modal.js.map +1 -1
  101. package/dist/esm/components/profile/integration/api-tab/create-api-modal.js +17 -10
  102. package/dist/esm/components/profile/integration/api-tab/create-api-modal.js.map +1 -1
  103. package/dist/esm/components/ui/accordion.js +16 -0
  104. package/dist/esm/components/ui/accordion.js.map +1 -0
  105. package/dist/esm/i18n/messages/en.json.js +154 -20
  106. package/dist/esm/i18n/messages/en.json.js.map +1 -1
  107. package/dist/esm/i18n/messages/es.json.js +154 -20
  108. package/dist/esm/i18n/messages/es.json.js.map +1 -1
  109. package/dist/esm/i18n/messages/fr.json.js +154 -20
  110. package/dist/esm/i18n/messages/fr.json.js.map +1 -1
  111. package/dist/esm/i18n/messages/zh.json.js +154 -20
  112. package/dist/esm/i18n/messages/zh.json.js.map +1 -1
  113. package/dist/esm/index.js +1 -0
  114. package/dist/esm/index.js.map +1 -1
  115. package/dist/esm/node_modules/.pnpm/@radix-ui_react-accordion@1.2.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types_re_b91476821b9fe8f172a947f727875267/node_modules/@radix-ui/react-accordion/dist/index.js +306 -0
  116. package/dist/esm/node_modules/.pnpm/@radix-ui_react-accordion@1.2.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types_re_b91476821b9fe8f172a947f727875267/node_modules/@radix-ui/react-accordion/dist/index.js.map +1 -0
  117. package/dist/esm/node_modules/.pnpm/@radix-ui_react-collapsible@1.1.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types__d84e28844e8116e61b459b615fab358a/node_modules/@radix-ui/react-collapsible/dist/index.js +146 -0
  118. package/dist/esm/node_modules/.pnpm/@radix-ui_react-collapsible@1.1.8_@types_react-dom@19.2.1_@types_react@19.1.17__@types__d84e28844e8116e61b459b615fab358a/node_modules/@radix-ui/react-collapsible/dist/index.js.map +1 -0
  119. package/dist/esm/node_modules/.pnpm/@radix-ui_react-collection@1.1.4_@types_react-dom@19.2.1_@types_react@19.1.17__@types_r_7047fbfe61c39c77a2126486502acdb6/node_modules/@radix-ui/react-collection/dist/index.js +71 -0
  120. package/dist/esm/node_modules/.pnpm/@radix-ui_react-collection@1.1.4_@types_react-dom@19.2.1_@types_react@19.1.17__@types_r_7047fbfe61c39c77a2126486502acdb6/node_modules/@radix-ui/react-collection/dist/index.js.map +1 -0
  121. package/dist/esm/node_modules/.pnpm/css-mediaquery@0.1.2/node_modules/css-mediaquery/index.js +1 -1
  122. package/dist/esm/node_modules/.pnpm/matchmediaquery@0.4.2/node_modules/matchmediaquery/index.js +1 -1
  123. package/dist/esm/node_modules/.pnpm/prop-types@15.8.1/node_modules/prop-types/index.js +1 -1
  124. package/dist/esm/web-containers/tsconfig.build.tsbuildinfo +1 -0
  125. package/dist/index.d.ts +31 -2
  126. package/dist/next/index.d.ts +6 -0
  127. package/dist/package.json +1 -1
  128. package/package.json +3 -3
  129. package/dist/cjs/web-containers/tsconfig.tsbuildinfo +0 -1
  130. package/dist/esm/web-containers/tsconfig.tsbuildinfo +0 -1
@@ -3,25 +3,79 @@ import { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { useT } from '../../../../../i18n/context.js';
4
4
  import '../../../../../packages/i18n/dist/index.esm.js';
5
5
  import { Button } from '../../../../ui/button.js';
6
+ import { Spinner } from '../../../../spinner/index.js';
6
7
  import { Input } from '../../../../ui/input.js';
7
8
  import { Label } from '../../../../ui/label.js';
8
9
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../../ui/dialog.js';
9
10
  import { Textarea } from '../../../../ui/textarea.js';
10
11
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../../../ui/select.js';
11
12
  import { RadioGroup, RadioGroupItem } from '../../../../ui/radio-group.js';
13
+ import { currentSignInScope, MCP_RADIO_ITEM_CLASS } from './mcp-connector-helpers.js';
14
+ import { Switch } from '../../../../ui/switch.js';
12
15
  import { toast } from 'sonner';
13
16
  import { TransportEnum } from '@iblai/iblai-api';
14
- import { useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery } from '@iblai/data-layer';
17
+ import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../../../chat/ui/alert-dialog.js';
18
+ import { emptyOAuthAppForm, storedAppEndpoints, oauthAppFormFromCredentials, sameOAuthAppForm, validateOAuthAppForm, oauthAppFormToFindFields, oauthAppFormToBody, describeApiFailure, OAuthAppFields, readOAuthAppFieldErrors, readEndpointRefusal } from './oauth-app-fields.js';
19
+ import { FieldInfo } from './field-info.js';
20
+ import { useLazyGetMCPServersQuery, useOauthFindMutation, useLazyStartOAuthFlowQuery, useCreateMCPServerMutation, usePartialUpdateMCPServerMutation, useCreateMCPServerConnectionMutation, usePatchMCPServerConnectionMutation, useGetConnectedServicesQuery, useSetMCPServerCredentialsMutation, useDeleteMCPServerCredentialsMutation, useGetMCPServerCredentialsQuery, useGetOAuthServicesQuery } from '@iblai/data-layer';
15
21
  import { useLocalStorage } from '../../../../../hooks/use-local-storage.js';
16
22
  import Image from '../../../../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/image.js';
23
+ import Unlink from '../../../../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/unlink.js';
24
+ import Link2 from '../../../../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/link-2.js';
17
25
  import Lock from '../../../../../node_modules/.pnpm/lucide-react@0.528.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/lock.js';
18
26
 
19
27
  const AUTH_SCOPE = {
20
- TENANT: 'tenant',
28
+ // The API's name for the organization-wide scope.
29
+ TENANT: 'platform',
21
30
  MENTOR: 'mentor',
22
31
  USER: 'user',
23
32
  };
24
33
  const KNOWN_TOKEN_TYPES = ['Bearer', 'Basic', 'API-Key', 'API-Token', 'Token'];
34
+ // Why discovery stopped, in our words, by the step oauth-find reports, when an
35
+ // app of the organization's own cannot help.
36
+ const DISCOVERY_MESSAGE_KEYS = {
37
+ mcp_server_url: 'mcpTabConnectorDialogs.diagnosticMcpServerUrl',
38
+ provider_name_conflict: 'mcpTabConnectorDialogs.diagnosticProviderNameConflict',
39
+ internal_error: 'mcpTabConnectorDialogs.diagnosticInternalError',
40
+ };
41
+ // The backend takes an organization's own app only when the server answered
42
+ // but registers no apps or publishes no sign-in settings, and never after a
43
+ // check that ran out of time; anything else is said under the address.
44
+ const discoveryStopMessageKey = (diagnostic) => {
45
+ var _a;
46
+ if (diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.timed_out)
47
+ return 'mcpTabConnectorDialogs.diagnosticTimedOut';
48
+ const step = diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.failed_step;
49
+ if (step === 'mcp_server_url' &&
50
+ ((_a = diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.attempts) === null || _a === void 0 ? void 0 : _a.some((attempt) => attempt.outcome === 'blocked'))) {
51
+ return 'mcpTabConnectorDialogs.diagnosticMcpServerUrlBlocked';
52
+ }
53
+ return step ? DISCOVERY_MESSAGE_KEYS[step] : undefined;
54
+ };
55
+ /** The `diagnostic` an oauth-find 400 carries, when the error has one. */
56
+ const readOAuthFindDiagnostic = (error) => {
57
+ var _a;
58
+ const diagnostic = (_a = error === null || error === void 0 ? void 0 : error.data) === null || _a === void 0 ? void 0 : _a.diagnostic;
59
+ return diagnostic && typeof diagnostic === 'object' ? diagnostic : null;
60
+ };
61
+ // A provider's sign-in page against the one discovery found, trailing slashes aside.
62
+ const sameUrl = (a, b) => !!a && !!b && a.replace(/\/+$/, '') === b.replace(/\/+$/, '');
63
+ const hostOf = (url) => {
64
+ try {
65
+ return url ? new URL(url).hostname.toLowerCase() : null;
66
+ }
67
+ catch (_a) {
68
+ return null;
69
+ }
70
+ };
71
+ /**
72
+ * The organization's provider a hand-entered app belongs to, found without
73
+ * asking: the sign-in page discovery named, else its host, else the server's host.
74
+ */
75
+ const matchProvider = (providers, authEndpoint, serverUrl) => {
76
+ var _a, _b;
77
+ return (_b = (_a = providers.find((p) => sameUrl(p.auth_url, authEndpoint))) !== null && _a !== void 0 ? _a : providers.find((p) => !!hostOf(p.auth_url) && hostOf(p.auth_url) === hostOf(authEndpoint))) !== null && _b !== void 0 ? _b : providers.find((p) => !!hostOf(p.url) && hostOf(p.url) === hostOf(serverUrl));
78
+ };
25
79
  const normalizeTransportValue = (value) => {
26
80
  const candidate = value === null || value === void 0 ? void 0 : value.toLowerCase();
27
81
  if (candidate === TransportEnum.SSE)
@@ -93,7 +147,8 @@ const validateCustomTokenType = (value) => {
93
147
  }
94
148
  return { isValid: true, error: null };
95
149
  };
96
- function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editingConnectionId, tenantKey, username, mentorId, onOAuthComplete, }) {
150
+ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editingConnectionId, tenantKey, username, mentorId, onOAuthComplete, restricted = false, onRestrictedSave, clearAgentScopeOverride, openOnSignInApp = false, onAuthScopeChanged, connection, }) {
151
+ var _a, _b, _c, _d, _e;
97
152
  const t = useT();
98
153
  // Form state
99
154
  const [connectorName, setConnectorName] = useState('');
@@ -101,8 +156,10 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
101
156
  const [connectorDescription, setConnectorDescription] = useState('');
102
157
  const [transport, setTransport] = useState(TransportEnum.STREAMABLE_HTTP);
103
158
  const [authMethod, setAuthMethod] = useState('no-auth');
104
- const [authScope, setAuthScope] = useState(AUTH_SCOPE.TENANT);
159
+ // The backend default, and the safer choice: each user signs in with their own account.
160
+ const [authScope, setAuthScope] = useState(AUTH_SCOPE.USER);
105
161
  const [connectorScope, setConnectorScope] = useState('tenant');
162
+ const [userCanDisable, setUserCanDisable] = useState(true);
106
163
  const [tokenType, setTokenType] = useState('Bearer');
107
164
  const [customTokenType, setCustomTokenType] = useState('');
108
165
  const [tokenValue, setTokenValue] = useState('');
@@ -111,6 +168,22 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
111
168
  const [credentialsMasked, setCredentialsMasked] = useState(false);
112
169
  const [isSubmitting, setIsSubmitting] = useState(false);
113
170
  const [oauthUrlError, setOauthUrlError] = useState(null);
171
+ // A server that didn't answer, or answered too slowly, may answer the next
172
+ // Connect at the same address.
173
+ const [addressErrorRetryable, setAddressErrorRetryable] = useState(false);
174
+ const [nameError, setNameError] = useState(null);
175
+ // The sign-in app fields show only when the connector needs an app entered by hand.
176
+ const [signInAppOpen, setSignInAppOpen] = useState(false);
177
+ const [signInReason, setSignInReason] = useState(null);
178
+ const [oauthApp, setOauthApp] = useState(() => emptyOAuthAppForm());
179
+ const [oauthServiceId, setOauthServiceId] = useState(null);
180
+ const [signInError, setSignInError] = useState(null);
181
+ const [appFieldErrors, setAppFieldErrors] = useState({});
182
+ // Discovery found no sign-in settings, so the app's addresses are the only ones there are.
183
+ const [endpointsUndiscovered, setEndpointsUndiscovered] = useState(false);
184
+ const [confirmRemoveApp, setConfirmRemoveApp] = useState(false);
185
+ // The sign-in page discovery found, to tell the provider of an app entered by hand.
186
+ const [discoveredAuthEndpoint, setDiscoveredAuthEndpoint] = useState(null);
114
187
  const [originalTokenPlaceholder, setOriginalTokenPlaceholder] = useState(null);
115
188
  // API hooks
116
189
  const [getMCPServers] = useLazyGetMCPServersQuery();
@@ -121,10 +194,64 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
121
194
  const [createMCPServerConnection] = useCreateMCPServerConnectionMutation();
122
195
  const [patchMCPServerConnection] = usePatchMCPServerConnectionMutation();
123
196
  const { refetch: refetchConnected } = useGetConnectedServicesQuery({ org: tenantKey || '', userId: username || '' }, { skip: !tenantKey || !username });
197
+ const [saveCredentials] = useSetMCPServerCredentialsMutation();
198
+ const [deleteCredentials] = useDeleteMCPServerCredentialsMutation();
199
+ // The stored sign-in app of the connector being edited (secrets masked);
200
+ // `currentData`, so the previous connector's app never shows on this one.
201
+ const credentialsQuery = useGetMCPServerCredentialsQuery({ org: tenantKey || '', userId: username || '', id: (_a = editingServer === null || editingServer === void 0 ? void 0 : editingServer.id) !== null && _a !== void 0 ? _a : 0 }, { skip: !open || !editingServer || authMethod !== 'oauth' || !tenantKey || !username });
202
+ // The stored app belongs to the stored address's provider; another address may be another's.
203
+ const storedAppApplies = !!editingServer && connectorServer.trim() === editingServer.url;
204
+ const storedApp = storedAppApplies && ((_b = credentialsQuery.currentData) === null || _b === void 0 ? void 0 : _b.auth_type) === 'oauth2'
205
+ ? credentialsQuery.currentData
206
+ : undefined;
207
+ const appConfigured = !!(storedApp === null || storedApp === void 0 ? void 0 : storedApp.configured);
208
+ // Only an app entered by hand is what the fields edit; one the service registered for us is not shown.
209
+ const manualAppConfigured = appConfigured && (storedApp === null || storedApp === void 0 ? void 0 : storedApp.source) !== 'dcr';
210
+ // Nobody can sign in: no app of the organization's, none shared by ibl.ai.
211
+ const needsApp = !!storedApp && !storedApp.configured && !storedApp.shared_client_allowed;
212
+ // The app goes to oauth-find, which finds or creates its provider itself.
213
+ const findsWithApp = signInReason === 'registration' || signInReason === 'metadata';
214
+ const appEndpoints = signInReason === 'metadata' || (!findsWithApp && endpointsUndiscovered)
215
+ ? 'required'
216
+ : findsWithApp
217
+ ? 'hidden'
218
+ : storedAppEndpoints(storedApp);
219
+ // The organization's sign-in providers, to tell the connector's provider without asking.
220
+ const providersQuery = useGetOAuthServicesQuery({ org: tenantKey || '' }, { skip: !open || !tenantKey || !signInAppOpen || restricted || findsWithApp });
221
+ const storedService = ((_c = editingServer === null || editingServer === void 0 ? void 0 : editingServer.oauth_service_data) === null || _c === void 0 ? void 0 : _c.id) === oauthServiceId
222
+ ? editingServer === null || editingServer === void 0 ? void 0 : editingServer.oauth_service_data
223
+ : undefined;
224
+ const chosenService = storedService !== null && storedService !== void 0 ? storedService : ((_d = providersQuery.data) !== null && _d !== void 0 ? _d : [])
225
+ .flatMap((provider) => provider.services)
226
+ .find((service) => service.id === oauthServiceId);
227
+ // Whether the app entered by hand has a provider to be stored under.
228
+ const providerState = oauthServiceId !== null
229
+ ? 'known'
230
+ : providersQuery.isError
231
+ ? 'failed'
232
+ : providersQuery.data === undefined
233
+ ? 'checking'
234
+ : 'unknown';
235
+ // Whose developer settings the redirect URI goes into.
236
+ const providerLabel = (chosenService === null || chosenService === void 0 ? void 0 : chosenService.display_name) || connectorName.trim() || t('mcpTabConnectorDialogs.oauth');
124
237
  // Use localStorage hook for pending OAuth server data
125
238
  const [, setPendingOAuthServer, removePendingOAuthServer] = useLocalStorage('oauth_pending_server', null);
126
239
  const fileInputRef = useRef(null);
240
+ const contentRef = useRef(null);
241
+ const nameInputRef = useRef(null);
127
242
  const objectUrlRef = useRef(null);
243
+ // A connector created by hand whose sign-in app then failed to save: the retry updates it.
244
+ const createdServerRef = useRef(null);
245
+ // The service oauth-find made for the organization's app, for a retry after
246
+ // the connector could not be saved: the app is stored already, and a second
247
+ // oauth-find with it would be refused.
248
+ // The app it was found with is kept too: one corrected since is saved over it.
249
+ const foundServiceRef = useRef(null);
250
+ // The address the effect below last handled, and the reason the fields are
251
+ // open, read there without re-running it.
252
+ const handledAddressRef = useRef(null);
253
+ const signInReasonRef = useRef(signInReason);
254
+ signInReasonRef.current = signInReason;
128
255
  // Cleanup object URL on unmount
129
256
  useEffect(() => {
130
257
  return () => {
@@ -144,8 +271,9 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
144
271
  setConnectorDescription('');
145
272
  setTransport(TransportEnum.STREAMABLE_HTTP);
146
273
  setAuthMethod('no-auth');
147
- setAuthScope(AUTH_SCOPE.TENANT);
274
+ setAuthScope(AUTH_SCOPE.USER);
148
275
  setConnectorScope('tenant');
276
+ setUserCanDisable(true);
149
277
  setTokenType('Bearer');
150
278
  setCustomTokenType('');
151
279
  setTokenValue('');
@@ -153,13 +281,26 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
153
281
  setImageFile(null);
154
282
  setCredentialsMasked(false);
155
283
  setOauthUrlError(null);
284
+ setNameError(null);
285
+ setSignInAppOpen(false);
286
+ setSignInReason(null);
287
+ setOauthApp(emptyOAuthAppForm());
288
+ setOauthServiceId(null);
289
+ setSignInError(null);
290
+ setAppFieldErrors({});
291
+ setEndpointsUndiscovered(false);
292
+ setConfirmRemoveApp(false);
293
+ setDiscoveredAuthEndpoint(null);
294
+ createdServerRef.current = null;
295
+ foundServiceRef.current = null;
296
+ handledAddressRef.current = null;
156
297
  setOriginalTokenPlaceholder(null);
157
298
  if (fileInputRef.current)
158
299
  fileInputRef.current.value = '';
159
300
  }, []);
160
301
  // Pre-fill form when editing
161
302
  useEffect(() => {
162
- var _a, _b;
303
+ var _a, _b, _c, _d;
163
304
  if (!open)
164
305
  return;
165
306
  if (editingServer) {
@@ -175,11 +316,22 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
175
316
  setConnectorImage(editingServer.image || null);
176
317
  setImageFile(null);
177
318
  setConnectorScope(editingServer.mentor ? 'this-mentor' : 'tenant');
319
+ setUserCanDisable(editingServer.user_can_disable !== false);
178
320
  // Check auth type and set appropriate method
179
321
  const authType = (_b = editingServer.auth_type) === null || _b === void 0 ? void 0 : _b.toLowerCase();
180
322
  if (authType === 'oauth2') {
181
323
  setAuthMethod('oauth');
182
- setAuthScope(editingServer.auth_scope || AUTH_SCOPE.TENANT);
324
+ // The scope in force for this agent: its own choice, else the connector's.
325
+ setAuthScope(editingServer.effective_auth_scope || editingServer.auth_scope || AUTH_SCOPE.USER);
326
+ setOauthServiceId((_d = (_c = editingServer.oauth_service_data) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null);
327
+ // A stored app entered by hand, once read, opens the fields too.
328
+ setSignInAppOpen(openOnSignInApp);
329
+ setSignInReason(null);
330
+ setOauthApp(emptyOAuthAppForm());
331
+ setSignInError(null);
332
+ setAppFieldErrors({});
333
+ setEndpointsUndiscovered(false);
334
+ setDiscoveredAuthEndpoint(null);
183
335
  setTokenType('Bearer');
184
336
  setCustomTokenType('');
185
337
  setTokenValue('');
@@ -210,6 +362,7 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
210
362
  }
211
363
  else {
212
364
  setAuthMethod('no-auth');
365
+ setOauthServiceId(null);
213
366
  setTokenType('Bearer');
214
367
  setCustomTokenType('');
215
368
  setTokenValue('');
@@ -221,13 +374,66 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
221
374
  else {
222
375
  resetForm();
223
376
  }
224
- }, [editingServer, open, resetForm]);
225
- // Clear OAuth error when URL changes
377
+ }, [editingServer, open, openOnSignInApp, resetForm]);
378
+ // The address decides the sign-in app. A changed one means discovery runs
379
+ // again for it, so what the last one said, and the stored provider and app,
380
+ // no longer apply; back on the stored address, they do (an app entered by
381
+ // hand opens with its fields, prefilled). Runs once on open, before the prefill.
226
382
  useEffect(() => {
227
- if (authMethod === 'oauth') {
228
- setOauthUrlError(null);
383
+ var _a, _b, _c;
384
+ if (authMethod !== 'oauth') {
385
+ handledAddressRef.current = null;
386
+ return;
387
+ }
388
+ const address = `${(_a = editingServer === null || editingServer === void 0 ? void 0 : editingServer.id) !== null && _a !== void 0 ? _a : ''} ${connectorServer.trim()} ${openOnSignInApp}`;
389
+ const sameAddress = handledAddressRef.current === address;
390
+ handledAddressRef.current = address;
391
+ // The stored app refetched while an app is being entered (oauth-find with
392
+ // one refreshes it) changes nothing typed.
393
+ if (sameAddress && signInReasonRef.current)
394
+ return;
395
+ setOauthUrlError(null);
396
+ setSignInReason(null);
397
+ setSignInError(null);
398
+ setAppFieldErrors({});
399
+ setEndpointsUndiscovered(false);
400
+ setDiscoveredAuthEndpoint(null);
401
+ if (editingServer && connectorServer.trim() === editingServer.url) {
402
+ setOauthServiceId((_c = (_b = editingServer.oauth_service_data) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null);
403
+ setSignInAppOpen(openOnSignInApp || manualAppConfigured);
404
+ setOauthApp(manualAppConfigured && storedApp
405
+ ? oauthAppFormFromCredentials(storedApp)
406
+ : emptyOAuthAppForm());
407
+ return;
229
408
  }
230
- }, [connectorServer, authMethod]);
409
+ setSignInAppOpen(false);
410
+ setOauthServiceId(null);
411
+ setOauthApp(emptyOAuthAppForm());
412
+ // `storedApp` changes identity with every fetch; what matters is its content.
413
+ // eslint-disable-next-line react-hooks/exhaustive-deps
414
+ }, [
415
+ connectorServer,
416
+ authMethod,
417
+ editingServer,
418
+ openOnSignInApp,
419
+ manualAppConfigured,
420
+ storedApp === null || storedApp === void 0 ? void 0 : storedApp.updated_at,
421
+ ]);
422
+ // A new connector's provider, told from what discovery found; never asked.
423
+ useEffect(() => {
424
+ if (!signInAppOpen || findsWithApp || oauthServiceId !== null || !providersQuery.data)
425
+ return;
426
+ const owner = matchProvider(providersQuery.data, discoveredAuthEndpoint, connectorServer);
427
+ if (owner === null || owner === void 0 ? void 0 : owner.services[0])
428
+ setOauthServiceId(owner.services[0].id);
429
+ }, [
430
+ signInAppOpen,
431
+ findsWithApp,
432
+ oauthServiceId,
433
+ providersQuery.data,
434
+ discoveredAuthEndpoint,
435
+ connectorServer,
436
+ ]);
231
437
  const handleImageUpload = useCallback((event) => {
232
438
  var _a;
233
439
  const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
@@ -258,7 +464,7 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
258
464
  objectUrlRef.current = previewUrl;
259
465
  setConnectorImage(previewUrl);
260
466
  setImageFile(file);
261
- }, []);
467
+ }, [t]);
262
468
  // Handle OAuth flow
263
469
  // Note: tenantKey and username are validated in handleSubmit before this is called
264
470
  const handleOAuthFlow = useCallback(async (server, provider, service) => {
@@ -418,11 +624,10 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
418
624
  setPendingOAuthServer,
419
625
  removePendingOAuthServer,
420
626
  onOAuthComplete,
421
- resetForm,
422
- onClose,
627
+ t,
423
628
  ]);
424
629
  const handleSubmit = useCallback(async () => {
425
- var _a, _b;
630
+ var _a, _b, _c, _d, _e, _f, _g;
426
631
  if (isSubmitting)
427
632
  return;
428
633
  const trimmedName = connectorName.trim();
@@ -438,60 +643,369 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
438
643
  toast.error(t('mcpTabConnectorDialogs.missingRequiredParams'));
439
644
  return;
440
645
  }
441
- setIsSubmitting(true);
442
- setOauthUrlError(null);
443
- try {
444
- // Editing an existing OAuth server
445
- if (editingServer) {
446
- const urlChanged = editingServer.url !== trimmedServer;
447
- // Update server metadata (name, description, transport, auth_scope, mentor)
448
- await updateMCPServer({
449
- id: editingServer.id,
450
- org: tenantKey,
451
- userId: username,
452
- body: {
646
+ const callbackUrl = `${window.location.origin}/google-oauth-callback/`;
647
+ // The form opens on the scope in force for this agent, which may be the
648
+ // agent's own choice. Left as it opened, the connector keeps its own scope;
649
+ // only a scope the person changed is written for every agent.
650
+ const scopeEdited = !!editingServer && authScope !== currentSignInScope(editingServer);
651
+ const connectorAuthScope = editingServer && !scopeEdited ? editingServer.auth_scope || AUTH_SCOPE.USER : authScope;
652
+ const serverBody = {
653
+ name: trimmedName,
654
+ url: trimmedServer,
655
+ transport: transport,
656
+ description: connectorDescription.trim() || undefined,
657
+ auth_type: 'oauth2',
658
+ auth_scope: connectorAuthScope,
659
+ mentor: connectorScope === 'this-mentor' && mentorId ? mentorId : null,
660
+ user_can_disable: userCanDisable,
661
+ };
662
+ // The connector's fields, then the connection's scope when it changed.
663
+ const updateConnector = async (server, body, { signInFollows = false } = {}) => {
664
+ await updateMCPServer({ id: server.id, org: tenantKey, userId: username, body }).unwrap();
665
+ if (!scopeEdited)
666
+ return;
667
+ if (currentSignInScope(server) !== (server.auth_scope || AUTH_SCOPE.USER)) {
668
+ await (clearAgentScopeOverride === null || clearAgentScopeOverride === void 0 ? void 0 : clearAgentScopeOverride(server));
669
+ }
670
+ if (editingConnectionId) {
671
+ try {
672
+ await patchMCPServerConnection({
673
+ org: tenantKey,
674
+ userId: username,
675
+ id: editingConnectionId,
676
+ scope: authScope,
677
+ mentor: authScope === AUTH_SCOPE.MENTOR && mentorId ? mentorId : '',
678
+ ...(authScope !== AUTH_SCOPE.MENTOR && { user: username }),
679
+ }).unwrap();
680
+ }
681
+ catch (_a) {
682
+ // Non-blocking: server was updated, connection patch is best-effort
683
+ }
684
+ }
685
+ // A sign-in the dialog starts next creates the new scope's connection itself.
686
+ if (!signInFollows) {
687
+ await (onAuthScopeChanged === null || onAuthScopeChanged === void 0 ? void 0 : onAuthScopeChanged(server, authScope));
688
+ }
689
+ };
690
+ // An app entered by hand and left as stored is not written again.
691
+ const appUnchanged = manualAppConfigured &&
692
+ !!storedApp &&
693
+ sameOAuthAppForm(oauthApp, oauthAppFormFromCredentials(storedApp));
694
+ const saveSignInApp = async (serverId) => {
695
+ if (appUnchanged)
696
+ return;
697
+ await saveCredentials({
698
+ org: tenantKey,
699
+ userId: username,
700
+ id: serverId,
701
+ body: oauthAppFormToBody(oauthApp, manualAppConfigured, appEndpoints),
702
+ }).unwrap();
703
+ };
704
+ // The backend's word on the app: each field's under its input, the
705
+ // address's and the name's under theirs, anything else as one sentence.
706
+ const showAppFailure = (error) => {
707
+ const fieldErrors = readOAuthAppFieldErrors(error);
708
+ setAppFieldErrors(fieldErrors);
709
+ const body = error === null || error === void 0 ? void 0 : error.data;
710
+ const joined = (messages) => (Array.isArray(messages) ? messages.join(' ') : null);
711
+ const addressError = joined(body === null || body === void 0 ? void 0 : body.url);
712
+ const connectorNameError = joined(body === null || body === void 0 ? void 0 : body.name);
713
+ if (addressError) {
714
+ setOauthUrlError(addressError);
715
+ setAddressErrorRetryable(false);
716
+ }
717
+ if (connectorNameError)
718
+ setNameError(connectorNameError);
719
+ if (Object.keys(fieldErrors).length > 0 || addressError || connectorNameError)
720
+ return;
721
+ const diagnostic = readOAuthFindDiagnostic(error);
722
+ const step = diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.failed_step;
723
+ const hint = step === 'manual_client' ? diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.hint : null;
724
+ if (hint && /publishes no OAuth metadata/i.test(hint)) {
725
+ // The server has no sign-in settings after all: its addresses are needed.
726
+ setEndpointsUndiscovered(true);
727
+ setSignInReason('metadata');
728
+ setAppFieldErrors({ authorizationUrl: t('mcpOAuthApp.endpointsRequired') });
729
+ return;
730
+ }
731
+ const refusal = readEndpointRefusal(hint !== null && hint !== void 0 ? hint : (typeof (body === null || body === void 0 ? void 0 : body.error) === 'string' ? body.error : null), t, providerLabel);
732
+ if (refusal && appEndpoints !== 'hidden') {
733
+ setAppFieldErrors({ [refusal.field]: refusal.message });
734
+ return;
735
+ }
736
+ const key = discoveryStopMessageKey(diagnostic);
737
+ setSignInError(hint || (key && t(key)) || describeApiFailure(error, t) || t('mcpOAuthApp.saveFailed'));
738
+ };
739
+ // Only the settings for this agent, and the sign-in app when its fields
740
+ // are open: the connector itself is not written.
741
+ if (restricted && editingServer) {
742
+ if (signInAppOpen) {
743
+ if (!oauthServiceId) {
744
+ setSignInError(t('mcpTabConnectorDialogs.signInProviderUnknown'));
745
+ return;
746
+ }
747
+ // An app left as stored is not written, so nothing in it is checked either.
748
+ const appError = appUnchanged
749
+ ? null
750
+ : validateOAuthAppForm(oauthApp, manualAppConfigured, t, appEndpoints);
751
+ if (appError) {
752
+ setAppFieldErrors({ [appError.field]: appError.message });
753
+ return;
754
+ }
755
+ }
756
+ setIsSubmitting(true);
757
+ setSignInError(null);
758
+ setAppFieldErrors({});
759
+ let appPending = signInAppOpen && !appUnchanged;
760
+ try {
761
+ if (appPending) {
762
+ await saveSignInApp(editingServer.id);
763
+ appPending = false;
764
+ toast.success(t('mcpTabConnectorDialogs.appCredentialsSaved'));
765
+ if (onOAuthComplete)
766
+ onOAuthComplete();
767
+ }
768
+ await (onRestrictedSave === null || onRestrictedSave === void 0 ? void 0 : onRestrictedSave(editingServer, {
769
+ scope: authScope,
770
+ userCanDisable,
771
+ }));
772
+ setIsSubmitting(false);
773
+ resetForm();
774
+ onClose();
775
+ }
776
+ catch (error) {
777
+ // A refused choice was said by the host; only the app's failure reads here.
778
+ if (appPending)
779
+ showAppFailure(error);
780
+ }
781
+ finally {
782
+ setIsSubmitting(false);
783
+ }
784
+ return;
785
+ }
786
+ // The organization's own app for a service that cannot register one
787
+ // itself (guide §5.4): oauth-find again with the app, which stores it and
788
+ // returns the service; then the connector, then the sign-in.
789
+ if (signInAppOpen && findsWithApp) {
790
+ const appError = validateOAuthAppForm(oauthApp, false, t, appEndpoints);
791
+ if (appError) {
792
+ setAppFieldErrors({ [appError.field]: appError.message });
793
+ return;
794
+ }
795
+ setIsSubmitting(true);
796
+ setOauthUrlError(null);
797
+ setNameError(null);
798
+ setSignInError(null);
799
+ setAppFieldErrors({});
800
+ try {
801
+ const earlier = ((_a = foundServiceRef.current) === null || _a === void 0 ? void 0 : _a.url) === trimmedServer ? foundServiceRef.current : null;
802
+ // Stored with the app as it was then; corrected since, it is saved again below.
803
+ const appChanged = !!earlier &&
804
+ (!sameOAuthAppForm(earlier.app, oauthApp) || earlier.endpoints !== appEndpoints);
805
+ let found = (_b = earlier === null || earlier === void 0 ? void 0 : earlier.service) !== null && _b !== void 0 ? _b : null;
806
+ if (!found) {
807
+ found = await oauthFind({
808
+ org: tenantKey,
809
+ userId: username,
810
+ url: trimmedServer,
453
811
  name: trimmedName,
812
+ ...oauthAppFormToFindFields(oauthApp, appEndpoints),
813
+ }).unwrap();
814
+ foundServiceRef.current = {
454
815
  url: trimmedServer,
455
- transport: transport,
456
- description: connectorDescription.trim() || undefined,
457
- auth_type: 'oauth2',
458
- auth_scope: authScope,
459
- mentor: connectorScope === 'this-mentor' && mentorId ? mentorId : null,
460
- },
461
- }).unwrap();
462
- // If auth_scope changed, update the connection's mentor field
463
- const scopeChanged = editingServer.auth_scope !== authScope;
464
- if (scopeChanged && editingConnectionId) {
465
- try {
466
- await patchMCPServerConnection({
816
+ service: found,
817
+ app: oauthApp,
818
+ endpoints: appEndpoints,
819
+ };
820
+ }
821
+ const body = { ...serverBody, oauth_service: found.id };
822
+ let server;
823
+ if (editingServer) {
824
+ await updateConnector(editingServer, body, { signInFollows: true });
825
+ server = { ...editingServer, url: trimmedServer };
826
+ }
827
+ else if (createdServerRef.current) {
828
+ await updateMCPServer({
829
+ id: createdServerRef.current.id,
830
+ org: tenantKey,
831
+ userId: username,
832
+ body,
833
+ }).unwrap();
834
+ server = { ...createdServerRef.current, ...body };
835
+ }
836
+ else {
837
+ server = await createMCPServer({ org: tenantKey, userId: username, body }).unwrap();
838
+ createdServerRef.current = server;
839
+ }
840
+ if (appChanged) {
841
+ await saveCredentials({
842
+ org: tenantKey,
843
+ userId: username,
844
+ id: server.id,
845
+ body: oauthAppFormToBody(oauthApp, false, appEndpoints),
846
+ }).unwrap();
847
+ foundServiceRef.current = {
848
+ url: trimmedServer,
849
+ service: found,
850
+ app: oauthApp,
851
+ endpoints: appEndpoints,
852
+ };
853
+ }
854
+ await handleOAuthFlow(server, found.oauth_provider, found.name);
855
+ setIsSubmitting(false);
856
+ resetForm();
857
+ onClose();
858
+ }
859
+ catch (error) {
860
+ const diagnostic = readOAuthFindDiagnostic(error);
861
+ if ((diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.failed_step) === 'tenant_client_configured') {
862
+ // The organization has an app for this service already: it is
863
+ // edited through the connector's credentials, not stored again.
864
+ setDiscoveredAuthEndpoint((_d = (_c = diagnostic.discovered) === null || _c === void 0 ? void 0 : _c.authorization_endpoint) !== null && _d !== void 0 ? _d : null);
865
+ setSignInReason('existing');
866
+ }
867
+ else {
868
+ showAppFailure(error);
869
+ }
870
+ }
871
+ finally {
872
+ setIsSubmitting(false);
873
+ }
874
+ return;
875
+ }
876
+ // A hand-entered sign-in app: no discovery. The connector keeps its
877
+ // provider, or gets the one told from what discovery found.
878
+ if (signInAppOpen) {
879
+ if (!oauthServiceId) {
880
+ setSignInError(t('mcpTabConnectorDialogs.signInProviderUnknown'));
881
+ return;
882
+ }
883
+ // An app left as stored is not written, so nothing in it is checked either.
884
+ const appError = appUnchanged
885
+ ? null
886
+ : validateOAuthAppForm(oauthApp, manualAppConfigured, t, appEndpoints);
887
+ if (appError) {
888
+ setAppFieldErrors({ [appError.field]: appError.message });
889
+ return;
890
+ }
891
+ setIsSubmitting(true);
892
+ setOauthUrlError(null);
893
+ setNameError(null);
894
+ setSignInError(null);
895
+ setAppFieldErrors({});
896
+ try {
897
+ if (editingServer) {
898
+ await updateConnector(editingServer, {
899
+ ...serverBody,
900
+ ...(oauthServiceId !== ((_e = editingServer.oauth_service_data) === null || _e === void 0 ? void 0 : _e.id) && {
901
+ oauth_service: oauthServiceId,
902
+ }),
903
+ });
904
+ await saveSignInApp(editingServer.id);
905
+ toast.success(t('mcpTabConnectorDialogs.connectorUpdatedSuccess'));
906
+ }
907
+ else {
908
+ const body = { ...serverBody, oauth_service: oauthServiceId };
909
+ let server = createdServerRef.current;
910
+ if (server) {
911
+ // Created on an earlier try whose app then failed to save.
912
+ await updateMCPServer({
913
+ id: server.id,
467
914
  org: tenantKey,
468
915
  userId: username,
469
- id: editingConnectionId,
470
- scope: authScope,
471
- mentor: authScope === AUTH_SCOPE.MENTOR && mentorId ? mentorId : '',
472
- ...(authScope !== AUTH_SCOPE.MENTOR && { user: username }),
916
+ body,
473
917
  }).unwrap();
474
918
  }
475
- catch (_c) {
476
- // Non-blocking: server was updated, connection patch is best-effort
919
+ else {
920
+ server = await createMCPServer({ org: tenantKey, userId: username, body }).unwrap();
921
+ createdServerRef.current = server;
922
+ }
923
+ await saveSignInApp(server.id);
924
+ toast.success(t('mcpTabConnectorDialogs.appCredentialsSaved'));
925
+ // As after discovery: the sign-in starts right away.
926
+ if (chosenService) {
927
+ await handleOAuthFlow(server, chosenService.oauth_provider, chosenService.name);
477
928
  }
478
929
  }
479
- // Only restart OAuth flow if the URL changed
480
- if (urlChanged) {
930
+ if (onOAuthComplete)
931
+ onOAuthComplete();
932
+ setIsSubmitting(false);
933
+ resetForm();
934
+ onClose();
935
+ }
936
+ catch (error) {
937
+ showAppFailure(error);
938
+ }
939
+ finally {
940
+ setIsSubmitting(false);
941
+ }
942
+ return;
943
+ }
944
+ setIsSubmitting(true);
945
+ setOauthUrlError(null);
946
+ setSignInError(null);
947
+ // Discovery stopped. A service that registers no apps itself, or publishes
948
+ // no sign-in settings, or one the organization already has an app for,
949
+ // shows the app fields with what was found; for anything else an app of
950
+ // the organization's own cannot help, so it is one sentence under the address.
951
+ const handleDiscoveryFailure = (error, fallbackKey) => {
952
+ var _a, _b, _c, _d, _e;
953
+ const diagnostic = readOAuthFindDiagnostic(error);
954
+ const step = (diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.timed_out) ? null : diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.failed_step;
955
+ setEndpointsUndiscovered(step === 'authorization_server_metadata');
956
+ const reason = step === 'client_registration'
957
+ ? 'registration'
958
+ : step === 'authorization_server_metadata'
959
+ ? 'metadata'
960
+ : step === 'tenant_client_configured'
961
+ ? 'existing'
962
+ : null;
963
+ if (reason) {
964
+ const found = (_b = (_a = diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.discovered) === null || _a === void 0 ? void 0 : _a.scopes_supported) !== null && _b !== void 0 ? _b : [];
965
+ setDiscoveredAuthEndpoint((_d = (_c = diagnostic === null || diagnostic === void 0 ? void 0 : diagnostic.discovered) === null || _c === void 0 ? void 0 : _c.authorization_endpoint) !== null && _d !== void 0 ? _d : null);
966
+ setOauthApp((form) => ({ ...form, scopes: form.scopes || found.join(' ') }));
967
+ setSignInReason(reason);
968
+ setSignInAppOpen(true);
969
+ return;
970
+ }
971
+ const key = discoveryStopMessageKey(diagnostic);
972
+ setAddressErrorRetryable(key === 'mcpTabConnectorDialogs.diagnosticTimedOut' ||
973
+ key === 'mcpTabConnectorDialogs.diagnosticMcpServerUrl');
974
+ // No reason in the answer (a server error, a refused or lost request):
975
+ // its status says more than blaming the address would.
976
+ const status = error === null || error === void 0 ? void 0 : error.status;
977
+ setOauthUrlError(key
978
+ ? t(key)
979
+ : getValidationErrorMessage((_e = describeApiFailure(error, t)) !== null && _e !== void 0 ? _e : extractApiErrorMessage(error), typeof status === 'number'
980
+ ? t('mcpTabConnectorDialogs.discoveryFailedStatus', { status })
981
+ : typeof status === 'string' && status
982
+ ? t('mcpTabConnectorDialogs.discoveryUnreachable')
983
+ : t(fallbackKey)));
984
+ };
985
+ try {
986
+ // Editing an existing OAuth server
987
+ if (editingServer) {
988
+ const urlChanged = editingServer.url !== trimmedServer;
989
+ // A new address, or no app anyone can sign in through: find and
990
+ // register again.
991
+ const signInFollows = urlChanged || needsApp;
992
+ await updateConnector(editingServer, serverBody, { signInFollows });
993
+ if (signInFollows) {
481
994
  // Re-check featured servers for the new URL
482
- const existingServersResult = await getMCPServers({
483
- org: tenantKey,
484
- userId: username,
485
- url: trimmedServer,
486
- isFeatured: true,
487
- }).unwrap();
488
- const matchingServer = (_a = existingServersResult.results) === null || _a === void 0 ? void 0 : _a.find((s) => { var _a; return s.url === trimmedServer && ((_a = s.auth_type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'oauth2'; });
995
+ const existingServersResult = urlChanged
996
+ ? await getMCPServers({
997
+ org: tenantKey,
998
+ userId: username,
999
+ url: trimmedServer,
1000
+ isFeatured: true,
1001
+ }).unwrap()
1002
+ : undefined;
1003
+ const matchingServer = (_f = existingServersResult === null || existingServersResult === void 0 ? void 0 : existingServersResult.results) === null || _f === void 0 ? void 0 : _f.find((s) => { var _a; return s.url === trimmedServer && ((_a = s.auth_type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'oauth2'; });
489
1004
  if (matchingServer === null || matchingServer === void 0 ? void 0 : matchingServer.oauth_service_data) {
490
1005
  const oauthData = matchingServer.oauth_service_data;
491
1006
  await handleOAuthFlow(matchingServer, oauthData.oauth_provider, oauthData.name);
492
1007
  }
493
1008
  else {
494
- const callbackUrl = `${window.location.origin}/google-oauth-callback/`;
495
1009
  const oauthFindResult = await oauthFind({
496
1010
  org: tenantKey,
497
1011
  userId: username,
@@ -499,6 +1013,13 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
499
1013
  name: trimmedName,
500
1014
  callback_url: callbackUrl,
501
1015
  }).unwrap();
1016
+ // The connector now signs in through what discovery found.
1017
+ await updateMCPServer({
1018
+ id: editingServer.id,
1019
+ org: tenantKey,
1020
+ userId: username,
1021
+ body: { oauth_service: oauthFindResult.id },
1022
+ }).unwrap();
502
1023
  await handleOAuthFlow({ ...editingServer, url: trimmedServer }, oauthFindResult.oauth_provider, oauthFindResult.name);
503
1024
  }
504
1025
  }
@@ -520,7 +1041,7 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
520
1041
  url: trimmedServer,
521
1042
  isFeatured: true,
522
1043
  }).unwrap();
523
- const existingServer = (_b = existingServersResult.results) === null || _b === void 0 ? void 0 : _b.find((s) => { var _a; return s.url === trimmedServer && ((_a = s.auth_type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'oauth2'; });
1044
+ const existingServer = (_g = existingServersResult.results) === null || _g === void 0 ? void 0 : _g.find((s) => { var _a; return s.url === trimmedServer && ((_a = s.auth_type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'oauth2'; });
524
1045
  if (existingServer && existingServer.oauth_service_data) {
525
1046
  // Use existing server
526
1047
  const oauthData = existingServer.oauth_service_data;
@@ -532,7 +1053,6 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
532
1053
  }
533
1054
  // If not found, call oauth-find endpoint
534
1055
  try {
535
- const callbackUrl = `${window.location.origin}/google-oauth-callback/`;
536
1056
  const oauthFindResult = await oauthFind({
537
1057
  org: tenantKey,
538
1058
  userId: username,
@@ -540,21 +1060,22 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
540
1060
  name: trimmedName,
541
1061
  callback_url: callbackUrl,
542
1062
  }).unwrap();
543
- // Create new MCP server with OAuth
544
- const newServer = await createMCPServer({
545
- org: tenantKey,
546
- userId: username,
547
- body: {
548
- name: trimmedName,
549
- url: trimmedServer,
550
- transport: transport,
551
- description: connectorDescription.trim() || undefined,
552
- auth_type: 'oauth2',
553
- auth_scope: authScope,
554
- oauth_service: oauthFindResult.id,
555
- mentor: connectorScope === 'this-mentor' && mentorId ? mentorId : null,
556
- },
557
- }).unwrap();
1063
+ const body = { ...serverBody, oauth_service: oauthFindResult.id };
1064
+ let newServer = createdServerRef.current;
1065
+ if (newServer) {
1066
+ // Created on an earlier try whose app then failed to save: it now
1067
+ // signs in through what discovery found.
1068
+ await updateMCPServer({
1069
+ id: newServer.id,
1070
+ org: tenantKey,
1071
+ userId: username,
1072
+ body,
1073
+ }).unwrap();
1074
+ newServer = { ...newServer, ...body };
1075
+ }
1076
+ else {
1077
+ newServer = await createMCPServer({ org: tenantKey, userId: username, body }).unwrap();
1078
+ }
558
1079
  // Start OAuth flow
559
1080
  await handleOAuthFlow(newServer, oauthFindResult.oauth_provider, oauthFindResult.name);
560
1081
  setIsSubmitting(false);
@@ -562,15 +1083,30 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
562
1083
  onClose();
563
1084
  }
564
1085
  catch (error) {
565
- const msg = extractApiErrorMessage(error);
566
- setOauthUrlError(getValidationErrorMessage(msg, t('mcpTabConnectorDialogs.oauthUrlInvalid')));
567
- toast.error(t('mcpTabConnectorDialogs.oauthConfigNotFound'));
1086
+ handleDiscoveryFailure(error, 'mcpTabConnectorDialogs.oauthUrlInvalid');
568
1087
  }
569
1088
  }
570
1089
  catch (error) {
571
- const msg = extractApiErrorMessage(error);
572
- setOauthUrlError(getValidationErrorMessage(msg, t('mcpTabConnectorDialogs.oauthUrlInvalid')));
573
- toast.error(t('mcpTabConnectorDialogs.oauthProcessFailed'));
1090
+ handleDiscoveryFailure(error, 'mcpTabConnectorDialogs.oauthProcessFailed');
1091
+ }
1092
+ finally {
1093
+ setIsSubmitting(false);
1094
+ }
1095
+ return;
1096
+ }
1097
+ // A connector without a sign-in has one setting for this agent to save.
1098
+ if (restricted && editingServer) {
1099
+ setIsSubmitting(true);
1100
+ try {
1101
+ await (onRestrictedSave === null || onRestrictedSave === void 0 ? void 0 : onRestrictedSave(editingServer, {
1102
+ scope: authScope,
1103
+ userCanDisable,
1104
+ }));
1105
+ resetForm();
1106
+ onClose();
1107
+ }
1108
+ catch (_h) {
1109
+ // Said by the host; the form stays open.
574
1110
  }
575
1111
  finally {
576
1112
  setIsSubmitting(false);
@@ -617,11 +1153,12 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
617
1153
  authType,
618
1154
  ...(imageFile && { image: imageFile }),
619
1155
  mentor: connectorScope === 'this-mentor' && mentorId ? mentorId : null,
1156
+ userCanDisable,
620
1157
  });
621
1158
  resetForm();
622
1159
  onClose();
623
1160
  }
624
- catch (_d) {
1161
+ catch (_j) {
625
1162
  // Error handled by parent
626
1163
  }
627
1164
  finally {
@@ -635,6 +1172,7 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
635
1172
  authMethod,
636
1173
  authScope,
637
1174
  connectorScope,
1175
+ userCanDisable,
638
1176
  tokenValue,
639
1177
  tokenType,
640
1178
  customTokenType,
@@ -656,6 +1194,58 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
656
1194
  editingConnectionId,
657
1195
  originalTokenPlaceholder,
658
1196
  onOAuthComplete,
1197
+ signInAppOpen,
1198
+ oauthApp,
1199
+ oauthServiceId,
1200
+ storedApp,
1201
+ manualAppConfigured,
1202
+ needsApp,
1203
+ restricted,
1204
+ onRestrictedSave,
1205
+ clearAgentScopeOverride,
1206
+ findsWithApp,
1207
+ appEndpoints,
1208
+ providerLabel,
1209
+ saveCredentials,
1210
+ chosenService,
1211
+ onAuthScopeChanged,
1212
+ t,
1213
+ ]);
1214
+ // Removing the organization's sign-in app signs everyone out of that provider.
1215
+ const removeSignInApp = useCallback(async () => {
1216
+ var _a;
1217
+ if (!editingServer || !tenantKey || !username)
1218
+ return;
1219
+ setIsSubmitting(true);
1220
+ try {
1221
+ await deleteCredentials({ org: tenantKey, userId: username, id: editingServer.id }).unwrap();
1222
+ toast.success(t('mcpOAuthApp.removed', { name: providerLabel }));
1223
+ setConfirmRemoveApp(false);
1224
+ setOauthApp(emptyOAuthAppForm());
1225
+ setSignInError(null);
1226
+ // The fields go away: the next save finds and registers an app again.
1227
+ setSignInAppOpen(false);
1228
+ setSignInReason(null);
1229
+ await credentialsQuery.refetch();
1230
+ if (onOAuthComplete)
1231
+ onOAuthComplete();
1232
+ }
1233
+ catch (error) {
1234
+ setConfirmRemoveApp(false);
1235
+ setSignInError((_a = describeApiFailure(error, t)) !== null && _a !== void 0 ? _a : t('mcpOAuthApp.removeFailed'));
1236
+ }
1237
+ finally {
1238
+ setIsSubmitting(false);
1239
+ }
1240
+ }, [
1241
+ editingServer,
1242
+ tenantKey,
1243
+ username,
1244
+ deleteCredentials,
1245
+ t,
1246
+ providerLabel,
1247
+ credentialsQuery,
1248
+ onOAuthComplete,
659
1249
  ]);
660
1250
  const handleClose = useCallback(() => {
661
1251
  if (isSubmitting)
@@ -664,18 +1254,91 @@ function ConnectorDialogs({ open, onClose, onAddConnector, editingServer, editin
664
1254
  resetForm();
665
1255
  onClose();
666
1256
  }, [isSubmitting, resetForm, onClose]);
667
- return (jsx(Dialog, { open: open, onOpenChange: handleClose, children: jsxs(DialogContent, { className: "bg-card flex max-h-[90vh] flex-col overflow-hidden p-0 sm:max-w-[600px]", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), children: [jsx(DialogHeader, { className: "border-border flex-shrink-0 border-b px-6 py-4", children: jsx(DialogTitle, { className: "text-foreground text-xl font-semibold", children: editingServer
668
- ? t('mcpTabConnectorDialogs.editMcpConnector')
669
- : t('mcpTabConnectorDialogs.addMcpConnector') }) }), jsx("div", { className: "flex-1 overflow-y-auto p-6", children: jsxs("div", { className: "mx-auto max-w-2xl space-y-4", children: [jsxs("div", { className: "flex items-center gap-4", children: [jsxs("div", { className: "group relative", children: [jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleImageUpload, className: "hidden", "aria-label": t('mcpTabConnectorDialogs.uploadConnectorImageLabel') }), jsx("div", { onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, className: "group relative flex h-16 w-16 cursor-pointer items-center justify-center overflow-hidden rounded-lg transition-colors", children: connectorImage ? (jsxs(Fragment, { children: [jsx("img", { src: connectorImage, alt: t('mcpTabConnectorDialogs.connectorThumbnailAlt'), className: "h-full w-full object-cover" }), jsx("div", { className: "absolute right-1 bottom-1 flex h-6 w-6 items-center justify-center rounded-full bg-[#38A1E5] opacity-0 transition-opacity group-hover:opacity-100", children: jsx(Image, { className: "h-3 w-3 text-white" }) })] })) : (jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-[#38A1E5]", children: jsx(Image, { className: "h-6 w-6 text-white" }) })) })] }), jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [jsxs(Label, { className: "text-base font-medium", children: [t('mcpTabConnectorDialogs.connectorNameLabel'), jsx("span", { className: "text-red-500", children: "*" })] }), jsx(Input, { value: connectorName, onChange: (e) => setConnectorName(e.target.value), placeholder: t('mcpTabConnectorDialogs.connectorNamePlaceholder'), className: "bg-muted border-border", disabled: isSubmitting })] })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs(Label, { className: "text-base font-medium", children: [t('mcpTabConnectorDialogs.connectorServerLabel'), jsx("span", { className: "text-red-500", children: "*" })] }), jsx(Input, { value: connectorServer, onChange: (e) => setConnectorServer(e.target.value), placeholder: t('mcpTabConnectorDialogs.connectorServerPlaceholder'), className: `bg-muted border-border ${oauthUrlError ? 'border-red-500' : ''}`, disabled: isSubmitting }), oauthUrlError && jsx("p", { className: "text-xs text-red-500", children: oauthUrlError })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.descriptionLabel') }), jsx("span", { className: "text-muted-foreground text-sm", children: t('mcpTabConnectorDialogs.optional') })] }), jsx(Textarea, { value: connectorDescription, onChange: (e) => setConnectorDescription(e.target.value), placeholder: t('mcpTabConnectorDialogs.descriptionPlaceholder'), className: "bg-muted border-border min-h-[100px]", disabled: isSubmitting })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.connectorScopeLabel') }), jsxs(RadioGroup, { value: connectorScope, onValueChange: (value) => setConnectorScope(value), disabled: isSubmitting, children: [jsxs("div", { className: "flex items-center space-x-2", children: [jsx(RadioGroupItem, { value: "tenant", id: "scope-tenant" }), jsx(Label, { htmlFor: "scope-tenant", className: "cursor-pointer font-normal", children: t('mcpTabConnectorDialogs.allAgents') })] }), jsxs("div", { className: "flex items-center space-x-2", children: [jsx(RadioGroupItem, { value: "this-mentor", id: "scope-this-mentor" }), jsx(Label, { htmlFor: "scope-this-mentor", className: "cursor-pointer font-normal", children: t('mcpTabConnectorDialogs.thisAgent') })] })] }), connectorScope === 'tenant' && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.mcpAvailableAllAgents') })), connectorScope === 'this-mentor' && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.mcpAvailableThisAgent') }))] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.transportLabel') }), jsxs(Select, { value: transport, onValueChange: (value) => setTransport(normalizeTransportValue(value)), disabled: isSubmitting, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: TransportEnum.SSE, children: "SSE" }), jsx(SelectItem, { value: TransportEnum.WEBSOCKET, children: t('mcpTabConnectorDialogs.websocket') }), jsx(SelectItem, { value: TransportEnum.STREAMABLE_HTTP, children: t('mcpTabConnectorDialogs.streamableHttp') })] })] })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.authMethodLabel') }), jsxs(Select, { value: authMethod, onValueChange: setAuthMethod, disabled: isSubmitting, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "no-auth", children: t('mcpTabConnectorDialogs.noAuthentication') }), jsx(SelectItem, { value: "api-key", children: t('mcpTabConnectorDialogs.apiKey') }), jsx(SelectItem, { value: "oauth", children: t('mcpTabConnectorDialogs.oauth') })] })] })] }), authMethod === 'oauth' && (jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.authScopeLabel') }), jsxs(Select, { value: authScope, onValueChange: setAuthScope, disabled: isSubmitting, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: AUTH_SCOPE.TENANT, children: t('mcpTabConnectorDialogs.tenant') }), jsx(SelectItem, { value: AUTH_SCOPE.MENTOR, children: t('mcpTabConnectorDialogs.agent') }), jsx(SelectItem, { value: AUTH_SCOPE.USER, children: t('mcpTabConnectorDialogs.user') })] })] }), authScope === AUTH_SCOPE.TENANT && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.oauthScopeTenantHint') })), authScope === AUTH_SCOPE.MENTOR && (jsx("div", { className: "flex items-center gap-1.5", children: jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.oauthScopeMentorHint') }) })), authScope === AUTH_SCOPE.USER && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.oauthScopeUserHint') }))] })), authMethod === 'api-key' && (jsxs("div", { className: "space-y-4 pt-2", children: [jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.tokenTypeLabel') }), jsxs(Select, { value: tokenType, onValueChange: setTokenType, disabled: isSubmitting, children: [jsx(SelectTrigger, { className: "bg-background border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "Bearer", children: "Bearer" }), jsx(SelectItem, { value: "Basic", children: "Basic" }), jsx(SelectItem, { value: "API-Key", children: "API-Key" }), jsx(SelectItem, { value: "API-Token", children: "API-Token" }), jsx(SelectItem, { value: "Token", children: "Token" }), jsx(SelectItem, { value: "Other", children: t('mcpTabConnectorDialogs.other') })] })] })] }), tokenType === 'Other' && (jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.customTokenTypeLabel') }), jsx(Input, { value: customTokenType, onChange: (e) => setCustomTokenType(e.target.value), placeholder: t('mcpTabConnectorDialogs.customTokenTypePlaceholder'), className: "bg-background border-border", disabled: isSubmitting, maxLength: 50 }), jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.customTokenTypeHint') })] })), jsxs("div", { className: "flex flex-col gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.tokenLabel') }), jsxs("div", { className: "relative", children: [jsx(Lock, { className: "text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" }), jsx(Input, { value: tokenValue, onChange: (e) => setTokenValue(e.target.value), placeholder: t('mcpTabConnectorDialogs.tokenPlaceholder'), type: "password", className: "bg-background border-border pl-10", disabled: isSubmitting })] }), credentialsMasked && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.existingTokenHidden') }))] })] })), jsx("div", { className: "flex justify-end", children: jsx(Button, { onClick: handleSubmit, disabled: !connectorName.trim() ||
670
- !connectorServer.trim() ||
671
- isSubmitting ||
672
- (authMethod === 'oauth' && !!oauthUrlError) ||
673
- (authMethod === 'api-key' && tokenType === 'Other' && !customTokenType.trim()), className: "ibl-button-primary shrink-0", children: isSubmitting
674
- ? t('mcpTabConnectorDialogs.saving')
675
- : editingServer
676
- ? t('mcpTabConnectorDialogs.update')
677
- : t('mcpTabConnectorDialogs.connect') }) })] }) })] }) }));
1257
+ // The sign-in app, shown only when the connector needs one entered by hand:
1258
+ // why, then the fields once the provider is known, or one sentence when not.
1259
+ const signInAppMessage = (jsx("p", { className: "text-muted-foreground text-xs", "data-testid": "mcp-sign-in-message", children: signInReason && !restricted
1260
+ ? t(signInReason === 'existing'
1261
+ ? 'mcpTabConnectorDialogs.needsManualAppExisting'
1262
+ : signInReason === 'metadata'
1263
+ ? 'mcpTabConnectorDialogs.needsManualAppMetadata'
1264
+ : 'mcpTabConnectorDialogs.needsManualApp')
1265
+ : t('mcpTabConnectorDialogs.appCredentialsOnlyHint', { name: providerLabel }) }));
1266
+ const signInAppControls = (jsxs(Fragment, { children: [providerState === 'known' || findsWithApp ? (jsx(OAuthAppFields, { form: oauthApp, onChange: (form) => {
1267
+ setOauthApp(form);
1268
+ setAppFieldErrors({});
1269
+ }, configured: manualAppConfigured && !findsWithApp, providerName: providerLabel, sharedClientAllowed: storedApp === null || storedApp === void 0 ? void 0 : storedApp.shared_client_allowed, disabled: isSubmitting, endpoints: appEndpoints, extraParams: !findsWithApp, fieldErrors: appFieldErrors })) : (jsxs("div", { className: "flex flex-col gap-2", children: [jsx("p", { "data-testid": "mcp-sign-in-provider", className: providerState === 'checking'
1270
+ ? 'text-muted-foreground text-xs'
1271
+ : 'text-xs text-red-500', children: t(providerState === 'checking'
1272
+ ? 'mcpTabConnectorDialogs.signInProviderChecking'
1273
+ : providerState === 'failed'
1274
+ ? 'mcpTabConnectorDialogs.signInProviderLoadFailed'
1275
+ : 'mcpTabConnectorDialogs.signInProviderUnknown') }), providerState === 'failed' && (jsx(Button, { type: "button", variant: "outline", size: "sm", "data-testid": "mcp-sign-in-provider-retry", className: "self-start", onClick: () => void providersQuery.refetch(), children: t('mcpTabConnectorDialogs.signInProviderRetry') }))] })), appConfigured && editingServer && (jsx(Button, { type: "button", variant: "outline", size: "sm", "data-testid": "mcp-oauth-app-remove", disabled: isSubmitting, onClick: () => setConfirmRemoveApp(true), className: "text-red-600 hover:bg-red-50 hover:text-red-700", children: t('mcpOAuthApp.remove') })), signInError && (jsx("p", { role: "alert", "data-testid": "mcp-sign-in-error", className: "text-xs text-red-500", children: signInError }))] }));
1276
+ const removeAppDialog = (jsx(AlertDialog, { open: confirmRemoveApp, onOpenChange: (next) => !next && setConfirmRemoveApp(false), children: jsxs(AlertDialogContent, { "data-testid": "mcp-oauth-app-remove-confirm", children: [jsxs(AlertDialogHeader, { children: [jsx(AlertDialogTitle, { children: t('mcpOAuthApp.removeTitle', { name: providerLabel }) }), jsx(AlertDialogDescription, { children: t('mcpOAuthApp.removeBody') })] }), jsxs(AlertDialogFooter, { children: [jsx(AlertDialogCancel, { disabled: isSubmitting, children: t('mcpTabConnectorManagementContent.cancel') }), jsx(AlertDialogAction, { "data-testid": "mcp-oauth-app-remove-confirm-button", disabled: isSubmitting, onClick: (event) => {
1277
+ event.preventDefault();
1278
+ void removeSignInApp();
1279
+ }, className: "bg-red-600 hover:bg-red-700", children: t('mcpOAuthApp.removeConfirm') })] })] }) }));
1280
+ // In the restricted form, every control about the connector itself is shown
1281
+ // as it is and cannot be changed; only its settings for this agent can.
1282
+ const lockedField = isSubmitting || restricted;
1283
+ // The host keeps an agent's choice only for a connector with a sign-in
1284
+ // service; one without cannot sign in, so there is nothing to choose.
1285
+ const lockedAuthScope = isSubmitting || (restricted && !(editingServer === null || editingServer === void 0 ? void 0 : editingServer.oauth_service_data));
1286
+ const savedSignInScope = editingServer ? currentSignInScope(editingServer) : null;
1287
+ // A changed, unsaved Authentication Scope would sign in at a scope nothing has stored yet.
1288
+ const showConnection = !!connection &&
1289
+ !!(editingServer === null || editingServer === void 0 ? void 0 : editingServer.oauth_service_data) &&
1290
+ ((_e = editingServer.auth_type) === null || _e === void 0 ? void 0 : _e.toLowerCase()) === 'oauth2' &&
1291
+ authMethod === 'oauth' &&
1292
+ authScope === savedSignInScope &&
1293
+ (connection.connected || connection.canConnect || connection.canDisconnect);
1294
+ const connectionInfoKey = savedSignInScope === AUTH_SCOPE.TENANT
1295
+ ? 'mcpTabConnectorDialogs.connectionInfoOrganization'
1296
+ : savedSignInScope === AUTH_SCOPE.MENTOR
1297
+ ? 'mcpTabConnectorDialogs.connectionInfoAgent'
1298
+ : 'mcpTabConnectorDialogs.connectionInfoUser';
1299
+ return (jsxs(Fragment, { children: [jsx(Dialog, { open: open, onOpenChange: handleClose, children: jsxs(DialogContent, { ref: contentRef, className: "bg-card flex max-h-[90vh] flex-col overflow-hidden p-0 sm:max-w-[600px]", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onOpenAutoFocus: (event) => {
1300
+ var _a;
1301
+ // Left to Radix, focus lands on the first info icon and opens its tooltip.
1302
+ event.preventDefault();
1303
+ const nameInput = nameInputRef.current;
1304
+ if (nameInput && !nameInput.disabled)
1305
+ nameInput.focus();
1306
+ else
1307
+ (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
1308
+ }, children: [jsx(DialogHeader, { className: "border-border flex-shrink-0 border-b px-6 py-4", children: jsx(DialogTitle, { className: "text-foreground text-xl font-semibold", children: editingServer
1309
+ ? t('mcpTabConnectorDialogs.editMcpConnector')
1310
+ : t('mcpTabConnectorDialogs.addMcpConnector') }) }), jsx("div", { className: "flex-1 overflow-y-auto p-6", children: jsxs("div", { className: "mx-auto max-w-2xl space-y-4", children: [jsxs("div", { className: "flex items-center gap-4", children: [jsxs("div", { className: "group relative", children: [jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleImageUpload, className: "hidden", "aria-label": t('mcpTabConnectorDialogs.uploadConnectorImageLabel'), disabled: restricted }), jsx("div", { onClick: () => { var _a; return !restricted && ((_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click()); }, "aria-disabled": restricted || undefined, className: `group relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-lg transition-colors ${restricted ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`, children: connectorImage ? (jsxs(Fragment, { children: [jsx("img", { src: connectorImage, alt: t('mcpTabConnectorDialogs.connectorThumbnailAlt'), className: "h-full w-full object-cover" }), !restricted && (jsx("div", { className: "absolute right-1 bottom-1 flex h-6 w-6 items-center justify-center rounded-full bg-[#38A1E5] opacity-0 transition-opacity group-hover:opacity-100", children: jsx(Image, { className: "h-3 w-3 text-white" }) }))] })) : (jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-[#38A1E5]", children: jsx(Image, { className: "h-6 w-6 text-white" }) })) })] }), jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsxs(Label, { className: "text-base font-medium", children: [t('mcpTabConnectorDialogs.connectorNameLabel'), jsx("span", { className: "text-red-500", children: "*" })] }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.connectorNameLabel'), text: t('mcpTabConnectorDialogs.connectorNameInfo') })] }), jsx(Input, { ref: nameInputRef, value: connectorName, onChange: (e) => {
1311
+ setConnectorName(e.target.value);
1312
+ setNameError(null);
1313
+ }, placeholder: t('mcpTabConnectorDialogs.connectorNamePlaceholder'), className: `bg-muted border-border ${nameError ? 'border-red-500' : ''}`, disabled: lockedField }), nameError && (jsx("p", { "data-testid": "mcp-connector-name-error", className: "text-xs text-red-500", children: nameError }))] })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsxs(Label, { className: "text-base font-medium", children: [t('mcpTabConnectorDialogs.connectorServerLabel'), jsx("span", { className: "text-red-500", children: "*" })] }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.connectorServerLabel'), text: t('mcpTabConnectorDialogs.connectorServerInfo') })] }), jsx(Input, { value: connectorServer, onChange: (e) => setConnectorServer(e.target.value), placeholder: t('mcpTabConnectorDialogs.connectorServerPlaceholder'), className: `bg-muted border-border ${oauthUrlError ? 'border-red-500' : ''}`, disabled: lockedField }), oauthUrlError && jsx("p", { className: "text-xs text-red-500", children: oauthUrlError })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.descriptionLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.descriptionLabel'), text: t('mcpTabConnectorDialogs.descriptionInfo') })] }), jsx("span", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.optional') })] }), jsx(Textarea, { value: connectorDescription, onChange: (e) => setConnectorDescription(e.target.value), placeholder: t('mcpTabConnectorDialogs.descriptionPlaceholder'), className: "bg-muted border-border min-h-[100px]", disabled: lockedField })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.connectorScopeLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.connectorScopeLabel'), text: t('mcpTabConnectorDialogs.connectorScopeInfo') })] }), jsxs(RadioGroup, { value: connectorScope, onValueChange: (value) => setConnectorScope(value), disabled: lockedField, children: [jsxs("div", { className: "flex items-center space-x-2", children: [jsx(RadioGroupItem, { value: "tenant", id: "scope-tenant", className: MCP_RADIO_ITEM_CLASS }), jsx(Label, { htmlFor: "scope-tenant", className: restricted
1314
+ ? 'cursor-not-allowed font-normal opacity-70'
1315
+ : 'cursor-pointer font-normal', children: t('mcpTabConnectorDialogs.allAgents') })] }), jsxs("div", { className: "flex items-center space-x-2", children: [jsx(RadioGroupItem, { value: "this-mentor", id: "scope-this-mentor", className: MCP_RADIO_ITEM_CLASS }), jsx(Label, { htmlFor: "scope-this-mentor", className: restricted
1316
+ ? 'cursor-not-allowed font-normal opacity-70'
1317
+ : 'cursor-pointer font-normal', children: t('mcpTabConnectorDialogs.thisAgent') })] })] }), connectorScope === 'tenant' && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.mcpAvailableAllAgents') })), connectorScope === 'this-mentor' && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.mcpAvailableThisAgent') }))] }), jsxs("div", { className: "flex items-center justify-between gap-4", children: [jsxs("div", { className: "flex flex-col gap-1", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { htmlFor: "connector-user-can-disable", className: "text-base font-medium", children: t('mcpTabConnectorDialogs.userCanDisableLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.userCanDisableLabel'), text: t('mcpTabConnectorDialogs.userCanDisableInfo') })] }), jsx("p", { className: "text-muted-foreground text-xs", children: userCanDisable
1318
+ ? t('mcpTabConnectorDialogs.userCanDisableOnHint')
1319
+ : t('mcpTabConnectorDialogs.userCanDisableOffHint') })] }), jsx(Switch, { id: "connector-user-can-disable", "data-testid": "connector-user-can-disable", checked: userCanDisable, onCheckedChange: setUserCanDisable, disabled: isSubmitting, className: "data-[state=checked]:bg-[#38A1E5] data-[state=unchecked]:bg-gray-200" })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.transportLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.transportLabel'), text: t('mcpTabConnectorDialogs.transportInfo') })] }), jsxs(Select, { value: transport, onValueChange: (value) => setTransport(normalizeTransportValue(value)), disabled: lockedField, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: TransportEnum.SSE, children: "SSE" }), jsx(SelectItem, { value: TransportEnum.WEBSOCKET, children: t('mcpTabConnectorDialogs.websocket') }), jsx(SelectItem, { value: TransportEnum.STREAMABLE_HTTP, children: t('mcpTabConnectorDialogs.streamableHttp') })] })] })] }), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.authMethodLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.authMethodLabel'), text: t('mcpTabConnectorDialogs.authMethodInfo') })] }), jsxs(Select, { value: authMethod, onValueChange: setAuthMethod, disabled: lockedField, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "no-auth", children: t('mcpTabConnectorDialogs.noAuthentication') }), jsx(SelectItem, { value: "api-key", children: t('mcpTabConnectorDialogs.apiKey') }), jsx(SelectItem, { value: "oauth", children: t('mcpTabConnectorDialogs.oauth') })] })] })] }), authMethod === 'oauth' && (jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.authScopeLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.authScopeLabel'), text: t('mcpTabConnectorDialogs.authScopeInfo') })] }), jsxs(Select, { value: authScope, onValueChange: setAuthScope, disabled: lockedAuthScope, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: AUTH_SCOPE.TENANT, children: t('mcpTabConnectorDialogs.tenant') }), jsx(SelectItem, { value: AUTH_SCOPE.MENTOR, children: t('mcpTabConnectorDialogs.agent') }), jsx(SelectItem, { value: AUTH_SCOPE.USER, children: t('mcpTabConnectorDialogs.user') })] })] }), authScope === AUTH_SCOPE.TENANT && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.oauthScopeTenantHint') })), authScope === AUTH_SCOPE.MENTOR && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.oauthScopeMentorHint') })), authScope === AUTH_SCOPE.USER && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.oauthScopeUserHint') }))] })), showConnection && connection && (jsxs("div", { className: "flex flex-col gap-2", "data-testid": "mcp-connection-row", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.connectionLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.connectionLabel'), text: t(connectionInfoKey) })] }), jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-sm", "data-testid": "mcp-connection-status", children: connection.connected
1320
+ ? t('mcpTabConnectorDialogs.connected')
1321
+ : t('mcpTabConnectorDialogs.notConnected') }), connection.canDisconnect ? (jsxs(Button, { type: "button", variant: "outline", size: "sm", "data-testid": "mcp-connection-disconnect", className: "h-8 text-red-600 hover:bg-red-50 hover:text-red-700", onClick: () => connection.onDisconnect(), disabled: connection.disconnecting, children: [connection.disconnecting ? (jsx(Spinner, { className: "mr-2 h-4 w-4" })) : (jsx(Unlink, { className: "mr-2 h-4 w-4" })), connection.disconnecting
1322
+ ? t('mcpTabConnectorManagementContent.disconnecting')
1323
+ : t('mcpTabConnectorManagementContent.disconnect')] })) : connection.canConnect ? (jsxs(Button, { type: "button", variant: "outline", size: "sm", "data-testid": "mcp-connection-connect", className: "h-8 border-[#38A1E5] bg-[#38A1E5] text-white hover:bg-[#2d8bc7] hover:text-white", onClick: () => connection.onConnect(), disabled: connection.connecting, children: [connection.connecting ? (jsx(Spinner, { className: "mr-2 h-4 w-4" })) : (jsx(Link2, { className: "mr-2 h-4 w-4" })), connection.connecting
1324
+ ? t('mcpTabConnectorManagementContent.connecting')
1325
+ : t('mcpTabConnectorManagementContent.connect')] })) : null] })] })), authMethod === 'oauth' && signInAppOpen && (jsxs("div", { className: "space-y-4 pt-2", "data-testid": "mcp-sign-in-app", children: [jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.appCredentialsLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.appCredentialsLabel'), text: t('mcpTabConnectorDialogs.appCredentialsInfo') })] }), signInAppMessage] }), signInAppControls] })), authMethod === 'api-key' && (jsxs("div", { className: "space-y-4 pt-2", children: [jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.tokenTypeLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.tokenTypeLabel'), text: t('mcpTabConnectorDialogs.tokenTypeInfo') })] }), jsxs(Select, { value: tokenType, onValueChange: setTokenType, disabled: lockedField, children: [jsx(SelectTrigger, { className: "bg-muted border-border", children: jsx(SelectValue, {}) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "Bearer", children: "Bearer" }), jsx(SelectItem, { value: "Basic", children: "Basic" }), jsx(SelectItem, { value: "API-Key", children: "API-Key" }), jsx(SelectItem, { value: "API-Token", children: "API-Token" }), jsx(SelectItem, { value: "Token", children: "Token" }), jsx(SelectItem, { value: "Other", children: t('mcpTabConnectorDialogs.other') })] })] })] }), tokenType === 'Other' && (jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.customTokenTypeLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.customTokenTypeLabel'), text: t('mcpTabConnectorDialogs.customTokenTypeInfo') })] }), jsx(Input, { value: customTokenType, onChange: (e) => setCustomTokenType(e.target.value), placeholder: t('mcpTabConnectorDialogs.customTokenTypePlaceholder'), className: "bg-muted border-border", disabled: lockedField, maxLength: 50 }), jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.customTokenTypeHint') })] })), jsxs("div", { className: "flex flex-col gap-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label, { className: "text-base font-medium", children: t('mcpTabConnectorDialogs.tokenLabel') }), jsx(FieldInfo, { label: t('mcpTabConnectorDialogs.tokenLabel'), text: t('mcpTabConnectorDialogs.tokenInfo') })] }), jsxs("div", { className: "relative", children: [jsx(Lock, { className: "text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" }), jsx(Input, { value: tokenValue, onChange: (e) => setTokenValue(e.target.value), placeholder: t('mcpTabConnectorDialogs.tokenPlaceholder'), type: "password", className: "bg-muted border-border pl-10", disabled: lockedField })] }), credentialsMasked && (jsx("p", { className: "text-muted-foreground text-xs", children: t('mcpTabConnectorDialogs.existingTokenHidden') }))] })] })), jsx("div", { className: "flex justify-end", children: jsx(Button, { onClick: handleSubmit, disabled: isSubmitting ||
1326
+ (authMethod === 'oauth' &&
1327
+ signInAppOpen &&
1328
+ !findsWithApp &&
1329
+ providerState !== 'known') ||
1330
+ (!restricted &&
1331
+ (!connectorName.trim() ||
1332
+ !connectorServer.trim() ||
1333
+ (authMethod === 'oauth' && !!oauthUrlError && !addressErrorRetryable) ||
1334
+ (authMethod === 'api-key' &&
1335
+ tokenType === 'Other' &&
1336
+ !customTokenType.trim()))), className: "ibl-button-primary shrink-0", children: isSubmitting
1337
+ ? t('mcpTabConnectorDialogs.saving')
1338
+ : editingServer
1339
+ ? t('mcpTabConnectorDialogs.update')
1340
+ : t('mcpTabConnectorDialogs.connect') }) })] }) })] }) }), removeAppDialog] }));
678
1341
  }
679
1342
 
680
- export { ConnectorDialogs, canCreateOAuthConnection, extractApiErrorMessage, getValidationErrorMessage, validateConnectorForm, validateCustomTokenType };
1343
+ export { ConnectorDialogs, canCreateOAuthConnection, extractApiErrorMessage, getValidationErrorMessage, matchProvider, readOAuthFindDiagnostic, validateConnectorForm, validateCustomTokenType };
681
1344
  //# sourceMappingURL=connector-dialogs.js.map