* feat(mcp): auto-promote deferred MCP tools from routing hints
When tool_search.enabled=true defers MCP tool schemas, PR1 routing hints
still require the model to spend a tool_search discovery round trip before
it can call the tool the routing metadata already points at. This adds a
McpRoutingMiddleware that matches the latest user message against PR1
routing keywords and promotes the matching deferred schemas before the
model call, removing that round trip.
Design (soft routing, opt-in, additive):
- Matches only the latest real HumanMessage (shared is_real_user_message
helper, reused by SkillActivationMiddleware so the two cannot drift);
case-insensitive substring match, no tokenizer dependency.
- Ordering: priority desc, then tool name asc; capped by the new global
tool_search.auto_promote_top_k (default 3, clamped 1..5). Does not add or
consume a per-tool auto_promote_top_k (PR1 schema unchanged); a per-tool
value is ignored with a DEBUG note.
- Returns a plain {"promoted": ...} state update (not a Command) and relies
on ThreadState.merge_promoted for union/dedupe, so auto-promote and a
model-triggered tool_search converge on the same catalog hash.
- Installed before DeferredToolFilterMiddleware on every deferred-tool path
(lead agent, subagent, embedded client, webhook via shared builders);
a construction-time assert rejects the reversed order. catalog_hash is
None / no routing index is a complete no-op, so bootstrap and ACP skip it.
- Privacy: never executes tools, never promotes policy-filtered tools, adds
no routing keywords or matched tool names to trace metadata or INFO/WARN
logs.
No behavior change when tool_search.enabled=false.
Tests: index construction, matching semantics, middleware state updates,
same-cycle deferred-filter interaction, lead/subagent/embedded-client
builder wiring + order invariant, config clamping, config.example.yaml
parseability, and privacy assertions.
* refactor(mcp): address auto-promote review nits
- executor: access app_config.tool_search.auto_promote_top_k directly to match
the lead-agent and embedded-client paths (drop the over-defensive getattr that
masked missing config); update the subagent test mock to carry tool_search.
- tool_search / mcp_routing_middleware: cross-reference the duplicated routing
priority/keyword normalization between the builder and the middleware's
defensive _normalize_index so they cannot silently drift.
- MCP_SERVER.md: document that auto-promote keyword matching is a case-insensitive
substring test (not word-boundary), advising distinctive keywords.
7.2 KiB
MCP (Model Context Protocol) Configuration
DeerFlow supports configurable MCP servers and skills to extend its capabilities, which are loaded from a dedicated extensions_config.json file in the project root directory.
Setup
-
Copy
extensions_config.example.jsontoextensions_config.jsonin the project root directory.# Copy example configuration cp extensions_config.example.json extensions_config.json -
Enable the desired MCP servers or skills by setting
"enabled": true. -
Configure each server’s command, arguments, and environment variables as needed.
-
Restart the application to load and register MCP tools.
Routing Hints
Use routing when an MCP server should be preferred for specific requests, such
as internal database questions that should use a PostgreSQL MCP tool before web
search. Routing hints are soft model guidance: they add a
<mcp_routing_hints> prompt section, but they do not forbid other tools. Use
agent-level allow/deny policy for hard restrictions. If tool_search.enabled
defers MCP tool schemas, matching routing metadata can also auto-promote the
deferred schema before the model call. Auto-promotion is controlled by the
top-level config.yaml -> tool_search.auto_promote_top_k setting.
{
"mcpServers": {
"postgres": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
"routing": {
"mode": "prefer",
"priority": 50,
"keywords": ["orders", "users", "SQL", "database", "table"]
},
"tools": {
"query": {
"routing": {
"mode": "prefer",
"priority": 100,
"keywords": ["query database", "orders table", "metrics"]
}
}
}
}
}
}
routing.mode:offdisables hints;preferemits hints.routing.priority:0to100; higher-priority hints are rendered first. Whentool_search.enabled=true, priority also orders auto-promote matches.routing.keywords: operator-authored terms that describe when to prefer the MCP tool. Empty keywords are allowed but do not emit a hint line and do not trigger auto-promotion. Auto-promote matching is a case-insensitive substring test against the latest user message (not token/word-boundary matching), so prefer distinctive keywords — a short term likeapialso matchesrapid. Over-matching only exposes an extra tool schema (soft/additive), never disables other tools.tools.<original_tool_name>.routing: overrides only the fields explicitly set for that tool. The key is the MCP server's original tool name, before the<server>_prefix added for model binding. If the server-levelrouting.modeisoff, a tool override must setmode: "prefer"; setting onlypriorityorkeywordsstill inheritsoffand emits no hint.tool_search.auto_promote_top_k: global limit for auto-promoted deferred MCP schemas per model call. Default3; valid range1..5.
Per-Tool Timeout (Stdio MCP Servers)
For stdio MCP servers, set tool_call_timeout to limit each individual MCP tool call in seconds:
{
"mcpServers": {
"github": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "$GITHUB_TOKEN"
},
"tool_call_timeout": 60
}
}
}
tool_call_timeout only applies to stdio servers. http and sse servers use transport-level timeouts, and DeerFlow logs a warning if tool_call_timeout is configured for those transports.
Filesystem MCP Servers
DeerFlow already provides built-in file tools for thread-scoped workspace access. Do not add an MCP filesystem server for the same DeerFlow workspace. The overlapping file tools use different path semantics, which can make LLM tool selection and file access behavior unstable.
DeerFlow does not currently adapt the MCP Roots mode for filesystem servers. In
particular, it does not publish per-thread MCP roots or map DeerFlow sandbox
paths such as /mnt/user-data/... to paths accepted by
@modelcontextprotocol/server-filesystem. Use DeerFlow's built-in file tools
for DeerFlow workspace files.
OAuth Support (HTTP/SSE MCP Servers)
For http and sse MCP servers, DeerFlow supports OAuth token acquisition and automatic token refresh.
- Supported grants:
client_credentials,refresh_token - Configure per-server
oauthblock inextensions_config.json - Secrets should be provided via environment variables (for example:
$MCP_OAUTH_CLIENT_SECRET)
Example:
{
"mcpServers": {
"secure-http-server": {
"enabled": true,
"type": "http",
"url": "https://api.example.com/mcp",
"oauth": {
"enabled": true,
"token_url": "https://auth.example.com/oauth/token",
"grant_type": "client_credentials",
"client_id": "$MCP_OAUTH_CLIENT_ID",
"client_secret": "$MCP_OAUTH_CLIENT_SECRET",
"scope": "mcp.read",
"refresh_skew_seconds": 60
}
}
}
}
Custom Tool Interceptors
You can register custom interceptors that run before every MCP tool call. This is useful for injecting per-request headers (e.g., user auth tokens from the LangGraph execution context), logging, or metrics.
Declare interceptors in extensions_config.json using the mcpInterceptors field:
{
"mcpInterceptors": [
"my_package.mcp.auth:build_auth_interceptor"
],
"mcpServers": { ... }
}
Each entry is a Python import path in module:variable format (resolved via resolve_variable). The variable must be a no-arg builder function that returns an async interceptor compatible with MultiServerMCPClient’s tool_interceptors interface, or None to skip.
Example interceptor that injects auth headers from LangGraph metadata:
def build_auth_interceptor():
async def interceptor(request, handler):
from langgraph.config import get_config
metadata = get_config().get("metadata", {})
headers = dict(request.headers or {})
if token := metadata.get("auth_token"):
headers["X-Auth-Token"] = token
return await handler(request.override(headers=headers))
return interceptor
- A single string value is accepted and normalized to a one-element list.
- Invalid paths or builder failures are logged as warnings without blocking other interceptors.
- The builder return value must be
callable; non-callable values are skipped with a warning.
How It Works
MCP servers expose tools that are automatically discovered and integrated into DeerFlow’s agent system at runtime. Once enabled, these tools become available to agents without additional code changes.
Example Capabilities
MCP servers can provide access to:
- Databases (e.g., PostgreSQL)
- External APIs (e.g., GitHub, Brave Search)
- Browser automation (e.g., Puppeteer)
- Custom MCP server implementations
Learn More
For detailed documentation about the Model Context Protocol, visit:
https://modelcontextprotocol.io