diff --git a/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx b/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx new file mode 100644 index 00000000000..5688f24d184 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/OAuthFormFields.tsx @@ -0,0 +1,151 @@ +import React from "react"; +import { Form, Select, Tooltip } from "antd"; +import { InfoCircleOutlined } from "@ant-design/icons"; +import { Button, TextInput } from "@tremor/react"; +import { OAUTH_FLOW } from "./types"; + +interface OAuthFlowStatus { + startOAuthFlow: () => void; + status: string; + error: string | null; + tokenResponse: { access_token?: string; expires_in?: number } | null; +} + +interface OAuthFormFieldsProps { + isM2M: boolean; + isEditing?: boolean; + oauthFlow?: OAuthFlowStatus; + initialFlowType?: string; +} + +const fieldClassName = "rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"; + +const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, tooltip }) => ( + + {label} + + + + +); + +const OAuthFormFields: React.FC = ({ + isM2M, + isEditing = false, + oauthFlow, + initialFlowType, +}) => { + const placeholderSuffix = isEditing ? " (leave blank to keep existing)" : ""; + + return ( + <> + OAuth Flow Type} + name="oauth_flow_type" + {...(initialFlowType ? { initialValue: initialFlowType } : {})} + > + + + + {isM2M ? ( + <> + } + name={["credentials", "client_id"]} + rules={[{ required: true, message: "Client ID is required for M2M OAuth" }]} + > + + + } + name={["credentials", "client_secret"]} + rules={[{ required: true, message: "Client Secret is required for M2M OAuth" }]} + > + + + } + name="token_url" + rules={[{ required: true, message: "Token URL is required for M2M OAuth" }]} + > + + + } + name={["credentials", "scopes"]} + > + + + } + name="authorization_url" + > + + + } + name="token_url" + > + + + } + name="registration_url" + > + + + {oauthFlow && ( +
+

+ Use OAuth to fetch a fresh access token and temporarily save it in the session as the authentication value. +

+ + {oauthFlow.error &&

{oauthFlow.error}

} + {oauthFlow.status === "success" && oauthFlow.tokenResponse?.access_token && ( +

+ Token fetched. Expires in {oauthFlow.tokenResponse.expires_in ?? "?"} seconds. +

+ )} +
+ )} + + )} + + ); +}; + +export default OAuthFormFields; diff --git a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx index a56cbaf492a..0ac7e8d3441 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx @@ -3,7 +3,8 @@ import { Modal, Tooltip, Form, Select, Input } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import { Button, TextInput } from "@tremor/react"; import { createMCPServer } from "../networking"; -import { AUTH_TYPE, MCPServer, MCPServerCostInfo } from "./types"; +import { AUTH_TYPE, OAUTH_FLOW, MCPServer, MCPServerCostInfo } from "./types"; +import OAuthFormFields from "./OAuthFormFields"; import MCPServerCostConfig from "./mcp_server_cost_config"; import MCPConnectionStatus from "./mcp_connection_status"; import MCPToolConfiguration from "./mcp_tool_configuration"; @@ -52,6 +53,8 @@ const CreateMCPServer: React.FC = ({ const authType = formValues.auth_type as string | undefined; const shouldShowAuthValueField = authType ? AUTH_TYPES_REQUIRING_AUTH_VALUE.includes(authType) : false; const isOAuthAuthType = authType === AUTH_TYPE.OAUTH2; + const isM2MFlow = isOAuthAuthType && formValues.oauth_flow_type === OAUTH_FLOW.M2M; + const isInteractiveFlow = isOAuthAuthType && formValues.oauth_flow_type !== OAUTH_FLOW.M2M; const persistCreateUiState = () => { if (typeof window === "undefined") { @@ -561,131 +564,16 @@ const CreateMCPServer: React.FC = ({ )} {transportType !== "stdio" && isOAuthAuthType && ( - <> - - OAuth Client ID (optional) - - - - - } - name={["credentials", "client_id"]} - > - - - - OAuth Client Secret (optional) - - - - - } - name={["credentials", "client_secret"]} - > - - - - OAuth Scopes (optional) - - - - - } - name={["credentials", "scopes"]} - > - + Interactive (PKCE) + Machine-to-Machine (M2M) + - - OAuth Client Secret (optional) - - - - - } - name={["credentials", "client_secret"]} - > - - - - OAuth Scopes (optional) - - - - - } - name={["credentials", "scopes"]} - > - + + + )} + + {/* Interactive (PKCE) fields: all optional, plus authorize button */} + {isInteractiveFlow && ( + <> + + OAuth Client ID (optional) + + + + + } + name={["credentials", "client_id"]} + > + + + + OAuth Client Secret (optional) + + + + + } + name={["credentials", "client_secret"]} + > + + + + OAuth Scopes (optional) + + + + + } + name={["credentials", "scopes"]} + > +