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