@iblai/web-containers 1.56.1 → 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.
- package/dist/cjs/_virtual/index2.js +2 -2
- package/dist/cjs/_virtual/index3.js +2 -2
- package/dist/cjs/components/chat/hooks/use-chat-file-drag-drop.js +13 -3
- package/dist/cjs/components/chat/hooks/use-chat-file-drag-drop.js.map +1 -1
- package/dist/cjs/components/chat/hooks/use-chat-file-upload.js +12 -16
- package/dist/cjs/components/chat/hooks/use-chat-file-upload.js.map +1 -1
- package/dist/cjs/components/chat/index.js +2 -2
- package/dist/cjs/components/chat/index.js.map +1 -1
- package/dist/cjs/components/chat/not-allowed-files-notice.js +62 -0
- package/dist/cjs/components/chat/not-allowed-files-notice.js.map +1 -0
- package/dist/cjs/components/chat/ui/button.js +4 -4
- package/dist/cjs/components/chat/ui/button.js.map +1 -1
- package/dist/cjs/components/chat-input-form/composer-plus-menu.js +94 -17
- package/dist/cjs/components/chat-input-form/composer-plus-menu.js.map +1 -1
- package/dist/cjs/components/chat-input-form/mcp-connect-explainer-dialog.js +29 -0
- package/dist/cjs/components/chat-input-form/mcp-connect-explainer-dialog.js.map +1 -0
- package/dist/cjs/components/chat-input-form/mcp-servers-menu.js +199 -0
- package/dist/cjs/components/chat-input-form/mcp-servers-menu.js.map +1 -0
- package/dist/cjs/components/chat-input-form/use-mcp-active-count.js +56 -0
- package/dist/cjs/components/chat-input-form/use-mcp-active-count.js.map +1 -0
- package/dist/cjs/components/chat-input-form.js +19 -6
- package/dist/cjs/components/chat-input-form.js.map +1 -1
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js +755 -90
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js.map +1 -1
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js +386 -984
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js.map +1 -1
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js +16 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js.map +1 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js +6 -3
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js.map +1 -1
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js +463 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js.map +1 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js +329 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js.map +1 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js +20 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js.map +1 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js +744 -0
- package/dist/cjs/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js.map +1 -0
- package/dist/cjs/components/modals/user-profile-modal.js +2 -0
- package/dist/cjs/components/modals/user-profile-modal.js.map +1 -1
- package/dist/cjs/components/ui/accordion.js +40 -0
- package/dist/cjs/components/ui/accordion.js.map +1 -0
- package/dist/cjs/i18n/messages/en.json.js +165 -20
- package/dist/cjs/i18n/messages/en.json.js.map +1 -1
- package/dist/cjs/i18n/messages/es.json.js +165 -20
- package/dist/cjs/i18n/messages/es.json.js.map +1 -1
- package/dist/cjs/i18n/messages/fr.json.js +165 -20
- package/dist/cjs/i18n/messages/fr.json.js.map +1 -1
- package/dist/cjs/i18n/messages/zh.json.js +165 -20
- package/dist/cjs/i18n/messages/zh.json.js.map +1 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -1
- 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
- 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
- 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
- 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
- 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
- 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
- package/dist/cjs/node_modules/.pnpm/css-mediaquery@0.1.2/node_modules/css-mediaquery/index.js +1 -1
- package/dist/cjs/node_modules/.pnpm/matchmediaquery@0.4.2/node_modules/matchmediaquery/index.js +1 -1
- package/dist/cjs/node_modules/.pnpm/prop-types@15.8.1/node_modules/prop-types/index.js +1 -1
- package/dist/cjs/web-containers/tsconfig.build.tsbuildinfo +1 -0
- package/dist/esm/_virtual/index2.js +2 -2
- package/dist/esm/_virtual/index3.js +2 -2
- package/dist/esm/components/chat/hooks/use-chat-file-drag-drop.js +14 -4
- package/dist/esm/components/chat/hooks/use-chat-file-drag-drop.js.map +1 -1
- package/dist/esm/components/chat/hooks/use-chat-file-upload.js +13 -17
- package/dist/esm/components/chat/hooks/use-chat-file-upload.js.map +1 -1
- package/dist/esm/components/chat/index.js +2 -2
- package/dist/esm/components/chat/index.js.map +1 -1
- package/dist/esm/components/chat/not-allowed-files-notice.js +60 -0
- package/dist/esm/components/chat/not-allowed-files-notice.js.map +1 -0
- package/dist/esm/components/chat-input-form/composer-plus-menu.js +94 -17
- package/dist/esm/components/chat-input-form/composer-plus-menu.js.map +1 -1
- package/dist/esm/components/chat-input-form/mcp-connect-explainer-dialog.js +27 -0
- package/dist/esm/components/chat-input-form/mcp-connect-explainer-dialog.js.map +1 -0
- package/dist/esm/components/chat-input-form/mcp-servers-menu.js +197 -0
- package/dist/esm/components/chat-input-form/mcp-servers-menu.js.map +1 -0
- package/dist/esm/components/chat-input-form/use-mcp-active-count.js +54 -0
- package/dist/esm/components/chat-input-form/use-mcp-active-count.js.map +1 -0
- package/dist/esm/components/chat-input-form.js +19 -6
- package/dist/esm/components/chat-input-form.js.map +1 -1
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js +755 -92
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-dialogs.js.map +1 -1
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js +361 -968
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/connector-management-content.js.map +1 -1
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js +14 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/field-info.js.map +1 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js +6 -3
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/index.js.map +1 -1
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js +431 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/mcp-connector-helpers.js.map +1 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js +313 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/oauth-app-fields.js.map +1 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js +18 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/remove-connector-dialog.js.map +1 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js +742 -0
- package/dist/esm/components/modals/edit-mentor-modal/tabs/mcp-tab/use-mcp-server-actions.js.map +1 -0
- package/dist/esm/components/modals/user-profile-modal.js +2 -0
- package/dist/esm/components/modals/user-profile-modal.js.map +1 -1
- package/dist/esm/components/ui/accordion.js +16 -0
- package/dist/esm/components/ui/accordion.js.map +1 -0
- package/dist/esm/i18n/messages/en.json.js +163 -21
- package/dist/esm/i18n/messages/en.json.js.map +1 -1
- package/dist/esm/i18n/messages/es.json.js +163 -21
- package/dist/esm/i18n/messages/es.json.js.map +1 -1
- package/dist/esm/i18n/messages/fr.json.js +163 -21
- package/dist/esm/i18n/messages/fr.json.js.map +1 -1
- package/dist/esm/i18n/messages/zh.json.js +163 -21
- package/dist/esm/i18n/messages/zh.json.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- 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
- 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
- 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
- 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
- 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
- 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
- package/dist/esm/node_modules/.pnpm/css-mediaquery@0.1.2/node_modules/css-mediaquery/index.js +1 -1
- package/dist/esm/node_modules/.pnpm/matchmediaquery@0.4.2/node_modules/matchmediaquery/index.js +1 -1
- package/dist/esm/node_modules/.pnpm/prop-types@15.8.1/node_modules/prop-types/index.js +1 -1
- package/dist/esm/web-containers/tsconfig.build.tsbuildinfo +1 -0
- package/dist/index.d.ts +31 -2
- package/dist/package.json +1 -1
- package/package.json +3 -3
- package/dist/cjs/web-containers/tsconfig.tsbuildinfo +0 -1
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
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
|
-
|
|
478
|
-
|
|
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
|
-
|
|
482
|
-
|
|
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 =
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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 = (
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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 (
|
|
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
|
-
|
|
670
|
-
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|