mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-28 14:10:37 +00:00
* fix(mcp): avoid cache resets for unrelated config changes extensions_config.json carries MCP servers, skills and middleware settings, but the MCP cache treated any whole-file signature change as MCP state change, so toggling a skill retired every cached tool and closed all pooled sessions. Record a snapshot of the effective MCP slice (enabled servers' parsed config plus mcpInterceptors) and retire MCP state only when that slice changes. Hand the exact config revision to tool discovery so a change during discovery cannot publish tools built from a superseded revision, and add a non-initializing refresh entry point so disabling the last enabled server still converges the cache and pool on the next tool assembly. * docs(mcp): align cache guide and restore error diagnostics Review feedback on #5750 flagged three non-blocking issues: the MCP module guide still described the pre-semantic-snapshot invalidation contract; the tools.py sanitization covered every exception in the MCP block, discarding diagnostics for failures that cannot carry resolved credentials; and _is_cache_stale()'s docstring presented it as a pure predicate despite adopting a new file signature. Narrow the type-only logging to ExtensionsConfig.from_file() and restore logger.exception() for tool caching, pool cleanup and plugin filtering, with a regression test asserting the message and traceback survive. Document the effective-MCP-slice comparison, the stable config handoff to discovery, refresh_mcp_cache_if_active() and the _initialized_without_config handling in the module guide, and note the signature-adoption side effect in the docstring. * fix(mcp): unify config normalization and verify signatures Review follow-up on #5750. - Normalize mcpInterceptors (bare string == single-element list; missing key == empty list) and the type/transport alias through one shared module used by the tools cache, the interceptor builder and the durable-task config snapshot, so equivalent spellings no longer retire tools/sessions or raise McpTaskConfigurationError. - Treat a resolved-path switch as a signal to re-read the effective MCP slice instead of an invalidation reason: an identical slice adopts the new path and signature and keeps pooled sessions. - Refuse to publish or adopt a signature whose sha256 digest is missing ((mtime, size, None) from a file that could not be read). - Sync the English and Chinese MCP user docs with the effective-config comparison, and document that the Gateway admin reset stays unconditional. * docs(mcp): note the unverifiable-signature fail-closed branch A present config file whose content cannot be hashed is treated as stale, unlike a deleted file which keeps the last-known-good cache. Record that asymmetry in the module guide. * docs(mcp): scope the no-hash rationale to never-initialized processes The cost-free early return in refresh_mcp_cache_if_active() only covers a process that never published an MCP cache. A process that did publish one (including an empty cache from the zero-server or LangGraph Studio lazy-init path) still pays one stat+sha256 per tool assembly. Correct the rationale in the docstring and the matching comment in get_available_tools().
47 lines
1.8 KiB
Python
47 lines
1.8 KiB
Python
"""Shared normalization of MCP configuration values.
|
|
|
|
The MCP tools cache, the custom-interceptor builder and the durable-task
|
|
configuration snapshot must all agree on when two configs are equivalent.
|
|
Keeping the rules here stops those three call sites from drifting apart.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
# Server fields that only affect how tools are presented or routed to the
|
|
# model, never how a durable-task server is contacted or driven.
|
|
TASK_PRESENTATION_FIELDS = ("description", "routing", "tools", "tool_name_prefix")
|
|
|
|
|
|
def normalize_mcp_interceptor_paths(raw: Any) -> list[Any]:
|
|
"""Return the custom interceptor paths a raw ``mcpInterceptors`` value selects.
|
|
|
|
Mirrors what ``build_mcp_tool_interceptors`` consumes: a bare string is one
|
|
path, a list is used verbatim (order and duplicates preserved), and any
|
|
other value — including a missing key and ``None`` — selects no custom
|
|
interceptors.
|
|
"""
|
|
if isinstance(raw, str):
|
|
return [raw]
|
|
if isinstance(raw, list):
|
|
return list(raw)
|
|
return []
|
|
|
|
|
|
def normalize_mcp_server_config(server: Any, *, task_runtime: bool = False) -> dict[str, Any]:
|
|
"""Return a parsed server config with alias-only fields normalized away.
|
|
|
|
``transport`` is an MCP-spec alias for ``type``: the model validator copies
|
|
it onto ``type`` but keeps the raw extra key (``extra="allow"``), so dropping
|
|
it makes equivalent ``type``/``transport`` spellings compare equal. With
|
|
``task_runtime=True`` the presentation-only fields are dropped too, matching
|
|
what the durable-task runtime actually depends on.
|
|
"""
|
|
dumped = server.model_dump(mode="json")
|
|
dumped.pop("transport", None)
|
|
if task_runtime:
|
|
for field in TASK_PRESENTATION_FIELDS:
|
|
dumped.pop(field, None)
|
|
return dumped
|