mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
Proxy UI MCP Auth passthrough (#11968)
* initial mcp auth with special header
* MCP Servers moved and added auth part
* Pass in x-mcp-auth with banner on tool call
* Revert "initial mcp auth with special header"
This reverts commit b22fb6d12e.
---------
Co-authored-by: wagnerjt <wagnerjt@github.com>
This commit is contained in:
parent
cde20cf825
commit
b3d8a6be9e
6 changed files with 364 additions and 181 deletions
|
|
@ -1,7 +1,34 @@
|
|||
import React from "react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { MCPTool, InputSchema } from "./types";
|
||||
import { Button } from "@tremor/react"
|
||||
import { Button, Callout, Icon } from "@tremor/react"
|
||||
|
||||
const AuthBanner = ({needsAuth, authValue}) => {
|
||||
if(!needsAuth || (needsAuth && authValue)) {
|
||||
return (
|
||||
<Callout
|
||||
title="Authentication"
|
||||
color="green"
|
||||
className="mb-4"
|
||||
>
|
||||
This tool does not require authentication or has authentication added.
|
||||
</Callout>
|
||||
)
|
||||
}
|
||||
|
||||
if (needsAuth && !authValue) {
|
||||
return (
|
||||
<Callout
|
||||
title="Authentication required"
|
||||
color="yellow"
|
||||
className="mb-4"
|
||||
>
|
||||
Please provide authentication details if this tool call requires auth.
|
||||
</Callout>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<MCPTool>[] = [
|
||||
{
|
||||
|
|
@ -78,6 +105,8 @@ export const columns: ColumnDef<MCPTool>[] = [
|
|||
// Tool Panel component to display when a tool is selected
|
||||
export function ToolTestPanel({
|
||||
tool,
|
||||
needsAuth,
|
||||
authValue,
|
||||
onSubmit,
|
||||
isLoading,
|
||||
result,
|
||||
|
|
@ -85,6 +114,8 @@ export function ToolTestPanel({
|
|||
onClose
|
||||
}: {
|
||||
tool: MCPTool;
|
||||
needsAuth: boolean;
|
||||
authValue?: string | null;
|
||||
onSubmit: (args: Record<string, any>) => void;
|
||||
isLoading: boolean;
|
||||
result: any | null;
|
||||
|
|
@ -131,6 +162,12 @@ export function ToolTestPanel({
|
|||
<p className="text-gray-600">{tool.description}</p>
|
||||
<p className="text-sm text-gray-500 mt-1">Provider: {tool.mcp_info.server_name}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<AuthBanner
|
||||
needsAuth={needsAuth}
|
||||
authValue={authValue}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 rounded-full hover:bg-gray-200"
|
||||
|
|
|
|||
|
|
@ -1,178 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { DataTable } from '../view_logs/table';
|
||||
import { columns, ToolTestPanel } from './columns';
|
||||
import { MCPTool, MCPToolsViewerProps, CallMCPToolResponse } from './types';
|
||||
import { listMCPTools, callMCPTool } from '../networking';
|
||||
import MCPServers from './mcp_servers';
|
||||
|
||||
// Wrapper to handle the type mismatch between MCPTool and DataTable's expected type
|
||||
function DataTableWrapper({
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
}: {
|
||||
columns: any;
|
||||
data: MCPTool[];
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
// Create a dummy renderSubComponent and getRowCanExpand function
|
||||
const renderSubComponent = () => <div />;
|
||||
const getRowCanExpand = () => false;
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns as any}
|
||||
data={data as any}
|
||||
isLoading={isLoading}
|
||||
renderSubComponent={renderSubComponent}
|
||||
getRowCanExpand={getRowCanExpand}
|
||||
loadingMessage="🚅 Loading tools..."
|
||||
noDataMessage="No tools found"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const MCPToolsViewer = ({
|
||||
serverId,
|
||||
accessToken,
|
||||
userRole,
|
||||
userID,
|
||||
}: MCPToolsViewerProps) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedTool, setSelectedTool] = useState<MCPTool | null>(null);
|
||||
const [toolResult, setToolResult] = useState<CallMCPToolResponse | null>(null);
|
||||
const [toolError, setToolError] = useState<Error | null>(null);
|
||||
|
||||
// Query to fetch MCP tools
|
||||
const { data: mcpTools, isLoading: isLoadingTools } = useQuery({
|
||||
queryKey: ['mcpTools'],
|
||||
queryFn: () => {
|
||||
if (!accessToken) throw new Error('Access Token required');
|
||||
return listMCPTools(accessToken, serverId);
|
||||
},
|
||||
enabled: !!accessToken,
|
||||
});
|
||||
|
||||
// Mutation for calling a tool
|
||||
const { mutate: executeTool, isPending: isCallingTool } = useMutation({
|
||||
mutationFn: (args: { tool: MCPTool; arguments: Record<string, any> }) => {
|
||||
if (!accessToken) throw new Error('Access Token required');
|
||||
return callMCPTool(
|
||||
accessToken,
|
||||
args.tool.name,
|
||||
args.arguments
|
||||
);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
setToolResult(data);
|
||||
setToolError(null);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
setToolError(error);
|
||||
setToolResult(null);
|
||||
},
|
||||
});
|
||||
|
||||
// Add onToolSelect handler to each tool
|
||||
const toolsData = React.useMemo(() => {
|
||||
if (!mcpTools) return [];
|
||||
|
||||
return mcpTools.map((tool: MCPTool) => ({
|
||||
...tool,
|
||||
onToolSelect: (tool: MCPTool) => {
|
||||
setSelectedTool(tool);
|
||||
setToolResult(null);
|
||||
setToolError(null);
|
||||
}
|
||||
}));
|
||||
}, [mcpTools]);
|
||||
|
||||
// Filter tools based on search term
|
||||
const filteredTools = React.useMemo(() => {
|
||||
return toolsData.filter((tool: MCPTool) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
return (
|
||||
tool.name.toLowerCase().includes(searchLower) ||
|
||||
(tool.description != null && tool.description.toLowerCase().includes(searchLower)) ||
|
||||
tool.mcp_info.server_name.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
}, [toolsData, searchTerm]);
|
||||
|
||||
// Handle tool call submission
|
||||
const handleToolSubmit = (args: Record<string, any>) => {
|
||||
if (!selectedTool) return;
|
||||
|
||||
executeTool({
|
||||
tool: selectedTool,
|
||||
arguments: args,
|
||||
});
|
||||
};
|
||||
|
||||
if (!accessToken || !userRole || !userID) {
|
||||
return <div className="p-6 text-center text-gray-500">Missing required authentication parameters.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-semibold">MCP Tools</h1>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="border-b px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="relative w-64">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tools..."
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<svg
|
||||
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{filteredTools.length} tool{filteredTools.length !== 1 ? "s" : ""} available
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTableWrapper
|
||||
columns={columns}
|
||||
data={filteredTools}
|
||||
isLoading={isLoadingTools}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tool Test Panel - Show when a tool is selected */}
|
||||
{selectedTool && (
|
||||
<div className="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
||||
<ToolTestPanel
|
||||
tool={selectedTool}
|
||||
onSubmit={handleToolSubmit}
|
||||
isLoading={isCallingTool}
|
||||
result={toolResult}
|
||||
error={toolError}
|
||||
onClose={() => setSelectedTool(null)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import MCPToolsViewer from './mcp_tools';
|
||||
|
||||
export { MCPToolsViewer, MCPServers };
|
||||
|
|
@ -96,6 +96,7 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
|
|||
<MCPToolsViewer
|
||||
serverId={mcpServer.server_id}
|
||||
accessToken={accessToken}
|
||||
auth_type={mcpServer.auth_type}
|
||||
userRole={userRole}
|
||||
userID={userID}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,306 @@
|
|||
import { MCPToolsViewer } from "./index";
|
||||
import React, { useState } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { DataTable } from "../view_logs/table";
|
||||
import { columns, ToolTestPanel } from "./columns";
|
||||
import {
|
||||
MCPTool,
|
||||
MCPToolsViewerProps,
|
||||
CallMCPToolResponse,
|
||||
mcpServerHasAuth,
|
||||
} from "./types";
|
||||
import { listMCPTools, callMCPTool } from "../networking";
|
||||
|
||||
import { Modal, Input, Form } from "antd";
|
||||
import { Button } from "@tremor/react";
|
||||
|
||||
import { AUTH_TYPE } from "./types";
|
||||
|
||||
type AuthModalProps = {
|
||||
visible: boolean;
|
||||
onOk: (values: any) => void;
|
||||
onCancel: () => void;
|
||||
authType?: string | null;
|
||||
};
|
||||
|
||||
export const AuthModal = ({
|
||||
visible,
|
||||
onOk,
|
||||
onCancel,
|
||||
authType,
|
||||
}: AuthModalProps) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// Handler for modal OK
|
||||
const handleOk = () => {
|
||||
form.validateFields().then((values) => {
|
||||
if (authType === AUTH_TYPE.BASIC) {
|
||||
onOk(`${values.username.trim()}:${values.password.trim()}`);
|
||||
} else {
|
||||
onOk(values.authValue.trim());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let content;
|
||||
if (authType === AUTH_TYPE.API_KEY || authType === AUTH_TYPE.BEARER_TOKEN) {
|
||||
const label = authType === AUTH_TYPE.API_KEY ? "API Key" : "Bearer Token";
|
||||
content = (
|
||||
<Form.Item
|
||||
name="authValue"
|
||||
label={label}
|
||||
rules={[{ required: true, message: `Please input your ${label}` }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
);
|
||||
} else if (authType === AUTH_TYPE.BASIC) {
|
||||
content = (
|
||||
<>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label="Username"
|
||||
rules={[{ required: true, message: "Please input your username" }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="Password"
|
||||
rules={[{ required: true, message: "Please input your password" }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
title="Authentication"
|
||||
onOk={handleOk}
|
||||
onCancel={onCancel}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
{content}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const AuthSection = ({ authType, onAuthSubmit }) => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
const handleAddAuth = () => setModalVisible(true);
|
||||
|
||||
const handleModalOk = (authValue) => {
|
||||
onAuthSubmit(authValue);
|
||||
setModalVisible(false);
|
||||
};
|
||||
|
||||
const handleModalCancel = () => setModalVisible(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={handleAddAuth}>
|
||||
Add Auth
|
||||
</Button>
|
||||
<AuthModal
|
||||
visible={modalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
authType={authType}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Wrapper to handle the type mismatch between MCPTool and DataTable's expected type
|
||||
function DataTableWrapper({
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
}: {
|
||||
columns: any;
|
||||
data: MCPTool[];
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
// Create a dummy renderSubComponent and getRowCanExpand function
|
||||
const renderSubComponent = () => <div />;
|
||||
const getRowCanExpand = () => false;
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns as any}
|
||||
data={data as any}
|
||||
isLoading={isLoading}
|
||||
renderSubComponent={renderSubComponent}
|
||||
getRowCanExpand={getRowCanExpand}
|
||||
loadingMessage="🚅 Loading tools..."
|
||||
noDataMessage="No tools found"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const MCPToolsViewer = ({
|
||||
serverId,
|
||||
accessToken,
|
||||
auth_type,
|
||||
userRole,
|
||||
userID,
|
||||
}: MCPToolsViewerProps) => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [mcpAuthValue, setMcpAuthValue] = useState("");
|
||||
const [selectedTool, setSelectedTool] = useState<MCPTool | null>(null);
|
||||
const [toolResult, setToolResult] = useState<CallMCPToolResponse | null>(
|
||||
null
|
||||
);
|
||||
const [toolError, setToolError] = useState<Error | null>(null);
|
||||
|
||||
// Query to fetch MCP tools
|
||||
const { data: mcpTools, isLoading: isLoadingTools } = useQuery({
|
||||
queryKey: ["mcpTools"],
|
||||
queryFn: () => {
|
||||
if (!accessToken) throw new Error("Access Token required");
|
||||
return listMCPTools(accessToken, serverId);
|
||||
},
|
||||
enabled: !!accessToken,
|
||||
});
|
||||
|
||||
// Mutation for calling a tool
|
||||
const { mutate: executeTool, isPending: isCallingTool } = useMutation({
|
||||
mutationFn: (args: { tool: MCPTool; arguments: Record<string, any>, authValue: string }) => {
|
||||
if (!accessToken) throw new Error("Access Token required");
|
||||
return callMCPTool(accessToken, args.tool.name, args.arguments, args.authValue);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
setToolResult(data);
|
||||
setToolError(null);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
setToolError(error);
|
||||
setToolResult(null);
|
||||
},
|
||||
});
|
||||
|
||||
// Add onToolSelect handler to each tool
|
||||
const toolsData = React.useMemo(() => {
|
||||
if (!mcpTools) return [];
|
||||
|
||||
return mcpTools.map((tool: MCPTool) => ({
|
||||
...tool,
|
||||
onToolSelect: (tool: MCPTool) => {
|
||||
setSelectedTool(tool);
|
||||
setToolResult(null);
|
||||
setToolError(null);
|
||||
},
|
||||
}));
|
||||
}, [mcpTools]);
|
||||
|
||||
// Filter tools based on search term
|
||||
const filteredTools = React.useMemo(() => {
|
||||
return toolsData.filter((tool: MCPTool) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
return (
|
||||
tool.name.toLowerCase().includes(searchLower) ||
|
||||
(tool.description != null &&
|
||||
tool.description.toLowerCase().includes(searchLower)) ||
|
||||
tool.mcp_info.server_name.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
}, [toolsData, searchTerm]);
|
||||
|
||||
// Handle tool call submission
|
||||
const handleToolSubmit = (args: Record<string, any>) => {
|
||||
if (!selectedTool) return;
|
||||
|
||||
executeTool({
|
||||
tool: selectedTool,
|
||||
arguments: args,
|
||||
authValue: mcpAuthValue
|
||||
});
|
||||
};
|
||||
|
||||
if (!accessToken || !userRole || !userID) {
|
||||
return (
|
||||
<div className="p-6 text-center text-gray-500">
|
||||
Missing required authentication parameters.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h1 className="text-xl font-semibold">MCP Tools</h1>
|
||||
</div>
|
||||
|
||||
{mcpServerHasAuth(auth_type) && (
|
||||
<AuthSection
|
||||
authType={auth_type}
|
||||
onAuthSubmit={(value) => {
|
||||
setMcpAuthValue(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="border-b px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="relative w-64">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tools..."
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<svg
|
||||
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{filteredTools.length} tool{filteredTools.length !== 1 ? "s" : ""}{" "}
|
||||
available
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTableWrapper
|
||||
columns={columns}
|
||||
data={filteredTools}
|
||||
isLoading={isLoadingTools}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tool Test Panel - Show when a tool is selected */}
|
||||
{selectedTool && (
|
||||
<div className="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center z-50 p-4">
|
||||
<ToolTestPanel
|
||||
tool={selectedTool}
|
||||
needsAuth={mcpServerHasAuth(auth_type)}
|
||||
authValue={mcpAuthValue}
|
||||
onSubmit={handleToolSubmit}
|
||||
isLoading={isCallingTool}
|
||||
result={toolResult}
|
||||
error={toolError}
|
||||
onClose={() => setSelectedTool(null)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: Move Tools viewer from index file to this file
|
||||
export default MCPToolsViewer;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
// Default no auth value
|
||||
export const AUTH_TYPE = {
|
||||
NONE: "none",
|
||||
API_KEY: "api_key",
|
||||
BEARER_TOKEN: "bearer_token",
|
||||
BASIC: "basic",
|
||||
};
|
||||
|
||||
export const TRANSPORT = {
|
||||
SSE: "sse",
|
||||
HTTP: "http",
|
||||
|
|
@ -14,12 +22,16 @@ export const handleTransport = (transport?: string | null): string => {
|
|||
|
||||
export const handleAuth = (authType?: string | null): string => {
|
||||
if (authType === null || authType === undefined) {
|
||||
return "none";
|
||||
return AUTH_TYPE.NONE;
|
||||
}
|
||||
|
||||
return authType;
|
||||
};
|
||||
|
||||
export const mcpServerHasAuth = (authType?: string | null): boolean => {
|
||||
return handleAuth(authType) !== AUTH_TYPE.NONE;
|
||||
}
|
||||
|
||||
// Define the structure for tool input schema properties
|
||||
export interface InputSchemaProperty {
|
||||
type: string;
|
||||
|
|
@ -89,6 +101,7 @@ export interface InputSchemaProperty {
|
|||
export interface MCPToolsViewerProps {
|
||||
serverId: string;
|
||||
accessToken: string | null;
|
||||
auth_type?: string | null;
|
||||
userRole: string | null;
|
||||
userID: string | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,8 @@ const handleError = async (errorData: string) => {
|
|||
};
|
||||
|
||||
// Global variable for the header name
|
||||
let globalLitellmHeaderName: string = "Authorization";
|
||||
let globalLitellmHeaderName: string = "Authorization";
|
||||
const MCP_AUTH_HEADER: string = "x-mcp-auth";
|
||||
|
||||
// Function to set the global header name
|
||||
export function setGlobalLitellmHeaderName(
|
||||
|
|
@ -4882,10 +4883,12 @@ export const listMCPTools = async (accessToken: string, serverId: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
export const callMCPTool = async (
|
||||
accessToken: string,
|
||||
toolName: string,
|
||||
toolArguments: Record<string, any>
|
||||
toolArguments: Record<string, any>,
|
||||
authValue: string,
|
||||
) => {
|
||||
try {
|
||||
// Construct base URL
|
||||
|
|
@ -4904,6 +4907,7 @@ export const callMCPTool = async (
|
|||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
[MCP_AUTH_HEADER]: authValue,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue