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