From 77735da982e8a54262d7f3899bd7bbddbf572632 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 12 Feb 2026 14:08:31 -0800 Subject: [PATCH] test: add unit test for NPM_CONFIG_CACHE injection in STDIO MCP Verifies that NPM_CONFIG_CACHE is auto-injected when not set, and preserved when explicitly provided. Also moves the import to module level per code style rules. --- .../mcp_server/mcp_server_manager.py | 3 +- .../mcp_server/test_mcp_server_manager.py | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index 68b20c5c3f7..7c211d48abe 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -31,6 +31,7 @@ from pydantic import AnyUrl import litellm from litellm._logging import verbose_logger +from litellm.constants import MCP_NPM_CACHE_DIR from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException from litellm.experimental_mcp_client.client import MCPClient from litellm.llms.custom_httpx.http_handler import get_async_httpx_client @@ -897,8 +898,6 @@ class MCPServerManager: # In containers the default (~/.npm or /app/.npm) may not exist # or be read-only, causing npx to fail with ENOENT. if "NPM_CONFIG_CACHE" not in resolved_env: - from litellm.constants import MCP_NPM_CACHE_DIR - resolved_env["NPM_CONFIG_CACHE"] = MCP_NPM_CACHE_DIR stdio_config: Optional[MCPStdioConfig] = None diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index b4b5811666b..1a50cacd308 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -112,7 +112,44 @@ class TestMCPServerManager: assert client.stdio_config is not None assert client.stdio_config["command"] == "node" assert client.stdio_config["args"] == ["server.js"] - assert client.stdio_config["env"] == {"NODE_ENV": "test"} + # NPM_CONFIG_CACHE is injected automatically for container compatibility + from litellm.constants import MCP_NPM_CACHE_DIR + + assert client.stdio_config["env"]["NODE_ENV"] == "test" + assert client.stdio_config["env"]["NPM_CONFIG_CACHE"] == MCP_NPM_CACHE_DIR + + async def test_create_mcp_client_stdio_injects_npm_config_cache(self): + """Test that _create_mcp_client injects NPM_CONFIG_CACHE when not already set, + and preserves user-provided NPM_CONFIG_CACHE when present.""" + from litellm.constants import MCP_NPM_CACHE_DIR + + manager = MCPServerManager() + + # Case 1: NPM_CONFIG_CACHE not set -> should be injected + server_no_cache = MCPServer( + server_id="stdio-npm-1", + name="test_npm_server", + url=None, + transport=MCPTransport.stdio, + command="npx", + args=["-y", "@modelcontextprotocol/server-everything"], + env={}, + ) + client = await manager._create_mcp_client(server_no_cache) + assert client.stdio_config["env"]["NPM_CONFIG_CACHE"] == MCP_NPM_CACHE_DIR + + # Case 2: NPM_CONFIG_CACHE already set -> should NOT be overwritten + server_with_cache = MCPServer( + server_id="stdio-npm-2", + name="test_npm_server_custom", + url=None, + transport=MCPTransport.stdio, + command="npx", + args=["-y", "@modelcontextprotocol/server-everything"], + env={"NPM_CONFIG_CACHE": "/custom/cache"}, + ) + client2 = await manager._create_mcp_client(server_with_cache) + assert client2.stdio_config["env"]["NPM_CONFIG_CACHE"] == "/custom/cache" def test_build_stdio_env_only_accepts_x_prefixed_placeholders(self): """Ensure only ${X-*} placeholders are substituted from headers."""