mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
feat(ui/mcp): place OAuth upstream-delegation toggle in the PKCE flow, add passthrough notice
Render the "Delegate auth to upstream (PKCE passthrough)" toggle inside the OAuth fields, right after the OAuth Flow Type selector and only for the Interactive (PKCE) flow, instead of leaving it floating at the bottom of the form or buried in the Permission Management / Access Control section. It now sits next to the OAuth config it controls in both the create and edit MCP server forms. When the toggle is on, show a short notice explaining that clients authenticate directly with the upstream MCP server, that LiteLLM won't enforce its own API key/SSO on this route, and that it won't store user credentials, so the endpoint is reachable without a LiteLLM login. The existing internal-network warning still shows when passthrough is on for an internal-only server. The toggle, the passthrough notice, and the internal-network warning live in a single prop-less DelegateAuthToUpstreamField component that reads everything from the form, shared by both forms. Because the toggle is only mounted for the Interactive OAuth flow, the safety that forces the flag back to false for M2M or non-oauth2 auth happens at submit time in both forms; switching transport to stdio also clears it in handleTransportChange so the form store stays consistent. The separate none-auth oauth_passthrough toggle in the Permission Management section is unrelated and left untouched.
This commit is contained in:
parent
49ca04d8c3
commit
e2b3728c05
8 changed files with 199 additions and 76 deletions
|
|
@ -0,0 +1,59 @@
|
|||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Form, Input } from "antd";
|
||||
|
||||
import DelegateAuthToUpstreamField from "./DelegateAuthToUpstreamField";
|
||||
|
||||
const PASSTHROUGH_NOTICE = /reachable without a LiteLLM login/i;
|
||||
const INTERNAL_WARNING = /Internal server with upstream OAuth delegation/i;
|
||||
|
||||
const Harness: React.FC<{ initialValues?: Record<string, any> }> = ({ initialValues }) => {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<Form form={form} initialValues={initialValues}>
|
||||
<Form.Item name="available_on_public_internet" hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<DelegateAuthToUpstreamField />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const renderField = (initialValues: Record<string, any> = {}) => render(<Harness initialValues={initialValues} />);
|
||||
|
||||
describe("DelegateAuthToUpstreamField", () => {
|
||||
it("renders the toggle off with no passthrough notice by default", () => {
|
||||
renderField();
|
||||
expect(screen.getByText("Delegate auth to upstream (PKCE passthrough)")).toBeInTheDocument();
|
||||
expect(screen.getByRole("switch")).toHaveAttribute("aria-checked", "false");
|
||||
expect(screen.queryByText(PASSTHROUGH_NOTICE)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the passthrough notice once delegation is enabled", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderField({ available_on_public_internet: true });
|
||||
|
||||
expect(screen.queryByText(PASSTHROUGH_NOTICE)).not.toBeInTheDocument();
|
||||
await user.click(screen.getByRole("switch"));
|
||||
|
||||
expect(screen.getByText(PASSTHROUGH_NOTICE)).toBeInTheDocument();
|
||||
expect(screen.queryByText(INTERNAL_WARNING)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("warns when delegation is on for an internal-only server", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderField({ available_on_public_internet: false });
|
||||
await user.click(screen.getByRole("switch"));
|
||||
|
||||
expect(screen.getByText(PASSTHROUGH_NOTICE)).toBeInTheDocument();
|
||||
expect(screen.getByText(INTERNAL_WARNING)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("reflects a saved delegate_auth_to_upstream value when editing", () => {
|
||||
renderField({ delegate_auth_to_upstream: true, available_on_public_internet: true });
|
||||
expect(screen.getByRole("switch")).toHaveAttribute("aria-checked", "true");
|
||||
expect(screen.getByText(PASSTHROUGH_NOTICE)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import React from "react";
|
||||
import { Alert, Form, Switch, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
|
||||
const DelegateAuthToUpstreamField: React.FC = () => {
|
||||
const form = Form.useFormInstance();
|
||||
const delegateAuth = Form.useWatch("delegate_auth_to_upstream", form) === true;
|
||||
const internalOnly = Form.useWatch("available_on_public_internet", form) === false;
|
||||
|
||||
return (
|
||||
<div className="space-y-3 mb-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Delegate auth to upstream (PKCE passthrough)
|
||||
<Tooltip title="When on, LiteLLM skips its own API key/SSO check for this server and lets the client complete PKCE directly with the upstream MCP server. Only honored for the Interactive (PKCE) flow. No spend tracking or per-key rate limiting will run on this route.">
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Bypass LiteLLM auth so clients authenticate directly with the upstream OAuth MCP server.
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item name="delegate_auth_to_upstream" valuePropName="checked" className="mb-0">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{delegateAuth && (
|
||||
<div className="p-3 bg-blue-50 rounded-lg text-sm text-blue-700 flex items-start gap-2">
|
||||
<InfoCircleOutlined className="mt-0.5 flex-shrink-0" />
|
||||
<span>
|
||||
Clients authenticate directly with the upstream MCP server. LiteLLM won't require its own API key/SSO
|
||||
on this route and won't store user credentials, so this endpoint is reachable without a LiteLLM login.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{delegateAuth && internalOnly && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="Internal server with upstream OAuth delegation"
|
||||
description="This MCP server is configured as internal-only but delegates auth to upstream. Anonymous users will be able to reach the upstream OAuth2 /authorize flow without a LiteLLM session. Ensure your upstream provider and network enforce access controls."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DelegateAuthToUpstreamField;
|
||||
|
|
@ -73,11 +73,11 @@ describe("MCPPermissionManagement", () => {
|
|||
);
|
||||
};
|
||||
|
||||
it("shows only the oauth2 PKCE-delegation toggle for oauth2 servers", async () => {
|
||||
it("does not render the oauth2 PKCE-delegation toggle here (it lives next to the auth fields now)", async () => {
|
||||
renderWithInitialValues({ allow_all_keys: false, auth_type: "oauth2" });
|
||||
await expandPanel();
|
||||
expect(screen.getByText("Delegate auth to upstream (PKCE passthrough)")).toBeInTheDocument();
|
||||
// The non-oauth2 pass-through toggle must NOT appear for oauth2 servers.
|
||||
expect(screen.queryByText("Delegate auth to upstream (PKCE passthrough)")).not.toBeInTheDocument();
|
||||
// The none-auth pass-through toggle must NOT appear for oauth2 servers either.
|
||||
expect(screen.queryByText("OAuth pass-through")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { Alert, Form, Select, Tooltip, Collapse, Input, Space, Button, Switch } from "antd";
|
||||
import { Form, Select, Tooltip, Collapse, Input, Space, Button, Switch } from "antd";
|
||||
import { InfoCircleOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { MCPServer, AUTH_TYPE } from "./types";
|
||||
const { Panel } = Collapse;
|
||||
|
|
@ -24,24 +24,15 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
}) => {
|
||||
const form = Form.useFormInstance();
|
||||
const watchedAuthType = Form.useWatch("auth_type", form);
|
||||
const isOAuth2 = watchedAuthType === AUTH_TYPE.OAUTH2;
|
||||
const isNoneAuth = watchedAuthType === AUTH_TYPE.NONE || watchedAuthType == null;
|
||||
const watchedExtraHeaders = Form.useWatch("extra_headers", form);
|
||||
const hasAuthorizationHeader =
|
||||
Array.isArray(watchedExtraHeaders) &&
|
||||
watchedExtraHeaders.some((h) => typeof h === "string" && h.toLowerCase() === "authorization");
|
||||
// Two distinct, independent opt-ins:
|
||||
// - delegate_auth_to_upstream: oauth2 servers only (PKCE passthrough —
|
||||
// bypass LiteLLM admission).
|
||||
// - oauth_passthrough: auth_type=none + Authorization in extra_headers
|
||||
// (OAuth pass-through: proxy upstream oauth-protected-resource, emit 401
|
||||
// challenges, propagate upstream 401/403).
|
||||
// Kept as separate flags so neither silently implies the other and existing
|
||||
// oauth2 servers can't regress into pass-through behavior.
|
||||
// oauth_passthrough only applies to auth_type=none servers that forward an
|
||||
// Authorization header upstream (proxy upstream oauth-protected-resource, emit
|
||||
// 401 challenges, propagate upstream 401/403).
|
||||
const canEnableOAuthPassthrough = isNoneAuth && hasAuthorizationHeader;
|
||||
const watchedDelegateAuth = Form.useWatch("delegate_auth_to_upstream", form);
|
||||
const watchedPublicInternet = Form.useWatch("available_on_public_internet", form);
|
||||
const showInternalDelegatePkceWarning = isOAuth2 && watchedDelegateAuth === true && watchedPublicInternet === false;
|
||||
|
||||
// Set initial values when mcpServer changes
|
||||
useEffect(() => {
|
||||
|
|
@ -70,29 +61,16 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
if (typeof mcpServer.available_on_public_internet === "boolean") {
|
||||
form.setFieldValue("available_on_public_internet", mcpServer.available_on_public_internet);
|
||||
}
|
||||
if (typeof mcpServer.delegate_auth_to_upstream === "boolean") {
|
||||
form.setFieldValue("delegate_auth_to_upstream", mcpServer.delegate_auth_to_upstream);
|
||||
}
|
||||
if (typeof mcpServer.oauth_passthrough === "boolean") {
|
||||
form.setFieldValue("oauth_passthrough", mcpServer.oauth_passthrough);
|
||||
}
|
||||
} else {
|
||||
form.setFieldValue("allow_all_keys", false);
|
||||
form.setFieldValue("available_on_public_internet", true);
|
||||
form.setFieldValue("delegate_auth_to_upstream", false);
|
||||
form.setFieldValue("oauth_passthrough", false);
|
||||
}
|
||||
}, [mcpServer, form]);
|
||||
|
||||
// delegate_auth_to_upstream is only honored server-side for oauth2 servers.
|
||||
// Force it back to false whenever the user switches away from oauth2 so a
|
||||
// stale toggle value doesn't get persisted unexpectedly.
|
||||
useEffect(() => {
|
||||
if (!isOAuth2) {
|
||||
form.setFieldValue("delegate_auth_to_upstream", false);
|
||||
}
|
||||
}, [isOAuth2, form]);
|
||||
|
||||
// oauth_passthrough is only honored for auth_type=none servers that forward
|
||||
// Authorization upstream. Force it back to false otherwise.
|
||||
useEffect(() => {
|
||||
|
|
@ -164,30 +142,6 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{isOAuth2 && (
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Delegate auth to upstream (PKCE passthrough)
|
||||
<Tooltip title="When on, LiteLLM skips its own API key/SSO check for this server and lets the client complete PKCE directly with the upstream MCP server. Only honored when Auth Type is oauth2. No spend tracking or per-key rate limiting will run on this route.">
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Bypass LiteLLM auth so clients authenticate directly with the upstream OAuth MCP server.
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="delegate_auth_to_upstream"
|
||||
valuePropName="checked"
|
||||
initialValue={mcpServer?.delegate_auth_to_upstream ?? false}
|
||||
className="mb-0"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{canEnableOAuthPassthrough && (
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
|
|
@ -213,16 +167,6 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{showInternalDelegatePkceWarning && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
className="mb-2"
|
||||
message="Internal server with upstream OAuth delegation"
|
||||
description="This MCP server is configured as internal-only but delegates auth to upstream. Anonymous users will be able to reach the upstream OAuth2 /authorize flow without a LiteLLM session. Ensure your upstream provider and network enforce access controls."
|
||||
/>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Form, Input, InputNumber, Select, Tooltip } from "antd";
|
|||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, TextInput } from "@tremor/react";
|
||||
import { OAUTH_FLOW } from "./types";
|
||||
import DelegateAuthToUpstreamField from "./DelegateAuthToUpstreamField";
|
||||
|
||||
interface OAuthFlowStatus {
|
||||
startOAuthFlow: () => void;
|
||||
|
|
@ -115,6 +116,7 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<DelegateAuthToUpstreamField />
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="flex items-center justify-between w-full">
|
||||
|
|
|
|||
|
|
@ -315,6 +315,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
// Transform access groups into objects with name property
|
||||
const accessGroups = restValues.mcp_access_groups;
|
||||
|
||||
const isInteractiveOAuth = restValues.auth_type === AUTH_TYPE.OAUTH2 && values.oauth_flow_type !== OAUTH_FLOW.M2M;
|
||||
|
||||
const staticHeaders = reduceStaticHeaders(staticHeadersList);
|
||||
const envVars = normalizeEnvVars(envVarsList);
|
||||
|
||||
|
|
@ -414,7 +416,10 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
tool_name_to_description: toolNameToDescription,
|
||||
allow_all_keys: Boolean(allowAllKeysRaw),
|
||||
available_on_public_internet: Boolean(availableOnPublicInternetRaw),
|
||||
delegate_auth_to_upstream: Boolean(delegateAuthToUpstreamRaw),
|
||||
// delegate_auth_to_upstream (PKCE passthrough) is only honored for the
|
||||
// Interactive OAuth flow; force false otherwise so a stale toggle value
|
||||
// can't persist against M2M or non-oauth2 auth.
|
||||
delegate_auth_to_upstream: isInteractiveOAuth && Boolean(delegateAuthToUpstreamRaw),
|
||||
oauth_passthrough: Boolean(oauthPassthroughRaw),
|
||||
static_headers: staticHeaders,
|
||||
env_vars: envVars,
|
||||
|
|
@ -443,7 +448,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
const oauthMode = getMcpOAuthMode({
|
||||
auth_type: restValues.auth_type,
|
||||
oauth2_flow: values.oauth_flow_type === OAUTH_FLOW.M2M ? MCP_OAUTH2_FLOW_M2M : null,
|
||||
delegate_auth_to_upstream: Boolean(delegateAuthToUpstreamRaw),
|
||||
delegate_auth_to_upstream: isInteractiveOAuth && Boolean(delegateAuthToUpstreamRaw),
|
||||
});
|
||||
if (oauthMode === "obo") {
|
||||
const scope = oauthTokenResponse.scope;
|
||||
|
|
@ -506,7 +511,13 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
setTransportType(value);
|
||||
// Clear fields that are not relevant for the selected transport
|
||||
if (value === "stdio") {
|
||||
form.setFieldsValue({ url: undefined, spec_path: undefined, auth_type: undefined, credentials: undefined });
|
||||
form.setFieldsValue({
|
||||
url: undefined,
|
||||
spec_path: undefined,
|
||||
auth_type: undefined,
|
||||
credentials: undefined,
|
||||
delegate_auth_to_upstream: false,
|
||||
});
|
||||
} else if (value === TRANSPORT.OPENAPI) {
|
||||
form.setFieldsValue({ url: undefined, command: undefined, args: undefined, env: undefined });
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -262,6 +262,60 @@ describe("MCPServerEdit (delegate auth)", () => {
|
|||
expect(payload.delegate_auth_to_upstream).toBe(false);
|
||||
});
|
||||
|
||||
it("clears the delegate auth flag when switching an oauth2 server to stdio transport", async () => {
|
||||
vi.mocked(networking.updateMCPServer).mockResolvedValue({
|
||||
...interactiveOAuthServer,
|
||||
transport: "stdio",
|
||||
auth_type: "none",
|
||||
command: "npx",
|
||||
});
|
||||
|
||||
render(
|
||||
<MCPServerEdit
|
||||
mcpServer={{
|
||||
...interactiveOAuthServer,
|
||||
delegate_auth_to_upstream: true,
|
||||
}}
|
||||
accessToken="access-token"
|
||||
onCancel={vi.fn()}
|
||||
onSuccess={vi.fn()}
|
||||
availableAccessGroups={[]}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Sanity: the delegate toggle is shown for the interactive-OAuth server.
|
||||
expect(screen.getByText("Delegate auth to upstream (PKCE passthrough)")).toBeInTheDocument();
|
||||
|
||||
// Switch transport to stdio: the OAuth section (and toggle) unmounts.
|
||||
const transportSelect = screen.getByLabelText("Transport Type");
|
||||
await act(async () => {
|
||||
fireEvent.mouseDown(transportSelect);
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText("Standard Input/Output (stdio)"));
|
||||
});
|
||||
|
||||
expect(screen.queryByText("Delegate auth to upstream (PKCE passthrough)")).not.toBeInTheDocument();
|
||||
|
||||
const commandInput = screen.getByLabelText("Command");
|
||||
await act(async () => {
|
||||
fireEvent.change(commandInput, { target: { value: "npx" } });
|
||||
});
|
||||
|
||||
const saveButtons = screen.getAllByRole("button", { name: "Save Changes" });
|
||||
await act(async () => {
|
||||
fireEvent.click(saveButtons[0]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(networking.updateMCPServer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const [, payload] = vi.mocked(networking.updateMCPServer).mock.calls[0];
|
||||
expect(payload.transport).toBe("stdio");
|
||||
expect(payload.delegate_auth_to_upstream).toBe(false);
|
||||
});
|
||||
|
||||
it("does not enable oauth_passthrough for an oauth2 server", async () => {
|
||||
vi.mocked(networking.updateMCPServer).mockResolvedValue({
|
||||
...interactiveOAuthServer,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { getToken, isTokenValid, setToken } from "@/utils/mcpTokenStore";
|
|||
import { buildMcpPassthroughAuthHeader } from "@/utils/mcpHeaderUtils";
|
||||
import MCPServerCostConfig from "./mcp_server_cost_config";
|
||||
import MCPPermissionManagement from "./MCPPermissionManagement";
|
||||
import DelegateAuthToUpstreamField from "./DelegateAuthToUpstreamField";
|
||||
import MCPToolConfiguration from "./mcp_tool_configuration";
|
||||
import StdioConfiguration from "./StdioConfiguration";
|
||||
import MCPLogoSelector from "./MCPLogoSelector";
|
||||
|
|
@ -426,6 +427,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
|
|||
authorization_url: undefined,
|
||||
token_url: undefined,
|
||||
registration_url: undefined,
|
||||
delegate_auth_to_upstream: false,
|
||||
});
|
||||
} else if (value === TRANSPORT.OPENAPI) {
|
||||
form.setFieldsValue({
|
||||
|
|
@ -641,16 +643,15 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
|
|||
env_vars: envVars,
|
||||
allow_all_keys: Boolean(allowAllKeysRaw ?? mcpServer.allow_all_keys),
|
||||
available_on_public_internet: Boolean(availableOnPublicInternetRaw ?? mcpServer.available_on_public_internet),
|
||||
// ``delegate_auth_to_upstream`` is only honored server-side for
|
||||
// ``auth_type=oauth2`` (PKCE passthrough). The Form.Item is
|
||||
// conditionally rendered so the value drops out of the form on
|
||||
// auth_type change; force false for any other configuration to avoid
|
||||
// persisting a stale ``true`` that would silently re-activate if the
|
||||
// configuration is later switched back.
|
||||
delegate_auth_to_upstream: (() => {
|
||||
const isOauth2 = restValues.auth_type === AUTH_TYPE.OAUTH2;
|
||||
return isOauth2 ? Boolean(delegateAuthToUpstreamRaw ?? mcpServer.delegate_auth_to_upstream) : false;
|
||||
})(),
|
||||
// ``delegate_auth_to_upstream`` (PKCE passthrough) is only honored for the
|
||||
// Interactive OAuth flow. The toggle is conditionally rendered, so force
|
||||
// false for M2M or non-oauth2 auth to avoid persisting a stale ``true``
|
||||
// that would silently re-activate if the configuration is later switched
|
||||
// back.
|
||||
delegate_auth_to_upstream:
|
||||
isOAuthAuthType && !isM2MFlow
|
||||
? Boolean(delegateAuthToUpstreamRaw ?? mcpServer.delegate_auth_to_upstream)
|
||||
: false,
|
||||
// ``oauth_passthrough`` is the dedicated, non-oauth2 opt-in. It is only
|
||||
// honored for ``auth_type=none`` servers that forward ``Authorization``
|
||||
// upstream. Kept separate from ``delegate_auth_to_upstream`` so enabling
|
||||
|
|
@ -913,6 +914,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
|
|||
|
||||
{!isStdioTransport && isOAuthAuthType && (
|
||||
<>
|
||||
{!isM2MFlow && <DelegateAuthToUpstreamField />}
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue