mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
feat(mcp): flag disabled overlong tools in UI and reject their calls
Per review of the exclusion contract: the admin UI tool listing now keeps tools whose prefixed name exceeds the provider limit visible, flagged disabled with the reason (grayed row and tooltip in the dashboard), while LLM-facing listings keep excluding them. Direct calls to a disabled tool are rejected at the shared call_tool seam with a clean 400 tool_name_too_long error on every surface (REST, MCP JSON-RPC, Responses API) instead of being forwarded upstream. The add-time preview warnings now render in the create-server form
This commit is contained in:
parent
c093858a67
commit
fed5479113
11 changed files with 218 additions and 15 deletions
|
|
@ -100,6 +100,7 @@ from litellm.proxy._experimental.mcp_server.utils import (
|
|||
split_server_prefix_from_name,
|
||||
split_tools_by_name_length,
|
||||
strip_known_server_prefix,
|
||||
tool_name_length_disabled_reason,
|
||||
validate_mcp_server_name,
|
||||
)
|
||||
from litellm.proxy._types import (
|
||||
|
|
@ -2355,6 +2356,7 @@ class MCPServerManager:
|
|||
raw_headers: Optional[dict[str, str]] = None,
|
||||
user_api_key_auth: Optional[UserAPIKeyAuth] = None,
|
||||
oauth2_headers: Optional[dict[str, str]] = None,
|
||||
drop_overlong_names: bool = True,
|
||||
) -> list[MCPTool]:
|
||||
"""
|
||||
Helper method to get tools from a single MCP server with prefixed names.
|
||||
|
|
@ -2362,6 +2364,10 @@ class MCPServerManager:
|
|||
Args:
|
||||
server (MCPServer): The server to query tools from
|
||||
mcp_auth_header: Optional auth header for MCP server
|
||||
drop_overlong_names: When True (every LLM-facing path), tools whose
|
||||
final listed name exceeds ``MCP_MAX_TOOL_NAME_LENGTH`` are
|
||||
excluded. The admin UI listing passes False so those tools stay
|
||||
visible and can be rendered as disabled.
|
||||
|
||||
Returns:
|
||||
List[MCPTool]: List of tools available on the server with prefixed names
|
||||
|
|
@ -2467,13 +2473,15 @@ class MCPServerManager:
|
|||
)
|
||||
for t in tools
|
||||
]
|
||||
return self._drop_tools_exceeding_name_length(tools, server)
|
||||
return self._drop_tools_exceeding_name_length(tools, server) if drop_overlong_names else tools
|
||||
else:
|
||||
tools = await self._fetch_tools_with_timeout(client, server.name)
|
||||
self._remember_upstream_initialize_instructions(server, client)
|
||||
|
||||
prefixed_or_original_tools = self._create_prefixed_tools(tools, server, add_prefix=add_prefix)
|
||||
|
||||
if not drop_overlong_names:
|
||||
return prefixed_or_original_tools
|
||||
return self._drop_tools_exceeding_name_length(prefixed_or_original_tools, server)
|
||||
|
||||
except MCPUpstreamAuthError:
|
||||
|
|
@ -3192,8 +3200,9 @@ class MCPServerManager:
|
|||
|
||||
Providers such as AWS Bedrock, OpenAI, and Gemini reject tool names longer
|
||||
than 64 characters, so listing them would make every downstream LLM request
|
||||
carrying the full tool list fail. The tools stay callable by name; they are
|
||||
only excluded from listings.
|
||||
carrying the full tool list fail. The admin UI listing keeps these tools
|
||||
visible (rendered as disabled) via ``drop_overlong_names=False``, and
|
||||
``call_tool`` rejects direct calls to them with the same reason.
|
||||
"""
|
||||
kept, dropped = split_tools_by_name_length(tools, MCP_MAX_TOOL_NAME_LENGTH)
|
||||
if dropped:
|
||||
|
|
@ -3205,7 +3214,8 @@ class MCPServerManager:
|
|||
)
|
||||
verbose_logger.warning(
|
||||
"MCP server %s has %d tool(s) whose name exceeds %d characters, which providers such as "
|
||||
"AWS Bedrock, OpenAI, and Gemini reject. Excluding them from tool listings: %s. "
|
||||
"AWS Bedrock, OpenAI, and Gemini reject. Disabling them: excluded from tool listings "
|
||||
"sent to LLMs and direct calls are rejected: %s. "
|
||||
"Use a shorter server alias, rename the tools on the MCP server, or set "
|
||||
"LITELLM_MCP_MAX_TOOL_NAME_LENGTH to change the limit.",
|
||||
server_label,
|
||||
|
|
@ -4030,6 +4040,18 @@ class MCPServerManager:
|
|||
start_time = datetime.datetime.now()
|
||||
mcp_server = self._resolve_mcp_server_for_tool_call(server_name, name)
|
||||
|
||||
# Callers may pass the name prefixed or unprefixed; normalize to the
|
||||
# canonical listed form before measuring against the provider limit.
|
||||
canonical_name = add_server_prefix_to_name(
|
||||
strip_known_server_prefix(name, mcp_server), get_server_prefix(mcp_server)
|
||||
)
|
||||
disabled_reason = tool_name_length_disabled_reason(canonical_name, MCP_MAX_TOOL_NAME_LENGTH)
|
||||
if disabled_reason is not None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={"error": "tool_name_too_long", "message": disabled_reason},
|
||||
)
|
||||
|
||||
# Resolved before any hook runs so a missing BYOK credential (401) never
|
||||
# leaves during-hook side effects (audit logging, rate-limit bookkeeping)
|
||||
# recorded against a call that ultimately fails.
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@ from litellm.proxy._experimental.mcp_server.ui_session_utils import (
|
|||
from litellm.constants import MCP_MAX_TOOL_NAME_LENGTH
|
||||
from litellm.proxy._experimental.mcp_server.utils import (
|
||||
MCPMissingUserEnvVarsError,
|
||||
add_server_prefix_to_name,
|
||||
get_server_prefix,
|
||||
is_short_mcp_tool_prefix_enabled,
|
||||
merge_mcp_headers,
|
||||
strip_known_server_prefix,
|
||||
tool_name_length_disabled_reason,
|
||||
tool_name_length_warnings,
|
||||
)
|
||||
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
|
||||
|
|
@ -262,14 +265,24 @@ if MCP_AVAILABLE:
|
|||
"server_id": server.server_id,
|
||||
"alias": server.alias,
|
||||
}
|
||||
server_prefix = get_server_prefix(server)
|
||||
disabled_reasons = [
|
||||
tool_name_length_disabled_reason(
|
||||
add_server_prefix_to_name(strip_known_server_prefix(tool.name, server), server_prefix),
|
||||
MCP_MAX_TOOL_NAME_LENGTH,
|
||||
)
|
||||
for tool in tools
|
||||
]
|
||||
return [
|
||||
ListMCPToolsRestAPIResponseObject(
|
||||
name=tool.name,
|
||||
description=tool.description,
|
||||
inputSchema=tool.inputSchema,
|
||||
mcp_info=enriched_mcp_info,
|
||||
disabled=reason is not None,
|
||||
disabled_reason=reason,
|
||||
)
|
||||
for tool in tools
|
||||
for tool, reason in zip(tools, disabled_reasons)
|
||||
]
|
||||
|
||||
def _extract_mcp_headers_from_request(
|
||||
|
|
@ -412,6 +425,7 @@ if MCP_AVAILABLE:
|
|||
add_prefix=False,
|
||||
raw_headers=raw_headers,
|
||||
user_api_key_auth=user_api_key_auth,
|
||||
drop_overlong_names=False,
|
||||
)
|
||||
|
||||
if not apply_tool_filters:
|
||||
|
|
|
|||
|
|
@ -359,9 +359,16 @@ if MCP_AVAILABLE:
|
|||
class ListMCPToolsRestAPIResponseObject(MCPTool):
|
||||
"""
|
||||
Object returned by the /tools/list REST API route.
|
||||
|
||||
``disabled`` is True when the tool's prefixed name exceeds the
|
||||
``MCP_MAX_TOOL_NAME_LENGTH`` provider limit: the tool is excluded from
|
||||
LLM-facing listings and direct calls are rejected, with the
|
||||
explanation in ``disabled_reason``.
|
||||
"""
|
||||
|
||||
mcp_info: Optional[MCPInfo] = None
|
||||
disabled: bool = False
|
||||
disabled_reason: Optional[str] = None
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
def _normalize_resource_contents(contents: list) -> List[ReadResourceContents]:
|
||||
|
|
|
|||
|
|
@ -283,11 +283,27 @@ def split_tools_by_name_length(tools: Sequence["MCPTool"], max_length: int) -> T
|
|||
return kept, dropped
|
||||
|
||||
|
||||
def tool_name_length_disabled_reason(prefixed_name: str, max_length: int) -> Optional[str]:
|
||||
"""The reason a tool is disabled by the name-length limit, or ``None`` when it fits.
|
||||
|
||||
A ``max_length`` of zero or less disables the check entirely.
|
||||
"""
|
||||
if max_length <= 0 or len(prefixed_name) <= max_length:
|
||||
return None
|
||||
return (
|
||||
f"Tool name '{prefixed_name}' is {len(prefixed_name)} characters, which exceeds the "
|
||||
f"{max_length} character tool name limit enforced by providers such as AWS Bedrock, OpenAI, and "
|
||||
f"Gemini. LiteLLM disables it: it is excluded from tool listings sent to LLMs and direct calls "
|
||||
f"are rejected. Use a shorter server alias, rename the tool on the MCP server, or set "
|
||||
f"LITELLM_MCP_MAX_TOOL_NAME_LENGTH to change the limit."
|
||||
)
|
||||
|
||||
|
||||
def tool_name_length_warnings(tool_names: Iterable[str], server_prefix: str, max_length: int) -> List[str]:
|
||||
"""Warnings for tools whose prefixed name would exceed ``max_length``.
|
||||
|
||||
Used at server add/preview time, before the server is persisted, to flag
|
||||
tool names that the runtime listing will exclude once the server prefix
|
||||
tool names that will be disabled once the server prefix
|
||||
(``<alias>-<tool>``) is applied.
|
||||
"""
|
||||
if max_length <= 0:
|
||||
|
|
@ -297,8 +313,8 @@ def tool_name_length_warnings(tool_names: Iterable[str], server_prefix: str, max
|
|||
(
|
||||
f"Tool '{name}' will be listed as '{prefixed}' ({len(prefixed)} characters), which exceeds the "
|
||||
f"{max_length} character tool name limit enforced by providers such as AWS Bedrock, OpenAI, and "
|
||||
f"Gemini. LiteLLM will exclude it from tool listings. Use a shorter server alias or rename the "
|
||||
f"tool on the MCP server."
|
||||
f"Gemini. LiteLLM will disable it: it will be excluded from tool listings sent to LLMs and "
|
||||
f"direct calls will be rejected. Use a shorter server alias or rename the tool on the MCP server."
|
||||
)
|
||||
for name, prefixed in prefixed_by_name
|
||||
if len(prefixed) > max_length
|
||||
|
|
|
|||
|
|
@ -6372,3 +6372,70 @@ class TestToolNameLengthExclusion:
|
|||
tools = await self._list_tools(self._server(alias), [raw_at_limit, raw_too_long], add_prefix=False)
|
||||
|
||||
assert [t.name for t in tools] == [raw_at_limit]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_listing_keeps_overlong_tools_when_drop_disabled(self):
|
||||
from litellm.constants import MCP_MAX_TOOL_NAME_LENGTH
|
||||
|
||||
alias = "network_config_audit"
|
||||
too_long = "t" * (MCP_MAX_TOOL_NAME_LENGTH - len(alias))
|
||||
|
||||
manager = MCPServerManager()
|
||||
manager._create_mcp_client = AsyncMock(return_value=object())
|
||||
manager._fetch_tools_with_timeout = AsyncMock(return_value=[MCPTool(name=too_long, inputSchema={})])
|
||||
tools = await manager._get_tools_from_server(
|
||||
server=self._server(alias), add_prefix=False, drop_overlong_names=False
|
||||
)
|
||||
|
||||
assert [t.name for t in tools] == [too_long]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_tool_rejects_overlong_tool_with_clean_error(self):
|
||||
from litellm.constants import MCP_MAX_TOOL_NAME_LENGTH
|
||||
|
||||
alias = "network_config_audit"
|
||||
too_long = "t" * (MCP_MAX_TOOL_NAME_LENGTH - len(alias))
|
||||
prefixed = f"{alias}-{too_long}"
|
||||
assert len(prefixed) == MCP_MAX_TOOL_NAME_LENGTH + 1
|
||||
|
||||
manager = MCPServerManager()
|
||||
server = self._server(alias)
|
||||
manager.registry = {server.server_id: server}
|
||||
manager.tool_name_to_mcp_server_name_mapping[too_long] = alias
|
||||
manager.tool_name_to_mcp_server_name_mapping[prefixed] = alias
|
||||
manager._create_mcp_client = AsyncMock(
|
||||
side_effect=AssertionError("must reject before connecting upstream")
|
||||
)
|
||||
|
||||
for call_name in (too_long, prefixed):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await manager.call_tool(server_name=alias, name=call_name, arguments={})
|
||||
assert exc_info.value.status_code == 400
|
||||
assert exc_info.value.detail["error"] == "tool_name_too_long"
|
||||
assert "exceeds the 64 character tool name limit" in exc_info.value.detail["message"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_tool_allows_tool_at_the_limit(self):
|
||||
from litellm.constants import MCP_MAX_TOOL_NAME_LENGTH
|
||||
|
||||
alias = "network_config_audit"
|
||||
fitting = "t" * (MCP_MAX_TOOL_NAME_LENGTH - len(alias) - 1)
|
||||
prefixed = f"{alias}-{fitting}"
|
||||
assert len(prefixed) == MCP_MAX_TOOL_NAME_LENGTH
|
||||
|
||||
manager = MCPServerManager()
|
||||
server = self._server(alias)
|
||||
manager.registry = {server.server_id: server}
|
||||
manager.tool_name_to_mcp_server_name_mapping[fitting] = alias
|
||||
manager.tool_name_to_mcp_server_name_mapping[prefixed] = alias
|
||||
|
||||
mock_client = AsyncMock()
|
||||
call_result = MagicMock(spec=CallToolResult)
|
||||
call_result.isError = False
|
||||
call_result.content = []
|
||||
mock_client.call_tool = AsyncMock(return_value=call_result)
|
||||
manager._create_mcp_client = AsyncMock(return_value=mock_client)
|
||||
|
||||
result = await manager.call_tool(server_name=alias, name=fitting, arguments={})
|
||||
|
||||
assert result.isError is False
|
||||
|
|
|
|||
|
|
@ -2496,3 +2496,35 @@ class TestToolResponseMcpInfoEnrichment:
|
|||
"server_id": "server-uuid",
|
||||
"alias": None,
|
||||
}
|
||||
|
||||
|
||||
class TestToolResponseDisabledAnnotation:
|
||||
"""LIT-4216: the UI listing keeps over-limit tools visible but flags them disabled."""
|
||||
|
||||
def test_marks_overlong_tool_disabled_with_reason(self):
|
||||
from mcp.types import Tool as MCPTool
|
||||
|
||||
from litellm.constants import MCP_MAX_TOOL_NAME_LENGTH
|
||||
from litellm.types.mcp_server.mcp_server_manager import MCPServer
|
||||
from litellm.types.mcp import MCPTransport
|
||||
|
||||
alias = "network_config_audit"
|
||||
fitting = "t" * (MCP_MAX_TOOL_NAME_LENGTH - len(alias) - 1)
|
||||
too_long = "t" * (MCP_MAX_TOOL_NAME_LENGTH - len(alias))
|
||||
server = MCPServer(
|
||||
server_id="len-limit",
|
||||
name="len-limit-server",
|
||||
alias=alias,
|
||||
url="https://up.example.com",
|
||||
transport=MCPTransport.http,
|
||||
)
|
||||
|
||||
objects = rest_endpoints._create_tool_response_objects(
|
||||
[MCPTool(name=fitting, inputSchema={}), MCPTool(name=too_long, inputSchema={})],
|
||||
server,
|
||||
)
|
||||
|
||||
assert [o.disabled for o in objects] == [False, True]
|
||||
assert objects[0].disabled_reason is None
|
||||
assert f"{alias}-{too_long}" in objects[1].disabled_reason
|
||||
assert "direct calls" in objects[1].disabled_reason
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
// Single hook call shared by MCPConnectionStatus and MCPToolConfiguration to avoid duplicate requests.
|
||||
const {
|
||||
tools,
|
||||
toolsWarnings,
|
||||
isLoadingTools,
|
||||
toolsError,
|
||||
toolsErrorStatus,
|
||||
|
|
@ -1169,6 +1170,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
|
|||
<MCPConnectionStatus
|
||||
formValues={formValues}
|
||||
tools={tools}
|
||||
toolsWarnings={toolsWarnings}
|
||||
isLoadingTools={isLoadingTools}
|
||||
toolsError={toolsError}
|
||||
toolsErrorStatus={toolsErrorStatus}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Card, Title, Text } from "@tremor/react";
|
|||
interface MCPConnectionStatusProps {
|
||||
formValues: Record<string, any>;
|
||||
tools: any[];
|
||||
toolsWarnings?: string[];
|
||||
isLoadingTools: boolean;
|
||||
toolsError: string | null;
|
||||
toolsErrorStatus?: number | null;
|
||||
|
|
@ -17,6 +18,7 @@ interface MCPConnectionStatusProps {
|
|||
const MCPConnectionStatus: React.FC<MCPConnectionStatusProps> = ({
|
||||
formValues,
|
||||
tools,
|
||||
toolsWarnings = [],
|
||||
isLoadingTools,
|
||||
toolsError,
|
||||
toolsErrorStatus = null,
|
||||
|
|
@ -95,6 +97,21 @@ const MCPConnectionStatus: React.FC<MCPConnectionStatusProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{toolsWarnings.length > 0 && !toolsError && (
|
||||
<Alert
|
||||
message="Some tool names exceed the provider limit"
|
||||
description={
|
||||
<ul style={{ margin: 0, paddingLeft: "16px" }}>
|
||||
{toolsWarnings.map((warning) => (
|
||||
<li key={warning}>{warning}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
|
||||
{toolsError && isPreviewForbidden && (
|
||||
<Alert message="Tool preview unavailable" description={toolsError} type="info" showIcon />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { setSecureItem } from "@/utils/secureStorage";
|
|||
|
||||
import { Card, Title, Text } from "@tremor/react";
|
||||
import { RobotOutlined, ToolOutlined, SearchOutlined, KeyOutlined, LockOutlined } from "@ant-design/icons";
|
||||
import { Input, Button as AntdButton } from "antd";
|
||||
import { Input, Button as AntdButton, Tooltip } from "antd";
|
||||
|
||||
const MCPToolsViewer = ({
|
||||
serverId,
|
||||
|
|
@ -479,9 +479,11 @@ const MCPToolsViewer = ({
|
|||
<div
|
||||
key={tool.name}
|
||||
className={`border rounded-lg p-3 cursor-pointer transition-all hover:shadow-xs ${
|
||||
selectedTool?.name === tool.name
|
||||
? "border-blue-500 bg-blue-50 ring-1 ring-blue-200"
|
||||
: "border-gray-200 bg-white hover:border-gray-300"
|
||||
tool.disabled
|
||||
? "border-gray-200 bg-gray-50 opacity-60"
|
||||
: selectedTool?.name === tool.name
|
||||
? "border-blue-500 bg-blue-50 ring-1 ring-blue-200"
|
||||
: "border-gray-200 bg-white hover:border-gray-300"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedTool(tool);
|
||||
|
|
@ -498,9 +500,22 @@ const MCPToolsViewer = ({
|
|||
/>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-mono text-xs font-medium text-gray-900 truncate">
|
||||
{tool.name}
|
||||
</h4>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<h4
|
||||
className={`font-mono text-xs font-medium truncate ${
|
||||
tool.disabled ? "text-gray-500" : "text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{tool.name}
|
||||
</h4>
|
||||
{tool.disabled && (
|
||||
<Tooltip title={tool.disabled_reason}>
|
||||
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 shrink-0">
|
||||
Disabled
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 truncate">{tool.mcp_info.server_name}</p>
|
||||
<p className="text-xs text-gray-600 mt-1 line-clamp-2 leading-relaxed">
|
||||
{tool.description}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ export interface MCPTool {
|
|||
description?: string;
|
||||
inputSchema: InputSchema | string; // API returns string "tool_input_schema" or the actual schema
|
||||
mcp_info: MCPInfo;
|
||||
// True when the tool's prefixed name exceeds the provider tool-name length
|
||||
// limit; the tool is excluded from LLM-facing listings and calls are rejected
|
||||
disabled?: boolean;
|
||||
disabled_reason?: string | null;
|
||||
// Function to select a tool (added in the component)
|
||||
onToolSelect?: (tool: MCPTool) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ interface UseTestMCPConnectionProps {
|
|||
|
||||
interface UseTestMCPConnectionReturn {
|
||||
tools: any[];
|
||||
toolsWarnings: string[];
|
||||
isLoadingTools: boolean;
|
||||
toolsError: string | null;
|
||||
toolsErrorStatus: number | null;
|
||||
|
|
@ -48,6 +49,7 @@ export const useTestMCPConnection = ({
|
|||
enabled = true,
|
||||
}: UseTestMCPConnectionProps): UseTestMCPConnectionReturn => {
|
||||
const [tools, setTools] = useState<any[]>([]);
|
||||
const [toolsWarnings, setToolsWarnings] = useState<string[]>([]);
|
||||
const [isLoadingTools, setIsLoadingTools] = useState(false);
|
||||
const [toolsError, setToolsError] = useState<string | null>(null);
|
||||
const [toolsErrorStatus, setToolsErrorStatus] = useState<number | null>(null);
|
||||
|
|
@ -157,6 +159,7 @@ export const useTestMCPConnection = ({
|
|||
|
||||
if (toolsResponse.tools && !toolsResponse.error) {
|
||||
setTools(toolsResponse.tools);
|
||||
setToolsWarnings(Array.isArray(toolsResponse.warnings) ? toolsResponse.warnings : []);
|
||||
setToolsError(null);
|
||||
setToolsErrorStatus(null);
|
||||
setToolsErrorStackTrace(null);
|
||||
|
|
@ -169,6 +172,7 @@ export const useTestMCPConnection = ({
|
|||
setToolsErrorStatus(typeof toolsResponse.status === "number" ? toolsResponse.status : null);
|
||||
setToolsErrorStackTrace(toolsResponse.status === 403 ? null : toolsResponse.stack_trace || null);
|
||||
setTools([]);
|
||||
setToolsWarnings([]);
|
||||
setHasShownSuccessMessage(false);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -177,6 +181,7 @@ export const useTestMCPConnection = ({
|
|||
setToolsErrorStatus(null);
|
||||
setToolsErrorStackTrace(null);
|
||||
setTools([]);
|
||||
setToolsWarnings([]);
|
||||
setHasShownSuccessMessage(false);
|
||||
} finally {
|
||||
setIsLoadingTools(false);
|
||||
|
|
@ -185,6 +190,7 @@ export const useTestMCPConnection = ({
|
|||
|
||||
const clearTools = useCallback(() => {
|
||||
setTools([]);
|
||||
setToolsWarnings([]);
|
||||
setToolsError(null);
|
||||
setToolsErrorStatus(null);
|
||||
setToolsErrorStackTrace(null);
|
||||
|
|
@ -218,6 +224,7 @@ export const useTestMCPConnection = ({
|
|||
|
||||
return {
|
||||
tools,
|
||||
toolsWarnings,
|
||||
isLoadingTools,
|
||||
toolsError,
|
||||
toolsErrorStatus,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue