mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
chore(ui): satisfy frontend-lint gate on touched MCP files
The frontend-lint job (added on the base branch after this branch diverged) runs prettier and eslint on the UI files a PR touches, measuring eslint errors against the committed eslint-suppressions.json baseline. Pulling the base in brings that gate, its config, and the baseline. Format the touched MCP env var components and networking.tsx so they are prettier-clean, and extend the suppressions baseline to cover the findings this branch adds in files that already carry grandfathered entries: the four extra raw fetch wrappers in networking.tsx (the API layer, where 270 raw fetches are already grandfathered and there is no React Query alternative) and the setState-in-effect findings in mcp_servers.tsx and UserEnvVarsModal.tsx, matching the same rule already baselined in the sibling MCP components.
This commit is contained in:
parent
fe20b4b904
commit
63a9f00103
10 changed files with 303 additions and 431 deletions
|
|
@ -54,9 +54,7 @@ test.describe("MCP Servers", () => {
|
|||
// the MCP servers grid so the form modal's `server_name` input — which
|
||||
// still holds the timestamped value during its close animation — can't
|
||||
// satisfy the assertion before the server actually lands in the list.
|
||||
await expect(page.getByText("MCP Server created successfully").first())
|
||||
.toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByTestId("mcp-servers-grid").getByText(uniqueName).first())
|
||||
.toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByText("MCP Server created successfully").first()).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByTestId("mcp-servers-grid").getByText(uniqueName).first()).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1307,6 +1307,11 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/mcp_tools/UserEnvVarsModal.tsx": {
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/mcp_tools/create_mcp_server.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
|
@ -1377,7 +1382,7 @@
|
|||
"count": 1
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 2
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"src/components/mcp_tools/mcp_tool_configuration.tsx": {
|
||||
|
|
@ -1504,7 +1509,7 @@
|
|||
"count": 23
|
||||
},
|
||||
"no-restricted-syntax": {
|
||||
"count": 270
|
||||
"count": 274
|
||||
}
|
||||
},
|
||||
"src/components/object_permissions_view.tsx": {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import React from "react";
|
||||
import { Form, Input, Select, Button, Tooltip, Typography } from "antd";
|
||||
import {
|
||||
InfoCircleOutlined,
|
||||
MinusCircleOutlined,
|
||||
PlusOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { InfoCircleOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
|
@ -33,12 +29,12 @@ const EnvVarsSection: React.FC = () => {
|
|||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
Define variables you can interpolate in Static Headers or
|
||||
Authentication using <code>{"${VAR_NAME}"}</code>. <br />
|
||||
Define variables you can interpolate in Static Headers or Authentication using{" "}
|
||||
<code>{"${VAR_NAME}"}</code>. <br />
|
||||
<b>Instance</b>: admin-defined value used for every user.
|
||||
<br />
|
||||
<b>Per-user</b>: each user supplies their own value (e.g. personal
|
||||
credentials) via the MCP Gateway dashboard.
|
||||
<b>Per-user</b>: each user supplies their own value (e.g. personal credentials) via the MCP Gateway
|
||||
dashboard.
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
|
@ -46,8 +42,7 @@ const EnvVarsSection: React.FC = () => {
|
|||
</Tooltip>
|
||||
</div>
|
||||
<Text className="text-xs text-gray-600 block mb-3">
|
||||
Reference these in Static Headers or Authentication as{" "}
|
||||
<code>{"${VAR_NAME}"}</code>. For example:{" "}
|
||||
Reference these in Static Headers or Authentication as <code>{"${VAR_NAME}"}</code>. For example:{" "}
|
||||
<code className="bg-white px-1 rounded border border-gray-200">
|
||||
{"${DB_PROTOCOL}://${CORP_USERNAME}:${CORP_PASSWORD}@${DB_HOSTNAME}"}
|
||||
</code>
|
||||
|
|
@ -79,10 +74,7 @@ const EnvVarsSection: React.FC = () => {
|
|||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g. DB_PROTOCOL"
|
||||
className="rounded-md font-mono"
|
||||
/>
|
||||
<Input placeholder="e.g. DB_PROTOCOL" className="rounded-md font-mono" />
|
||||
</Form.Item>
|
||||
<div style={{ flex: 1 }}>
|
||||
<ScopedValueOrDescription name={name} restField={restField} />
|
||||
|
|
@ -96,10 +88,7 @@ const EnvVarsSection: React.FC = () => {
|
|||
>
|
||||
<Select options={SCOPE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<div
|
||||
style={{ width: 24, height: 32 }}
|
||||
className="flex items-center justify-center"
|
||||
>
|
||||
<div style={{ width: 24, height: 32 }} className="flex items-center justify-center">
|
||||
<MinusCircleOutlined
|
||||
onClick={() => remove(name)}
|
||||
className="text-gray-500 hover:text-red-500 cursor-pointer"
|
||||
|
|
@ -107,12 +96,7 @@ const EnvVarsSection: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add({ scope: "global" })}
|
||||
icon={<PlusOutlined />}
|
||||
block
|
||||
>
|
||||
<Button type="dashed" onClick={() => add({ scope: "global" })} icon={<PlusOutlined />} block>
|
||||
Add Variable
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,10 +27,9 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
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",
|
||||
);
|
||||
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).
|
||||
|
|
@ -42,10 +41,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
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;
|
||||
const showInternalDelegatePkceWarning = isOAuth2 && watchedDelegateAuth === true && watchedPublicInternet === false;
|
||||
|
||||
// Set initial values when mcpServer changes
|
||||
useEffect(() => {
|
||||
|
|
@ -130,7 +126,9 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
<p className="text-sm text-gray-600 mt-1">Enable if this server should be "public" to all keys.</p>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Enable if this server should be "public" to all keys.
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="allow_all_keys"
|
||||
|
|
@ -150,7 +148,9 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
<p className="text-sm text-gray-600 mt-1">Turn on to restrict access to callers within your internal network only.</p>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Turn on to restrict access to callers within your internal network only.
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="available_on_public_internet"
|
||||
|
|
@ -198,7 +198,8 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
</Tooltip>
|
||||
</span>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Forward upstream OAuth discovery and 401 challenges so clients negotiate OAuth directly with the upstream MCP server.
|
||||
Forward upstream OAuth discovery and 401 challenges so clients negotiate OAuth directly with the
|
||||
upstream MCP server.
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item
|
||||
|
|
@ -313,12 +314,7 @@ const MCPPermissionManagement: React.FC<MCPPermissionManagementProps> = ({
|
|||
className="flex-1"
|
||||
rules={[{ required: true, message: "Header value is required" }]}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
allowClear
|
||||
className="rounded-lg"
|
||||
placeholder="Header value"
|
||||
/>
|
||||
<Input size="large" allowClear className="rounded-lg" placeholder="Header value" />
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined
|
||||
onClick={() => remove(name)}
|
||||
|
|
|
|||
|
|
@ -53,18 +53,14 @@ const MCPServerCard: FC<MCPServerCardProps> = ({
|
|||
// Logo is sourced exclusively from the admin-set `mcp_info.logo_url`.
|
||||
const candidateLogo = server.mcp_info?.logo_url ?? undefined;
|
||||
const [failedLogoUrl, setFailedLogoUrl] = useState<string | null>(null);
|
||||
const logoUrl =
|
||||
candidateLogo && failedLogoUrl !== candidateLogo ? candidateLogo : undefined;
|
||||
const logoUrl = candidateLogo && failedLogoUrl !== candidateLogo ? candidateLogo : undefined;
|
||||
const transport = server.transport || "http";
|
||||
const displayTransport =
|
||||
server.spec_path && transport !== "stdio" ? "openapi" : transport;
|
||||
const displayTransport = server.spec_path && transport !== "stdio" ? "openapi" : transport;
|
||||
const authType = server.auth_type || "none";
|
||||
const status = server.status || "unknown";
|
||||
const healthTone = HEALTH_TONE[status] ?? HEALTH_TONE.unknown;
|
||||
const isPublic = server.available_on_public_internet;
|
||||
const accessGroups = (server.mcp_access_groups ?? []).filter(
|
||||
(g): g is string => typeof g === "string",
|
||||
);
|
||||
const accessGroups = (server.mcp_access_groups ?? []).filter((g): g is string => typeof g === "string");
|
||||
|
||||
const missing = missingUserFields ?? [];
|
||||
const needsAttention = missing.length > 0;
|
||||
|
|
@ -82,8 +78,9 @@ const MCPServerCard: FC<MCPServerCardProps> = ({
|
|||
let subtitle = "";
|
||||
let subtitleTooltip = "";
|
||||
if (transport === "stdio") {
|
||||
const parts = [server.command, ...(server.args ?? [])]
|
||||
.filter((p): p is string => typeof p === "string" && p.length > 0);
|
||||
const parts = [server.command, ...(server.args ?? [])].filter(
|
||||
(p): p is string => typeof p === "string" && p.length > 0,
|
||||
);
|
||||
subtitle = parts.join(" ");
|
||||
subtitleTooltip = subtitle;
|
||||
} else if (server.spec_path) {
|
||||
|
|
@ -156,28 +153,19 @@ const MCPServerCard: FC<MCPServerCardProps> = ({
|
|||
</div>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div
|
||||
className="block w-full truncate text-left font-semibold text-gray-900"
|
||||
title={name}
|
||||
>
|
||||
<div className="block w-full truncate text-left font-semibold text-gray-900" title={name}>
|
||||
{name}
|
||||
</div>
|
||||
<div className="mt-0.5 flex items-center gap-2 text-xs text-gray-500">
|
||||
{alias && <span className="truncate">{alias}</span>}
|
||||
{alias && <span className="text-gray-300">·</span>}
|
||||
<Tooltip title={server.server_id}>
|
||||
<span className="font-mono text-blue-600">
|
||||
{server.server_id.slice(0, 7)}
|
||||
</span>
|
||||
<span className="font-mono text-blue-600">{server.server_id.slice(0, 7)}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{menuItems.length > 0 && (
|
||||
<Dropdown
|
||||
menu={{ items: menuItems }}
|
||||
trigger={["click"]}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Dropdown menu={{ items: menuItems }} trigger={["click"]} placement="bottomRight">
|
||||
<button
|
||||
type="button"
|
||||
onClick={stop}
|
||||
|
|
@ -193,10 +181,7 @@ const MCPServerCard: FC<MCPServerCardProps> = ({
|
|||
|
||||
{subtitle ? (
|
||||
<Tooltip title={subtitleTooltip}>
|
||||
<Text
|
||||
className="truncate font-mono text-xs text-gray-500"
|
||||
ellipsis
|
||||
>
|
||||
<Text className="truncate font-mono text-xs text-gray-500" ellipsis>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
|
|
@ -220,11 +205,7 @@ const MCPServerCard: FC<MCPServerCardProps> = ({
|
|||
<Tag className="m-0">{authType}</Tag>
|
||||
<Tag color={isPublic ? "green" : "orange"} className="m-0">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<span
|
||||
className={`h-1.5 w-1.5 rounded-full ${
|
||||
isPublic ? "bg-green-500" : "bg-orange-500"
|
||||
}`}
|
||||
/>
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${isPublic ? "bg-green-500" : "bg-orange-500"}`} />
|
||||
{isPublic ? "Public" : "Internal"}
|
||||
</span>
|
||||
</Tag>
|
||||
|
|
@ -242,12 +223,7 @@ const MCPServerCard: FC<MCPServerCardProps> = ({
|
|||
|
||||
{(server.is_byok || needsAttention) && (
|
||||
<div className="mt-auto flex flex-col gap-2">
|
||||
{server.is_byok && (
|
||||
<ByokRow
|
||||
connected={!!server.has_user_credential}
|
||||
onConnect={onByokConnect}
|
||||
/>
|
||||
)}
|
||||
{server.is_byok && <ByokRow connected={!!server.has_user_credential} onConnect={onByokConnect} />}
|
||||
{needsAttention && (
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<Tooltip
|
||||
|
|
@ -320,20 +296,14 @@ const HealthChip: FC<HealthChipProps> = ({
|
|||
const tooltip = (
|
||||
<div className="max-w-xs">
|
||||
<div className="font-semibold mb-1">Health: {status}</div>
|
||||
{lastCheck && (
|
||||
<div className="text-xs mb-1">
|
||||
Last check: {new Date(lastCheck).toLocaleString()}
|
||||
</div>
|
||||
)}
|
||||
{lastCheck && <div className="text-xs mb-1">Last check: {new Date(lastCheck).toLocaleString()}</div>}
|
||||
{error && (
|
||||
<div className="text-xs">
|
||||
<div className="font-medium text-red-300 mb-1">Error</div>
|
||||
<div className="break-words">{error}</div>
|
||||
</div>
|
||||
)}
|
||||
{!lastCheck && !error && (
|
||||
<div className="text-xs text-gray-400">No health data</div>
|
||||
)}
|
||||
{!lastCheck && !error && <div className="text-xs text-gray-400">No health data</div>}
|
||||
{onRecheck && <div className="mt-1 text-xs text-gray-300">Click to recheck</div>}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Modal, Form, Input, Button, Alert, Spin, Tag, Typography } from "antd";
|
||||
import { MCPServer, MCPUserEnvVarsStatus } from "./types";
|
||||
import {
|
||||
getMCPUserEnvVars,
|
||||
storeMCPUserEnvVars,
|
||||
} from "../networking";
|
||||
import { getMCPUserEnvVars, storeMCPUserEnvVars } from "../networking";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
|
@ -24,13 +21,7 @@ interface UserEnvVarsModalProps {
|
|||
* the admin marked as ``scope=user`` shows up with the admin-supplied
|
||||
* description as the placeholder.
|
||||
*/
|
||||
const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({
|
||||
server,
|
||||
open,
|
||||
accessToken,
|
||||
onClose,
|
||||
onSaved,
|
||||
}) => {
|
||||
const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, accessToken, onClose, onSaved }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [status, setStatus] = useState<MCPUserEnvVarsStatus | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
|
@ -77,9 +68,7 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({
|
|||
if (onSaved) onSaved(saved);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
NotificationsManager.fromBackend(
|
||||
`Failed to save env vars: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
NotificationsManager.fromBackend(`Failed to save env vars: ${err instanceof Error ? err.message : String(err)}`);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
@ -115,49 +104,31 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({
|
|||
<Spin />
|
||||
</div>
|
||||
) : required.length === 0 ? (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="No per-user fields configured for this server."
|
||||
/>
|
||||
<Alert type="info" showIcon message="No per-user fields configured for this server." />
|
||||
) : (
|
||||
<>
|
||||
<Text className="text-sm text-gray-600 block">
|
||||
These values are private to you. Your admin configured this MCP
|
||||
server to require these per-user credentials. Saved values are
|
||||
never shown back; leave an already-set field blank to keep it, or
|
||||
enter a value to set or change it.
|
||||
These values are private to you. Your admin configured this MCP server to require these per-user
|
||||
credentials. Saved values are never shown back; leave an already-set field blank to keep it, or enter a
|
||||
value to set or change it.
|
||||
</Text>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSave}
|
||||
disabled={isSaving}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSave} disabled={isSaving}>
|
||||
{required.map((spec) => (
|
||||
<Form.Item
|
||||
key={spec.name}
|
||||
name={spec.name}
|
||||
label={
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="font-mono text-sm font-semibold">
|
||||
{spec.name}
|
||||
</span>
|
||||
<span className="font-mono text-sm font-semibold">{spec.name}</span>
|
||||
{spec.is_set && <Tag color="green">Set</Tag>}
|
||||
</span>
|
||||
}
|
||||
extra={spec.description || undefined}
|
||||
rules={
|
||||
spec.is_set
|
||||
? undefined
|
||||
: [{ required: true, message: `${spec.name} is required` }]
|
||||
}
|
||||
rules={spec.is_set ? undefined : [{ required: true, message: `${spec.name} is required` }]}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder={
|
||||
spec.is_set
|
||||
? "Enter a new value to overwrite"
|
||||
: spec.description || `Enter your ${spec.name}`
|
||||
spec.is_set ? "Enter a new value to overwrite" : spec.description || `Enter your ${spec.name}`
|
||||
}
|
||||
visibilityToggle
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -81,12 +81,13 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
const [oauthDocsUrl, setOauthDocsUrl] = useState<string | null>(null);
|
||||
|
||||
// Single hook call shared by MCPConnectionStatus and MCPToolConfiguration to avoid duplicate requests.
|
||||
const { tools, isLoadingTools, toolsError, toolsErrorStackTrace, canFetchTools, fetchTools, clearTools } = useTestMCPConnection({
|
||||
accessToken,
|
||||
oauthAccessToken,
|
||||
formValues,
|
||||
enabled: true,
|
||||
});
|
||||
const { tools, isLoadingTools, toolsError, toolsErrorStackTrace, canFetchTools, fetchTools, clearTools } =
|
||||
useTestMCPConnection({
|
||||
accessToken,
|
||||
oauthAccessToken,
|
||||
formValues,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const authType = formValues.auth_type as string | undefined;
|
||||
const shouldShowAuthValueField = authType ? AUTH_TYPES_REQUIRING_AUTH_VALUE.includes(authType) : false;
|
||||
|
|
@ -439,9 +440,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
}
|
||||
|
||||
NotificationsManager.success(
|
||||
isAdmin
|
||||
? "MCP Server created successfully"
|
||||
: "MCP Server submitted for admin review"
|
||||
isAdmin ? "MCP Server created successfully" : "MCP Server submitted for admin review",
|
||||
);
|
||||
form.resetFields();
|
||||
setCostConfig({});
|
||||
|
|
@ -456,7 +455,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
NotificationsManager.fromBackend(
|
||||
isAdmin ? `Error creating MCP Server: ${reason}` : `Error submitting MCP Server: ${reason}`
|
||||
isAdmin ? `Error creating MCP Server: ${reason}` : `Error submitting MCP Server: ${reason}`,
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
|
@ -587,8 +586,8 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
>
|
||||
{!isAdmin && (
|
||||
<div className="rounded-md bg-blue-50 border border-blue-200 px-4 py-3 text-sm text-blue-800">
|
||||
Your submission will be sent for admin review before it becomes active.
|
||||
{" "}Note: the request must be made with a team-scoped API key.
|
||||
Your submission will be sent for admin review before it becomes active. Note: the request must be made
|
||||
with a team-scoped API key.
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
|
|
@ -701,9 +700,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
<OpenAPIFormSection
|
||||
form={form}
|
||||
accessToken={isModalVisible ? accessToken : null}
|
||||
onValuesChange={(updates) =>
|
||||
setFormValues((prev) => ({ ...prev, ...updates }))
|
||||
}
|
||||
onValuesChange={(updates) => setFormValues((prev) => ({ ...prev, ...updates }))}
|
||||
onKeyToolsChange={setKeyTools}
|
||||
onLogoUrlChange={setLogoUrl}
|
||||
onOAuthDocsUrlChange={setOauthDocsUrl}
|
||||
|
|
@ -728,7 +725,10 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.is_byok !== cur.is_byok || prev.auth_type !== cur.auth_type}>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prev, cur) => prev.is_byok !== cur.is_byok || prev.auth_type !== cur.auth_type}
|
||||
>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue("is_byok") ? (
|
||||
<>
|
||||
|
|
@ -752,7 +752,10 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
{!getFieldValue("auth_type") && (
|
||||
<div className="mb-4 p-3 bg-yellow-50 rounded-lg text-sm text-yellow-700 flex items-start gap-2">
|
||||
<InfoCircleOutlined className="mt-0.5 flex-shrink-0" />
|
||||
<span>Set the <strong>Authentication Type</strong> below to specify how user keys are sent (e.g., Bearer Token, API Key header).</span>
|
||||
<span>
|
||||
Set the <strong>Authentication Type</strong> below to specify how user keys are sent
|
||||
(e.g., Bearer Token, API Key header).
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Form.Item
|
||||
|
|
@ -805,10 +808,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
label: <span className="text-sm font-semibold text-gray-700">Authentication</span>,
|
||||
children: (
|
||||
<>
|
||||
<Form.Item
|
||||
name="auth_type"
|
||||
rules={[{ required: true, message: "Please select an auth type" }]}
|
||||
>
|
||||
<Form.Item name="auth_type" rules={[{ required: true, message: "Please select an auth type" }]}>
|
||||
<Select placeholder="Select auth type" className="rounded-lg" size="large">
|
||||
<Select.Option value="none">None</Select.Option>
|
||||
<Select.Option value="api_key">API Key</Select.Option>
|
||||
|
|
@ -872,7 +872,12 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
<>
|
||||
<p className="text-sm text-gray-500 mb-2">
|
||||
For MCP servers hosted on AWS Bedrock AgentCore.{" "}
|
||||
<a href="https://docs.litellm.ai/docs/mcp_aws_sigv4" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-700">
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/mcp_aws_sigv4"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:text-blue-700"
|
||||
>
|
||||
View docs →
|
||||
</a>
|
||||
</p>
|
||||
|
|
@ -925,7 +930,9 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
validator(_, value) {
|
||||
const secretKey = getFieldValue(["credentials", "aws_secret_access_key"]);
|
||||
if (secretKey && !value) {
|
||||
return Promise.reject(new Error("Access Key ID is required when Secret Access Key is provided"));
|
||||
return Promise.reject(
|
||||
new Error("Access Key ID is required when Secret Access Key is provided"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -953,7 +960,9 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
validator(_, value) {
|
||||
const accessKeyId = getFieldValue(["credentials", "aws_access_key_id"]);
|
||||
if (accessKeyId && !value) {
|
||||
return Promise.reject(new Error("Secret Access Key is required when Access Key ID is provided"));
|
||||
return Promise.reject(
|
||||
new Error("Secret Access Key is required when Access Key ID is provided"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -599,20 +599,15 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
|
|||
// 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;
|
||||
return isOauth2 ? 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
|
||||
// pass-through never regresses oauth2 servers. Force false otherwise.
|
||||
oauth_passthrough: (() => {
|
||||
const isNoneAuth =
|
||||
restValues.auth_type === AUTH_TYPE.NONE || restValues.auth_type == null;
|
||||
const extraHeaders = Array.isArray(restValues.extra_headers)
|
||||
? restValues.extra_headers
|
||||
: [];
|
||||
const isNoneAuth = restValues.auth_type === AUTH_TYPE.NONE || restValues.auth_type == null;
|
||||
const extraHeaders = Array.isArray(restValues.extra_headers) ? restValues.extra_headers : [];
|
||||
const hasAuthorizationHeader = extraHeaders.some(
|
||||
(h: unknown) => typeof h === "string" && h.toLowerCase() === "authorization",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,11 +38,7 @@ const HEALTH_RANK: Record<string, number> = {
|
|||
healthy: 2,
|
||||
};
|
||||
|
||||
const compareServers = (
|
||||
a: MCPServer,
|
||||
b: MCPServer,
|
||||
sort: SortKey,
|
||||
): number => {
|
||||
const compareServers = (a: MCPServer, b: MCPServer, sort: SortKey): number => {
|
||||
switch (sort) {
|
||||
case "name_asc": {
|
||||
const nameA = (a.server_name || a.alias || a.server_id).toLowerCase();
|
||||
|
|
@ -80,7 +76,12 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
const { data: mcpServers, isLoading: isLoadingServers, refetch } = useMCPServers();
|
||||
|
||||
// Fetch health status for all servers
|
||||
const { data: healthStatuses, isLoading: isLoadingHealth, recheckServerHealth, recheckingServerIds } = useMCPServerHealth();
|
||||
const {
|
||||
data: healthStatuses,
|
||||
isLoading: isLoadingHealth,
|
||||
recheckServerHealth,
|
||||
recheckingServerIds,
|
||||
} = useMCPServerHealth();
|
||||
|
||||
// Merge health status data into servers
|
||||
const serversWithHealth = useMemo(() => {
|
||||
|
|
@ -93,9 +94,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
const healthStatus = healthMap.get(server.server_id);
|
||||
return {
|
||||
...server,
|
||||
status: healthStatus
|
||||
? (healthStatus as "healthy" | "unhealthy" | "unknown")
|
||||
: server.status,
|
||||
status: healthStatus ? (healthStatus as "healthy" | "unhealthy" | "unknown") : server.status,
|
||||
};
|
||||
});
|
||||
}, [mcpServers, healthStatuses]);
|
||||
|
|
@ -147,9 +146,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
const missingFieldsByServer = useMemo(() => {
|
||||
const map: Record<string, string[]> = {};
|
||||
for (const [serverId, status] of Object.entries(envVarStatusByServer)) {
|
||||
map[serverId] = (status.required ?? [])
|
||||
.filter((spec) => !spec.is_set)
|
||||
.map((spec) => spec.name);
|
||||
map[serverId] = (status.required ?? []).filter((spec) => !spec.is_set).map((spec) => spec.name);
|
||||
}
|
||||
return map;
|
||||
}, [envVarStatusByServer]);
|
||||
|
|
@ -168,10 +165,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
setEnvVarsModalServer(match);
|
||||
params.delete("fill_env_vars");
|
||||
const newSearch = params.toString();
|
||||
const newUrl =
|
||||
window.location.pathname +
|
||||
(newSearch ? `?${newSearch}` : "") +
|
||||
window.location.hash;
|
||||
const newUrl = window.location.pathname + (newSearch ? `?${newSearch}` : "") + window.location.hash;
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
}
|
||||
}, [serversWithHealth]);
|
||||
|
|
@ -219,35 +213,40 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
if (!serversWithHealth) return [];
|
||||
return Array.from(
|
||||
new Set(
|
||||
serversWithHealth.flatMap((server) => server.mcp_access_groups).filter((group): group is string => group != null),
|
||||
serversWithHealth
|
||||
.flatMap((server) => server.mcp_access_groups)
|
||||
.filter((group): group is string => group != null),
|
||||
),
|
||||
);
|
||||
}, [serversWithHealth]);
|
||||
|
||||
// Filtering logic for both team and access group
|
||||
const filterServers = useCallback((teamId: string, group: string) => {
|
||||
if (!serversWithHealth) return setFilteredServers([]);
|
||||
let filtered = serversWithHealth;
|
||||
if (teamId === "personal") {
|
||||
setFilteredServers([]);
|
||||
return;
|
||||
}
|
||||
if (teamId !== "all") {
|
||||
filtered = filtered.filter((server) => server.teams?.some((team) => team.team_id === teamId));
|
||||
}
|
||||
if (group !== "all") {
|
||||
filtered = filtered.filter((server) =>
|
||||
server.mcp_access_groups?.some((g: any) => (typeof g === "string" ? g === group : g && g.name === group)),
|
||||
);
|
||||
}
|
||||
const sorted = [...filtered].sort((a, b) => {
|
||||
if (!a.created_at && !b.created_at) return 0;
|
||||
if (!a.created_at) return 1;
|
||||
if (!b.created_at) return -1;
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
||||
});
|
||||
setFilteredServers(sorted);
|
||||
}, [serversWithHealth]);
|
||||
const filterServers = useCallback(
|
||||
(teamId: string, group: string) => {
|
||||
if (!serversWithHealth) return setFilteredServers([]);
|
||||
let filtered = serversWithHealth;
|
||||
if (teamId === "personal") {
|
||||
setFilteredServers([]);
|
||||
return;
|
||||
}
|
||||
if (teamId !== "all") {
|
||||
filtered = filtered.filter((server) => server.teams?.some((team) => team.team_id === teamId));
|
||||
}
|
||||
if (group !== "all") {
|
||||
filtered = filtered.filter((server) =>
|
||||
server.mcp_access_groups?.some((g: any) => (typeof g === "string" ? g === group : g && g.name === group)),
|
||||
);
|
||||
}
|
||||
const sorted = [...filtered].sort((a, b) => {
|
||||
if (!a.created_at && !b.created_at) return 0;
|
||||
if (!a.created_at) return 1;
|
||||
if (!b.created_at) return -1;
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
||||
});
|
||||
setFilteredServers(sorted);
|
||||
},
|
||||
[serversWithHealth],
|
||||
);
|
||||
|
||||
// Handle team filter change
|
||||
const handleTeamChange = (teamId: string) => {
|
||||
|
|
@ -275,12 +274,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
const alias = (s.alias || "").toLowerCase();
|
||||
const url = (s.url || "").toLowerCase();
|
||||
const id = s.server_id.toLowerCase();
|
||||
return (
|
||||
name.includes(q) ||
|
||||
alias.includes(q) ||
|
||||
url.includes(q) ||
|
||||
id.includes(q)
|
||||
);
|
||||
return name.includes(q) || alias.includes(q) || url.includes(q) || id.includes(q);
|
||||
})
|
||||
: filteredServers;
|
||||
return [...matches].sort((a, b) => compareServers(a, b, sortKey));
|
||||
|
|
@ -335,18 +329,20 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
|
||||
// Memoize the selected server to prevent unnecessary re-renders
|
||||
const selectedServer = React.useMemo(() => {
|
||||
return filteredServers.find((server: MCPServer) => server.server_id === selectedServerId) || {
|
||||
server_id: "",
|
||||
server_name: "",
|
||||
alias: "",
|
||||
url: "",
|
||||
transport: "",
|
||||
auth_type: "",
|
||||
created_at: "",
|
||||
created_by: "",
|
||||
updated_at: "",
|
||||
updated_by: "",
|
||||
};
|
||||
return (
|
||||
filteredServers.find((server: MCPServer) => server.server_id === selectedServerId) || {
|
||||
server_id: "",
|
||||
server_name: "",
|
||||
alias: "",
|
||||
url: "",
|
||||
transport: "",
|
||||
auth_type: "",
|
||||
created_at: "",
|
||||
created_by: "",
|
||||
updated_at: "",
|
||||
updated_by: "",
|
||||
}
|
||||
);
|
||||
}, [filteredServers, selectedServerId]);
|
||||
|
||||
// Memoize the onBack callback to prevent unnecessary re-renders
|
||||
|
|
@ -375,14 +371,18 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
confirmLoading={isDeletingServer}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<AntdText className="text-gray-600">This action is permanent and cannot be undone. All associated configurations will be removed.</AntdText>
|
||||
<AntdText className="text-gray-600">
|
||||
This action is permanent and cannot be undone. All associated configurations will be removed.
|
||||
</AntdText>
|
||||
|
||||
{serverToDelete && (
|
||||
<div className="mt-3 p-4 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<Descriptions column={1} size="small" colon={false}>
|
||||
{serverToDelete.server_name && (
|
||||
<Descriptions.Item label={<span className="text-gray-500 text-sm">Name</span>}>
|
||||
<AntdText strong className="text-sm">{serverToDelete.server_name}</AntdText>
|
||||
<AntdText strong className="text-sm">
|
||||
{serverToDelete.server_name}
|
||||
</AntdText>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
<Descriptions.Item label={<span className="text-gray-500 text-sm">ID</span>}>
|
||||
|
|
@ -472,7 +472,13 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
<Tab>Connect</Tab>
|
||||
<Tab>Semantic Filter</Tab>
|
||||
<Tab>Network Settings</Tab>
|
||||
{isAdminRole(userRole) && <Tab><span className="flex items-center gap-2">Submitted MCPs <NewBadge /></span></Tab>}
|
||||
{isAdminRole(userRole) && (
|
||||
<Tab>
|
||||
<span className="flex items-center gap-2">
|
||||
Submitted MCPs <NewBadge />
|
||||
</span>
|
||||
</Tab>
|
||||
)}
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
|
|
@ -498,7 +504,9 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
<Text className="text-sm font-medium text-gray-600 whitespace-nowrap">Team</Text>
|
||||
<Select value={selectedTeam} onChange={handleTeamChange} style={{ width: 220 }} size="middle">
|
||||
<Option value="all">
|
||||
<span className="font-medium">{isInternalUser ? "All Available Servers" : "All Servers"}</span>
|
||||
<span className="font-medium">
|
||||
{isInternalUser ? "All Available Servers" : "All Servers"}
|
||||
</span>
|
||||
</Option>
|
||||
<Option value="personal">
|
||||
<span className="font-medium">Personal</span>
|
||||
|
|
@ -518,7 +526,12 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
<QuestionCircleOutlined style={{ marginLeft: 4, color: "#9ca3af" }} />
|
||||
</Tooltip>
|
||||
</Text>
|
||||
<Select value={selectedMcpAccessGroup} onChange={handleMcpAccessGroupChange} style={{ width: 220 }} size="middle">
|
||||
<Select
|
||||
value={selectedMcpAccessGroup}
|
||||
onChange={handleMcpAccessGroupChange}
|
||||
style={{ width: 220 }}
|
||||
size="middle"
|
||||
>
|
||||
<Option value="all">
|
||||
<span className="font-medium">All Access Groups</span>
|
||||
</Option>
|
||||
|
|
@ -542,9 +555,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
style={{ maxWidth: 320 }}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<Text className="whitespace-nowrap text-sm font-medium text-gray-600">
|
||||
Sort
|
||||
</Text>
|
||||
<Text className="whitespace-nowrap text-sm font-medium text-gray-600">Sort</Text>
|
||||
<Select
|
||||
value={sortKey}
|
||||
onChange={(v: SortKey) => setSortKey(v)}
|
||||
|
|
@ -594,19 +605,11 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
|
|||
setEditServer(true);
|
||||
}}
|
||||
onRecheckHealth={
|
||||
recheckServerHealth
|
||||
? () => recheckServerHealth(server.server_id)
|
||||
: undefined
|
||||
}
|
||||
onByokConnect={
|
||||
server.is_byok ? () => setByokModalServer(server) : undefined
|
||||
recheckServerHealth ? () => recheckServerHealth(server.server_id) : undefined
|
||||
}
|
||||
onByokConnect={server.is_byok ? () => setByokModalServer(server) : undefined}
|
||||
onOpenFillFields={() => setEnvVarsModalServer(server)}
|
||||
onDelete={
|
||||
isAdminRole(userRole)
|
||||
? () => handleDelete(server.server_id)
|
||||
: undefined
|
||||
}
|
||||
onDelete={isAdminRole(userRole) ? () => handleDelete(server.server_id) : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,10 +81,9 @@ import type { MCPUserEnvVarsStatus } from "./mcp_tools/types";
|
|||
const isLocal = process.env.NODE_ENV === "development";
|
||||
// In dev, if NEXT_PUBLIC_USE_REWRITES=true the Next.js dev server proxies API calls
|
||||
// to the backend — use relative URLs (null) so rewrites can intercept them.
|
||||
const defaultProxyBaseUrl =
|
||||
process.env.NEXT_PUBLIC_BASE_URL
|
||||
? process.env.NEXT_PUBLIC_BASE_URL
|
||||
: isLocal && process.env.NEXT_PUBLIC_USE_REWRITES !== "true"
|
||||
const defaultProxyBaseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
||||
? process.env.NEXT_PUBLIC_BASE_URL
|
||||
: isLocal && process.env.NEXT_PUBLIC_USE_REWRITES !== "true"
|
||||
? "http://localhost:4000"
|
||||
: null;
|
||||
const defaultServerRootPath = "/";
|
||||
|
|
@ -92,22 +91,23 @@ export let serverRootPath = defaultServerRootPath;
|
|||
const WORKER_URL_KEY = "litellm_worker_url";
|
||||
// If a worker URL is in localStorage, use it as the initial proxyBaseUrl.
|
||||
// This survives page navigation and the sessionStorage.clear() in user_dashboard.
|
||||
const _rawWorkerUrl =
|
||||
typeof window !== "undefined" ? window.localStorage.getItem(WORKER_URL_KEY) : null;
|
||||
const _rawWorkerUrl = typeof window !== "undefined" ? window.localStorage.getItem(WORKER_URL_KEY) : null;
|
||||
// Validate stored worker URL — reject non-HTTP schemes to prevent exfiltration
|
||||
const _initialWorkerUrl = (() => {
|
||||
if (!_rawWorkerUrl) return null;
|
||||
try {
|
||||
const parsed = new URL(_rawWorkerUrl);
|
||||
if (parsed.protocol === "http:" || parsed.protocol === "https:") return _rawWorkerUrl;
|
||||
} catch { /* invalid URL */ }
|
||||
} catch {
|
||||
/* invalid URL */
|
||||
}
|
||||
// Invalid URL in storage — clear it
|
||||
if (typeof window !== "undefined") window.localStorage.removeItem(WORKER_URL_KEY);
|
||||
return null;
|
||||
})();
|
||||
export let proxyBaseUrl: string | null = _initialWorkerUrl ?? defaultProxyBaseUrl;
|
||||
if (isLocal != true) {
|
||||
console.log = function () { };
|
||||
console.log = function () {};
|
||||
}
|
||||
|
||||
const getWindowLocation = () => {
|
||||
|
|
@ -129,8 +129,8 @@ const updateProxyBaseUrl = (serverRootPath: string, receivedProxyBaseUrl: string
|
|||
const resolvedDefaultProxyBaseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
||||
? process.env.NEXT_PUBLIC_BASE_URL
|
||||
: isLocal && process.env.NEXT_PUBLIC_USE_REWRITES !== "true"
|
||||
? "http://localhost:4000"
|
||||
: browserLocation?.origin ?? null;
|
||||
? "http://localhost:4000"
|
||||
: browserLocation?.origin ?? null;
|
||||
let initialProxyBaseUrl = receivedProxyBaseUrl || resolvedDefaultProxyBaseUrl;
|
||||
console.log("proxyBaseUrl:", proxyBaseUrl);
|
||||
console.log("serverRootPath:", serverRootPath);
|
||||
|
|
@ -542,9 +542,7 @@ export const cancelModelCostMapReload = async (accessToken: string) => {
|
|||
|
||||
export const getModelCostMapSource = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/model/cost_map/source`
|
||||
: `/model/cost_map/source`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/model/cost_map/source` : `/model/cost_map/source`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
|
@ -1296,10 +1294,7 @@ export interface UserInfoV2Response {
|
|||
* @param accessToken - Bearer token for auth
|
||||
* @param userId - Optional user ID to look up. If omitted, returns the caller's own info.
|
||||
*/
|
||||
export const userGetInfoV2 = async (
|
||||
accessToken: string,
|
||||
userId?: string,
|
||||
): Promise<UserInfoV2Response> => {
|
||||
export const userGetInfoV2 = async (accessToken: string, userId?: string): Promise<UserInfoV2Response> => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/user/info` : `/v2/user/info`;
|
||||
if (userId) {
|
||||
|
|
@ -1892,7 +1887,13 @@ const fetchDailyActivity = async ({
|
|||
}
|
||||
};
|
||||
|
||||
export const userDailyActivityCall = async (accessToken: string, startTime: Date, endTime: Date, page: number = 1, userId: string | null = null) => {
|
||||
export const userDailyActivityCall = async (
|
||||
accessToken: string,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
page: number = 1,
|
||||
userId: string | null = null,
|
||||
) => {
|
||||
/**
|
||||
* Get daily user activity on proxy
|
||||
*/
|
||||
|
|
@ -3325,7 +3326,12 @@ export const keyAliasesCall = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const userDailyActivityAggregatedCall = async (accessToken: string, startTime: Date, endTime: Date, userId: string | null = null) => {
|
||||
export const userDailyActivityAggregatedCall = async (
|
||||
accessToken: string,
|
||||
startTime: Date,
|
||||
endTime: Date,
|
||||
userId: string | null = null,
|
||||
) => {
|
||||
/**
|
||||
* Get aggregated daily user activity (no pagination)
|
||||
*/
|
||||
|
|
@ -5064,7 +5070,7 @@ interface ListGuardrailSubmissionsResponse {
|
|||
|
||||
export const listGuardrailSubmissions = async (
|
||||
accessToken: string,
|
||||
params?: { status?: string; team_id?: string; team_guardrail?: boolean; search?: string }
|
||||
params?: { status?: string; team_id?: string; team_guardrail?: boolean; search?: string },
|
||||
): Promise<ListGuardrailSubmissionsResponse> => {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/submissions` : `/guardrails/submissions`;
|
||||
const searchParams = new URLSearchParams();
|
||||
|
|
@ -5091,7 +5097,7 @@ export const listGuardrailSubmissions = async (
|
|||
|
||||
export const approveGuardrailSubmission = async (
|
||||
accessToken: string,
|
||||
guardrailId: string
|
||||
guardrailId: string,
|
||||
): Promise<{ guardrail_id: string; status: string; message: string }> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/guardrails/submissions/${encodeURIComponent(guardrailId)}/approve`
|
||||
|
|
@ -5114,7 +5120,7 @@ export const approveGuardrailSubmission = async (
|
|||
|
||||
export const rejectGuardrailSubmission = async (
|
||||
accessToken: string,
|
||||
guardrailId: string
|
||||
guardrailId: string,
|
||||
): Promise<{ guardrail_id: string; status: string; message: string }> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/guardrails/submissions/${encodeURIComponent(guardrailId)}/reject`
|
||||
|
|
@ -5136,11 +5142,7 @@ export const rejectGuardrailSubmission = async (
|
|||
};
|
||||
|
||||
// Guardrails / Policies usage (dashboard)
|
||||
export const getGuardrailsUsageOverview = async (
|
||||
accessToken: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
) => {
|
||||
export const getGuardrailsUsageOverview = async (accessToken: string, startDate?: string, endDate?: string) => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/usage/overview` : `/guardrails/usage/overview`;
|
||||
const params = new URLSearchParams();
|
||||
|
|
@ -5169,10 +5171,12 @@ export const getGuardrailsUsageDetail = async (
|
|||
accessToken: string,
|
||||
guardrailId: string,
|
||||
startDate?: string,
|
||||
endDate?: string
|
||||
endDate?: string,
|
||||
) => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/usage/detail/${encodeURIComponent(guardrailId)}` : `/guardrails/usage/detail/${encodeURIComponent(guardrailId)}`;
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/guardrails/usage/detail/${encodeURIComponent(guardrailId)}`
|
||||
: `/guardrails/usage/detail/${encodeURIComponent(guardrailId)}`;
|
||||
const params = new URLSearchParams();
|
||||
if (startDate) params.append("start_date", startDate);
|
||||
if (endDate) params.append("end_date", endDate);
|
||||
|
|
@ -5197,7 +5201,15 @@ export const getGuardrailsUsageDetail = async (
|
|||
|
||||
export const getGuardrailsUsageLogs = async (
|
||||
accessToken: string,
|
||||
options: { guardrailId?: string; policyId?: string; page?: number; pageSize?: number; action?: string; startDate?: string; endDate?: string }
|
||||
options: {
|
||||
guardrailId?: string;
|
||||
policyId?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
action?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/usage/logs` : `/guardrails/usage/logs`;
|
||||
|
|
@ -5299,7 +5311,7 @@ interface TestPoliciesAndGuardrailsResponse {
|
|||
export const testPoliciesAndGuardrails = async (
|
||||
accessToken: string,
|
||||
body: TestPoliciesAndGuardrailsRequest,
|
||||
signal?: AbortSignal
|
||||
signal?: AbortSignal,
|
||||
): Promise<TestPoliciesAndGuardrailsResponse> => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
|
|
@ -5328,7 +5340,8 @@ export const testPoliciesAndGuardrails = async (
|
|||
let errorMessage = "Failed to test policies and guardrails";
|
||||
try {
|
||||
const errorJson = JSON.parse(errorData);
|
||||
if (errorJson.detail) errorMessage = typeof errorJson.detail === "string" ? errorJson.detail : JSON.stringify(errorJson.detail);
|
||||
if (errorJson.detail)
|
||||
errorMessage = typeof errorJson.detail === "string" ? errorJson.detail : JSON.stringify(errorJson.detail);
|
||||
else if (errorJson.message) errorMessage = errorJson.message;
|
||||
} catch {
|
||||
errorMessage = errorData || errorMessage;
|
||||
|
|
@ -5401,12 +5414,10 @@ export const enrichPolicyTemplate = async (
|
|||
templateId: string,
|
||||
parameters: Record<string, string>,
|
||||
model?: string,
|
||||
competitors?: string[]
|
||||
competitors?: string[],
|
||||
) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policy/templates/enrich`
|
||||
: `/policy/templates/enrich`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates/enrich` : `/policy/templates/enrich`;
|
||||
const body: any = { template_id: templateId, parameters };
|
||||
if (model) body.model = model;
|
||||
if (competitors) body.competitors = competitors;
|
||||
|
|
@ -5438,12 +5449,10 @@ export const suggestPolicyTemplates = async (
|
|||
accessToken: string,
|
||||
attackExamples: string[],
|
||||
description: string,
|
||||
model: string
|
||||
model: string,
|
||||
) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policy/templates/suggest`
|
||||
: `/policy/templates/suggest`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates/suggest` : `/policy/templates/suggest`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -5471,15 +5480,9 @@ export const suggestPolicyTemplates = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const testPolicyTemplate = async (
|
||||
accessToken: string,
|
||||
guardrailDefinitions: any[],
|
||||
text: string
|
||||
) => {
|
||||
export const testPolicyTemplate = async (accessToken: string, guardrailDefinitions: any[], text: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policy/templates/test`
|
||||
: `/policy/templates/test`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates/test` : `/policy/templates/test`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -5519,11 +5522,9 @@ export const enrichPolicyTemplateStream = async (
|
|||
}) => void,
|
||||
onError?: (error: string) => void,
|
||||
options?: { instruction?: string; existingCompetitors?: string[] },
|
||||
onStatus?: (message: string) => void
|
||||
onStatus?: (message: string) => void,
|
||||
) => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policy/templates/enrich/stream`
|
||||
: `/policy/templates/enrich/stream`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates/enrich/stream` : `/policy/templates/enrich/stream`;
|
||||
const body: any = { template_id: templateId, parameters, model };
|
||||
if (options?.instruction) body.instruction = options.instruction;
|
||||
if (options?.existingCompetitors) body.competitors = options.existingCompetitors;
|
||||
|
|
@ -5598,9 +5599,7 @@ export const usageAiChatStream = async (
|
|||
onToolCall?: (event: UsageAiToolCallEvent) => void,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/usage/ai/chat`
|
||||
: `/usage/ai/chat`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/usage/ai/chat` : `/usage/ai/chat`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
|
|
@ -5712,7 +5711,7 @@ export const updatePolicyCall = async (accessToken: string, policyId: string, po
|
|||
|
||||
export const listPolicyVersions = async (
|
||||
accessToken: string,
|
||||
policyName: string
|
||||
policyName: string,
|
||||
): Promise<{ policy_name: string; versions: any[]; total_count: number }> => {
|
||||
try {
|
||||
const encodedName = encodeURIComponent(policyName);
|
||||
|
|
@ -5744,7 +5743,7 @@ export const listPolicyVersions = async (
|
|||
export const createPolicyVersion = async (
|
||||
accessToken: string,
|
||||
policyName: string,
|
||||
sourcePolicyId?: string | null
|
||||
sourcePolicyId?: string | null,
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const encodedName = encodeURIComponent(policyName);
|
||||
|
|
@ -5777,12 +5776,10 @@ export const createPolicyVersion = async (
|
|||
export const updatePolicyVersionStatus = async (
|
||||
accessToken: string,
|
||||
policyId: string,
|
||||
versionStatus: "published" | "production"
|
||||
versionStatus: "published" | "production",
|
||||
): Promise<any> => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policies/${policyId}/status`
|
||||
: `/policies/${policyId}/status`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/${policyId}/status` : `/policies/${policyId}/status`;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
|
|
@ -5944,7 +5941,7 @@ export const deletePolicyAttachmentCall = async (accessToken: string, attachment
|
|||
export const testPipelineCall = async (
|
||||
accessToken: string,
|
||||
pipeline: any,
|
||||
testMessages: Array<{role: string, content: string}>
|
||||
testMessages: Array<{ role: string; content: string }>,
|
||||
) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/test-pipeline` : `/policies/test-pipeline`;
|
||||
|
|
@ -6002,12 +5999,10 @@ export const getResolvedGuardrails = async (accessToken: string, policyId: strin
|
|||
|
||||
export const resolvePoliciesCall = async (
|
||||
accessToken: string,
|
||||
context: { team_alias?: string; key_alias?: string; model?: string; tags?: string[] }
|
||||
context: { team_alias?: string; key_alias?: string; model?: string; tags?: string[] },
|
||||
) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policies/resolve`
|
||||
: `/policies/resolve`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/resolve` : `/policies/resolve`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -6031,10 +6026,7 @@ export const resolvePoliciesCall = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const estimateAttachmentImpactCall = async (
|
||||
accessToken: string,
|
||||
attachmentData: any
|
||||
) => {
|
||||
export const estimateAttachmentImpactCall = async (accessToken: string, attachmentData: any) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policies/attachments/estimate-impact`
|
||||
|
|
@ -6062,10 +6054,7 @@ export const estimateAttachmentImpactCall = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const getPromptsList = async (
|
||||
accessToken: string,
|
||||
environment?: string,
|
||||
): Promise<ListPromptsResponse> => {
|
||||
export const getPromptsList = async (accessToken: string, environment?: string): Promise<ListPromptsResponse> => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/prompts/list` : `/prompts/list`;
|
||||
if (environment) {
|
||||
|
|
@ -6094,7 +6083,11 @@ export const getPromptsList = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const getPromptInfo = async (accessToken: string, promptId: string, environment?: string): Promise<PromptInfoResponse> => {
|
||||
export const getPromptInfo = async (
|
||||
accessToken: string,
|
||||
promptId: string,
|
||||
environment?: string,
|
||||
): Promise<PromptInfoResponse> => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/prompts/${promptId}/info` : `/prompts/${promptId}/info`;
|
||||
if (environment) {
|
||||
|
|
@ -6123,7 +6116,11 @@ export const getPromptInfo = async (accessToken: string, promptId: string, envir
|
|||
}
|
||||
};
|
||||
|
||||
export const getPromptVersions = async (accessToken: string, promptId: string, environment?: string): Promise<ListPromptsResponse> => {
|
||||
export const getPromptVersions = async (
|
||||
accessToken: string,
|
||||
promptId: string,
|
||||
environment?: string,
|
||||
): Promise<ListPromptsResponse> => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/prompts/${promptId}/versions` : `/prompts/${promptId}/versions`;
|
||||
if (environment) {
|
||||
|
|
@ -6501,9 +6498,7 @@ export const updateInternalUserSettings = async (accessToken: string, settings:
|
|||
|
||||
export const fetchOpenAPIRegistry = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/openapi-registry`
|
||||
: `/v1/mcp/openapi-registry`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/openapi-registry` : `/v1/mcp/openapi-registry`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: HTTP_REQUEST.GET,
|
||||
|
|
@ -6527,9 +6522,7 @@ export const fetchOpenAPIRegistry = async (accessToken: string) => {
|
|||
|
||||
export const fetchDiscoverableMCPServers = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/discover`
|
||||
: `/v1/mcp/discover`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/discover` : `/v1/mcp/discover`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: HTTP_REQUEST.GET,
|
||||
|
|
@ -6660,9 +6653,7 @@ export const fetchMCPAccessGroups = async (accessToken: string) => {
|
|||
|
||||
export const fetchMCPClientIp = async (accessToken: string): Promise<string | null> => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/network/client-ip`
|
||||
: `/v1/mcp/network/client-ip`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/network/client-ip` : `/v1/mcp/network/client-ip`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: HTTP_REQUEST.GET,
|
||||
|
|
@ -7141,11 +7132,7 @@ export const testSearchToolConnection = async (accessToken: string, litellmParam
|
|||
}
|
||||
};
|
||||
|
||||
export const listMCPTools = async (
|
||||
accessToken: string,
|
||||
serverId: string,
|
||||
customHeaders?: Record<string, string>,
|
||||
) => {
|
||||
export const listMCPTools = async (accessToken: string, serverId: string, customHeaders?: Record<string, string>) => {
|
||||
// Construct base URL
|
||||
let url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/mcp-rest/tools/list?server_id=${serverId}`
|
||||
|
|
@ -7199,8 +7186,7 @@ export const listMCPTools = async (
|
|||
// to inspect `result.error` / `result.message`. Attach `status` so
|
||||
// callers that need to react to auth failures (e.g. the useQuery in
|
||||
// mcp_tools.tsx) can still detect 401s from the returned object.
|
||||
const errorMessage =
|
||||
(data && (data.message || data.error)) || "Failed to fetch MCP tools";
|
||||
const errorMessage = (data && (data.message || data.error)) || "Failed to fetch MCP tools";
|
||||
return {
|
||||
tools: [],
|
||||
error: (data && data.error) || `http_${response.status}`,
|
||||
|
|
@ -8092,9 +8078,7 @@ export const getCategoryYaml = async (accessToken: string, categoryName: string)
|
|||
|
||||
export const getMajorAirlines = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/guardrails/ui/major_airlines`
|
||||
: `/guardrails/ui/major_airlines`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/ui/major_airlines` : `/guardrails/ui/major_airlines`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
|
|
@ -8106,10 +8090,7 @@ export const getMajorAirlines = async (accessToken: string) => {
|
|||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
console.error(
|
||||
`Failed to get major airlines. Status: ${response.status}, Error:`,
|
||||
errorData
|
||||
);
|
||||
console.error(`Failed to get major airlines. Status: ${response.status}, Error:`, errorData);
|
||||
handleError(errorData);
|
||||
throw new Error(`Failed to get major airlines: ${response.status} ${errorData}`);
|
||||
}
|
||||
|
|
@ -8641,9 +8622,7 @@ export interface LicenseInfo {
|
|||
};
|
||||
}
|
||||
|
||||
export const getLicenseInfo = async (
|
||||
accessToken: string,
|
||||
): Promise<LicenseInfo | null> => {
|
||||
export const getLicenseInfo = async (accessToken: string): Promise<LicenseInfo | null> => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/health/license` : `/health/license`;
|
||||
|
||||
|
|
@ -9333,7 +9312,8 @@ export const deriveErrorMessage = (errorData: any): string => {
|
|||
? detail
|
||||
: undefined;
|
||||
return (
|
||||
(errorData?.error && (errorData.error.message || (typeof errorData.error === "string" ? errorData.error : undefined))) ||
|
||||
(errorData?.error &&
|
||||
(errorData.error.message || (typeof errorData.error === "string" ? errorData.error : undefined))) ||
|
||||
errorData?.message ||
|
||||
detailStr ||
|
||||
JSON.stringify(errorData)
|
||||
|
|
@ -9382,9 +9362,7 @@ export const loginCall = async (username: string, password: string, useV3?: bool
|
|||
|
||||
// v3 returns an opaque code — exchange it for the real JWT
|
||||
if (useV3 && data.code) {
|
||||
const exchangeUrl = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v3/login/exchange`
|
||||
: "/v3/login/exchange";
|
||||
const exchangeUrl = proxyBaseUrl ? `${proxyBaseUrl}/v3/login/exchange` : "/v3/login/exchange";
|
||||
|
||||
const exchangeResponse = await fetch(exchangeUrl, {
|
||||
method: "POST",
|
||||
|
|
@ -9472,7 +9450,6 @@ export const updateUiSettings = async (accessToken: string, settings: Record<str
|
|||
return data;
|
||||
};
|
||||
|
||||
|
||||
// Claude Code Marketplace Networking Functions
|
||||
|
||||
/**
|
||||
|
|
@ -9752,11 +9729,9 @@ export interface ComplianceCheckRequest {
|
|||
|
||||
export const checkEuAiActCompliance = async (
|
||||
accessToken: string,
|
||||
payload: ComplianceCheckRequest
|
||||
payload: ComplianceCheckRequest,
|
||||
): Promise<ComplianceResponse> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/compliance/eu-ai-act`
|
||||
: `/compliance/eu-ai-act`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/compliance/eu-ai-act` : `/compliance/eu-ai-act`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -9774,11 +9749,9 @@ export const checkEuAiActCompliance = async (
|
|||
|
||||
export const checkGdprCompliance = async (
|
||||
accessToken: string,
|
||||
payload: ComplianceCheckRequest
|
||||
payload: ComplianceCheckRequest,
|
||||
): Promise<ComplianceResponse> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/compliance/gdpr`
|
||||
: `/compliance/gdpr`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/compliance/gdpr` : `/compliance/gdpr`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -9824,12 +9797,8 @@ export interface ToolPolicyOptionsResponse {
|
|||
output_policies: ToolPolicyOption[];
|
||||
}
|
||||
|
||||
export const fetchToolPolicyOptions = async (
|
||||
accessToken: string
|
||||
): Promise<ToolPolicyOptionsResponse> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/tool/policy/options`
|
||||
: `/v1/tool/policy/options`;
|
||||
export const fetchToolPolicyOptions = async (accessToken: string): Promise<ToolPolicyOptionsResponse> => {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/policy/options` : `/v1/tool/policy/options`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
|
@ -9896,12 +9865,10 @@ export interface ToolUsageLogsResponse {
|
|||
export const getToolUsageLogs = async (
|
||||
accessToken: string,
|
||||
toolName: string,
|
||||
options: { page?: number; pageSize?: number; startDate?: string; endDate?: string }
|
||||
options: { page?: number; pageSize?: number; startDate?: string; endDate?: string },
|
||||
): Promise<ToolUsageLogsResponse> => {
|
||||
const encoded = encodeURIComponent(toolName);
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/tool/${encoded}/logs`
|
||||
: `/v1/tool/${encoded}/logs`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/${encoded}/logs` : `/v1/tool/${encoded}/logs`;
|
||||
const params = new URLSearchParams();
|
||||
if (options.page != null) params.append("page", String(options.page));
|
||||
if (options.pageSize != null) params.append("page_size", String(options.pageSize));
|
||||
|
|
@ -9922,14 +9889,9 @@ export const getToolUsageLogs = async (
|
|||
return response.json();
|
||||
};
|
||||
|
||||
export const fetchToolDetail = async (
|
||||
accessToken: string,
|
||||
toolName: string
|
||||
): Promise<ToolDetailResponse> => {
|
||||
export const fetchToolDetail = async (accessToken: string, toolName: string): Promise<ToolDetailResponse> => {
|
||||
const encoded = encodeURIComponent(toolName);
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/tool/${encoded}/detail`
|
||||
: `/v1/tool/${encoded}/detail`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/${encoded}/detail` : `/v1/tool/${encoded}/detail`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
|
@ -9948,7 +9910,7 @@ export const updateToolPolicy = async (
|
|||
accessToken: string,
|
||||
toolName: string,
|
||||
policies: { input_policy?: string; output_policy?: string },
|
||||
options?: { team_id?: string | null; key_hash?: string | null; key_alias?: string | null }
|
||||
options?: { team_id?: string | null; key_hash?: string | null; key_alias?: string | null },
|
||||
): Promise<ToolRow> => {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/policy` : `/v1/tool/policy`;
|
||||
const body: Record<string, string | undefined | null> = {
|
||||
|
|
@ -9977,7 +9939,7 @@ export const updateToolPolicy = async (
|
|||
export const deleteToolPolicyOverride = async (
|
||||
accessToken: string,
|
||||
toolName: string,
|
||||
params: { team_id?: string | null; key_hash?: string | null }
|
||||
params: { team_id?: string | null; key_hash?: string | null },
|
||||
): Promise<{ deleted: boolean; tool_name: string }> => {
|
||||
const encoded = encodeURIComponent(toolName);
|
||||
const q = new URLSearchParams();
|
||||
|
|
@ -10040,14 +10002,17 @@ export const storeMCPOAuthUserCredential = async (
|
|||
const err = await response.json().catch(() => ({}));
|
||||
const errObj = err as { detail?: unknown };
|
||||
const detail = errObj?.detail;
|
||||
const detailMsg =
|
||||
Array.isArray(detail)
|
||||
? detail.map((d: unknown) => (d && typeof d === "object" ? (d as Record<string, unknown>).msg ?? JSON.stringify(d) : String(d))).join("; ")
|
||||
: typeof detail === "string"
|
||||
? detail
|
||||
: detail && typeof (detail as Record<string, unknown>).error === "string"
|
||||
? (detail as Record<string, unknown>).error as string
|
||||
: undefined;
|
||||
const detailMsg = Array.isArray(detail)
|
||||
? detail
|
||||
.map((d: unknown) =>
|
||||
d && typeof d === "object" ? (d as Record<string, unknown>).msg ?? JSON.stringify(d) : String(d),
|
||||
)
|
||||
.join("; ")
|
||||
: typeof detail === "string"
|
||||
? detail
|
||||
: detail && typeof (detail as Record<string, unknown>).error === "string"
|
||||
? ((detail as Record<string, unknown>).error as string)
|
||||
: undefined;
|
||||
throw new Error(detailMsg || "Failed to store OAuth credential");
|
||||
}
|
||||
return response.json();
|
||||
|
|
@ -10068,14 +10033,17 @@ export const deleteMCPOAuthUserCredential = async (
|
|||
const err = await response.json().catch(() => ({}));
|
||||
const errObj = err as { detail?: unknown };
|
||||
const detail = errObj?.detail;
|
||||
const detailMsg =
|
||||
Array.isArray(detail)
|
||||
? detail.map((d: unknown) => (d && typeof d === "object" ? (d as Record<string, unknown>).msg ?? JSON.stringify(d) : String(d))).join("; ")
|
||||
: typeof detail === "string"
|
||||
? detail
|
||||
: detail && typeof (detail as Record<string, unknown>).error === "string"
|
||||
? (detail as Record<string, unknown>).error as string
|
||||
: undefined;
|
||||
const detailMsg = Array.isArray(detail)
|
||||
? detail
|
||||
.map((d: unknown) =>
|
||||
d && typeof d === "object" ? (d as Record<string, unknown>).msg ?? JSON.stringify(d) : String(d),
|
||||
)
|
||||
.join("; ")
|
||||
: typeof detail === "string"
|
||||
? detail
|
||||
: detail && typeof (detail as Record<string, unknown>).error === "string"
|
||||
? ((detail as Record<string, unknown>).error as string)
|
||||
: undefined;
|
||||
throw new Error(detailMsg || "Failed to revoke OAuth credential");
|
||||
}
|
||||
return response.json();
|
||||
|
|
@ -10098,12 +10066,8 @@ export const getMCPOAuthUserCredentialStatus = async (
|
|||
return response.json();
|
||||
};
|
||||
|
||||
export const listMCPUserCredentials = async (
|
||||
accessToken: string,
|
||||
): Promise<MCPUserCredentialListItem[]> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/user-credentials`
|
||||
: `/v1/mcp/user-credentials`;
|
||||
export const listMCPUserCredentials = async (accessToken: string): Promise<MCPUserCredentialListItem[]> => {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/user-credentials` : `/v1/mcp/user-credentials`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}` },
|
||||
|
|
@ -10116,10 +10080,7 @@ export const listMCPUserCredentials = async (
|
|||
// MCP per-user env vars (/v1/mcp/server/{id}/user-env-vars)
|
||||
// ============================================================
|
||||
|
||||
export const getMCPUserEnvVars = async (
|
||||
accessToken: string,
|
||||
serverId: string,
|
||||
): Promise<MCPUserEnvVarsStatus> => {
|
||||
export const getMCPUserEnvVars = async (accessToken: string, serverId: string): Promise<MCPUserEnvVarsStatus> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/server/${serverId}/user-env-vars`
|
||||
: `/v1/mcp/server/${serverId}/user-env-vars`;
|
||||
|
|
@ -10131,9 +10092,7 @@ export const getMCPUserEnvVars = async (
|
|||
const err = await response.json().catch(() => ({}));
|
||||
const detail = (err as { detail?: unknown })?.detail;
|
||||
const message =
|
||||
typeof detail === "string"
|
||||
? detail
|
||||
: (detail as { error?: string })?.error || "Failed to load env vars";
|
||||
typeof detail === "string" ? detail : (detail as { error?: string })?.error || "Failed to load env vars";
|
||||
throw new Error(message);
|
||||
}
|
||||
return response.json();
|
||||
|
|
@ -10159,18 +10118,13 @@ export const storeMCPUserEnvVars = async (
|
|||
const err = await response.json().catch(() => ({}));
|
||||
const detail = (err as { detail?: unknown })?.detail;
|
||||
const message =
|
||||
typeof detail === "string"
|
||||
? detail
|
||||
: (detail as { error?: string })?.error || "Failed to save env vars";
|
||||
typeof detail === "string" ? detail : (detail as { error?: string })?.error || "Failed to save env vars";
|
||||
throw new Error(message);
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const clearMCPUserEnvVars = async (
|
||||
accessToken: string,
|
||||
serverId: string,
|
||||
): Promise<MCPUserEnvVarsStatus> => {
|
||||
export const clearMCPUserEnvVars = async (accessToken: string, serverId: string): Promise<MCPUserEnvVarsStatus> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/server/${serverId}/user-env-vars`
|
||||
: `/v1/mcp/server/${serverId}/user-env-vars`;
|
||||
|
|
@ -10184,12 +10138,8 @@ export const clearMCPUserEnvVars = async (
|
|||
return response.json();
|
||||
};
|
||||
|
||||
export const listMCPUserEnvVarStatus = async (
|
||||
accessToken: string,
|
||||
): Promise<MCPUserEnvVarsStatus[]> => {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/mcp/user-env-vars/status`
|
||||
: `/v1/mcp/user-env-vars/status`;
|
||||
export const listMCPUserEnvVarStatus = async (accessToken: string): Promise<MCPUserEnvVarsStatus[]> => {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/user-env-vars/status` : `/v1/mcp/user-env-vars/status`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}` },
|
||||
|
|
@ -10215,8 +10165,7 @@ export const listMCPUserEnvVarStatus = async (
|
|||
* other potentially-unsafe character (spaces, `?`, `#`, `%`, etc.) per
|
||||
* path segment.
|
||||
*/
|
||||
const encodeMemoryKeyForPath = (key: string): string =>
|
||||
key.split("/").map(encodeURIComponent).join("/");
|
||||
const encodeMemoryKeyForPath = (key: string): string => key.split("/").map(encodeURIComponent).join("/");
|
||||
|
||||
export interface MemoryRow {
|
||||
memory_id: string;
|
||||
|
|
@ -10255,8 +10204,7 @@ export const fetchMemoryList = async (
|
|||
params.append("key", options.key);
|
||||
}
|
||||
if (options.page != null) params.append("page", String(options.page));
|
||||
if (options.pageSize != null)
|
||||
params.append("page_size", String(options.pageSize));
|
||||
if (options.pageSize != null) params.append("page_size", String(options.pageSize));
|
||||
const url = params.toString() ? `${base}?${params.toString()}` : base;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
|
|
@ -10303,9 +10251,7 @@ export const updateMemory = async (
|
|||
payload: { value?: string; metadata?: unknown },
|
||||
): Promise<MemoryRow> => {
|
||||
const encoded = encodeMemoryKeyForPath(key);
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/memory/${encoded}`
|
||||
: `/v1/memory/${encoded}`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory/${encoded}` : `/v1/memory/${encoded}`;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
|
|
@ -10321,14 +10267,9 @@ export const updateMemory = async (
|
|||
return response.json();
|
||||
};
|
||||
|
||||
export const deleteMemory = async (
|
||||
accessToken: string,
|
||||
key: string,
|
||||
): Promise<void> => {
|
||||
export const deleteMemory = async (accessToken: string, key: string): Promise<void> => {
|
||||
const encoded = encodeMemoryKeyForPath(key);
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/v1/memory/${encoded}`
|
||||
: `/v1/memory/${encoded}`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory/${encoded}` : `/v1/memory/${encoded}`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue