fix: ensure MCP and skills config changes are immediately reflected

- Use ExtensionsConfig.from_file() instead of cached config to always
  read latest configuration from disk in LangGraph Server
- Add mtime-based cache invalidation for MCP tools to detect config
  file changes made through Gateway API
- Call reload_extensions_config() in Gateway API after updates to
  refresh the global cache
- Remove unnecessary MCP initialization from Gateway startup since
  MCP tools are only used by LangGraph Server

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hetaoBackend
2026-01-25 22:37:53 +08:00
parent 044e38aec6
commit 038f5d44f4
7 changed files with 89 additions and 29 deletions

View File

@@ -2,6 +2,7 @@
import asyncio
import logging
import os
from langchain_core.tools import BaseTool
@@ -10,6 +11,46 @@ logger = logging.getLogger(__name__)
_mcp_tools_cache: list[BaseTool] | None = None
_cache_initialized = False
_initialization_lock = asyncio.Lock()
_config_mtime: float | None = None # Track config file modification time
def _get_config_mtime() -> float | None:
"""Get the modification time of the extensions config file.
Returns:
The modification time as a float, or None if the file doesn't exist.
"""
from src.config.extensions_config import ExtensionsConfig
config_path = ExtensionsConfig.resolve_config_path()
if config_path and config_path.exists():
return os.path.getmtime(config_path)
return None
def _is_cache_stale() -> bool:
"""Check if the cache is stale due to config file changes.
Returns:
True if the cache should be invalidated, False otherwise.
"""
global _config_mtime
if not _cache_initialized:
return False # Not initialized yet, not stale
current_mtime = _get_config_mtime()
# If we couldn't get mtime before or now, assume not stale
if _config_mtime is None or current_mtime is None:
return False
# If the config file has been modified since we cached, it's stale
if current_mtime > _config_mtime:
logger.info(f"MCP config file has been modified (mtime: {_config_mtime} -> {current_mtime}), cache is stale")
return True
return False
async def initialize_mcp_tools() -> list[BaseTool]:
@@ -20,7 +61,7 @@ async def initialize_mcp_tools() -> list[BaseTool]:
Returns:
List of LangChain tools from all enabled MCP servers.
"""
global _mcp_tools_cache, _cache_initialized
global _mcp_tools_cache, _cache_initialized, _config_mtime
async with _initialization_lock:
if _cache_initialized:
@@ -32,7 +73,8 @@ async def initialize_mcp_tools() -> list[BaseTool]:
logger.info("Initializing MCP tools...")
_mcp_tools_cache = await get_mcp_tools()
_cache_initialized = True
logger.info(f"MCP tools initialized: {len(_mcp_tools_cache)} tool(s) loaded")
_config_mtime = _get_config_mtime() # Record config file mtime
logger.info(f"MCP tools initialized: {len(_mcp_tools_cache)} tool(s) loaded (config mtime: {_config_mtime})")
return _mcp_tools_cache
@@ -43,11 +85,21 @@ def get_cached_mcp_tools() -> list[BaseTool]:
If tools are not initialized, automatically initializes them.
This ensures MCP tools work in both FastAPI and LangGraph Studio contexts.
Also checks if the config file has been modified since last initialization,
and re-initializes if needed. This ensures that changes made through the
Gateway API (which runs in a separate process) are reflected in the
LangGraph Server.
Returns:
List of cached MCP tools.
"""
global _cache_initialized
# Check if cache is stale due to config file changes
if _is_cache_stale():
logger.info("MCP cache is stale, resetting for re-initialization...")
reset_mcp_tools_cache()
if not _cache_initialized:
logger.info("MCP tools not initialized, performing lazy initialization...")
try:
@@ -79,7 +131,8 @@ def reset_mcp_tools_cache() -> None:
This is useful for testing or when you want to reload MCP tools.
"""
global _mcp_tools_cache, _cache_initialized
global _mcp_tools_cache, _cache_initialized, _config_mtime
_mcp_tools_cache = None
_cache_initialized = False
_config_mtime = None
logger.info("MCP tools cache reset")

View File

@@ -4,7 +4,7 @@ import logging
from langchain_core.tools import BaseTool
from src.config.extensions_config import get_extensions_config
from src.config.extensions_config import ExtensionsConfig
from src.mcp.client import build_servers_config
logger = logging.getLogger(__name__)
@@ -22,7 +22,11 @@ async def get_mcp_tools() -> list[BaseTool]:
logger.warning("langchain-mcp-adapters not installed. Install it to enable MCP tools: pip install langchain-mcp-adapters")
return []
extensions_config = get_extensions_config()
# NOTE: We use ExtensionsConfig.from_file() instead of get_extensions_config()
# to always read the latest configuration from disk. This ensures that changes
# made through the Gateway API (which runs in a separate process) are immediately
# reflected when initializing MCP tools.
extensions_config = ExtensionsConfig.from_file()
servers_config = build_servers_config(extensions_config)
if not servers_config: