Boot MCP servers (npx/uvx) on a background thread instead of blocking
agent init. Built-in tools serve traffic immediately while MCP comes
online; each new agent reads whatever is ready at creation time.
Idempotent via _mcp_loaded flag — concurrent sessions never re-fork
subprocesses. Per-server failures are isolated and warmup is triggered
in app.py so loading overlaps with channel startup.
Stability fixes in mcp_client.py:
- Fix stderr buffer overflow: start daemon thread to continuously drain
stderr pipe, preventing 64KB buffer fill that blocks child process
- Fix notification interference: loop readline and skip JSON-RPC messages
without 'id' field (notifications) instead of treating them as responses
- Fix concurrent race condition: wrap send+receive in _call_lock so
multiple sessions cannot interleave reads/writes on the same client
- Fix missing timeout: use select.select() with 30s timeout in
_readline_with_timeout() to prevent infinite block on dead MCP server
Config improvements in tool_manager.py:
- Add _normalize_mcp_configs() to support both list format (mcp_servers)
and dict format (mcpServers used by Claude Desktop / Cursor)
- Add _load_mcp_configs() to load from ~/cow/mcp.json first, falling back
to config.json mcp_servers field for backward compatibility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs found during end-to-end validation with Amap and Chrome DevTools
MCP servers:
1. MCP tools were loaded into ToolManager._mcp_tool_instances but never
added to the agent's tool list. AgentInitializer._load_tools() only
iterated tool_classes (built-in tools). Added a second pass to append
all MCP tool instances.
2. When a MCP server config contains an "env" dict, it was passed directly
to subprocess.Popen, replacing the entire process environment. This
caused npx to fail because PATH and other inherited vars were missing.
Fixed by merging config env on top of os.environ.
Validated with:
- @amap/amap-maps-mcp-server (12 tools, stdio + API key env var)
- chrome-devtools-mcp (29 tools, stdio + remote debugging port)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows CowAgent to dynamically load tools from any MCP server at startup,
extending the agent from a fixed toolset to an open, extensible tool ecosystem.
## What's added
- `agent/tools/mcp/mcp_client.py`: lightweight JSON-RPC client supporting both
stdio (subprocess) and SSE (HTTP) transports — zero extra dependencies
- `agent/tools/mcp/mcp_tool.py`: `McpTool` wraps a single MCP tool as a
`BaseTool`, with dynamic name/description/params set at instance level
- `agent/tools/tool_manager.py`: new `_load_mcp_tools()` loads MCP servers at
startup via `McpClientRegistry`; falls back gracefully on any error; no-op
when `mcp_servers` is not configured
- `config.py`: registers `mcp_servers` in `available_setting` with inline docs
## Design
- No new dependencies — JSON-RPC implemented from scratch using stdlib only
- MCP clients are long-lived (initialized once, shared across tool calls)
- `McpClientRegistry` holds all subprocess handles and shuts them down cleanly
- Server init failures are non-fatal: logged as warnings, agent continues normally
- Zero overhead when `mcp_servers` is absent from config
## Config example
```json
"mcp_servers": [
{
"name": "filesystem",
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
]
```
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The translate module previously only supported Baidu translation, and the
factory raised a bare RuntimeError for any other type. This change adds
Youdao Translation as a second provider and improves the factory's error
message.
Implementation details:
- New YoudaoTranslator class in translate/youdao/youdao_translate.py
- Implements Youdao's v3 SHA-256 signature scheme, including the
truncate-input rule for queries longer than 20 characters
- Maps ISO 639-1 language codes to Youdao-specific codes
(zh -> zh-CHS, zh-TW -> zh-CHT, others pass through)
- Differentiates network errors, API error codes, and empty translations
- factory.create_translator now lists the supported types in its
RuntimeError message instead of failing silently
- Default config exposes youdao_translate_app_key and
youdao_translate_app_secret
Adds 17 unit tests covering signature correctness, language code mapping,
input truncation edge cases, the full request/response flow, and factory
dispatch. All tests pass under Python 3.11.
- rewrite streaming reply to official cardkit v2.0 API (default on, auto-fallback)
- fix Whisper hallucination: bump ASR sample rate to 16k, pass language=zh
- fix lock-over-IO and tmp file cleanup from #2791
- drop deprecated feishu_bot_name; quiet unknown-key warnings
- docs: cardkit permission and feishu_stream_reply usage
Further refinements on top of #2795:
- persist real session_id (notify_session_id) at task creation so group chats
correctly map back to the user's actual conversation
- mark scheduler turns with [SCHEDULED] (recognise legacy "Scheduled task"
prefix too for backward-compatible pruning)
- prune both DB and in-memory to scheduler_inject_max_per_session (default 3),
only marker-tagged pairs are touched; regular user turns never deleted
- send_message type gated by scheduler_inject_send_message (default false) —
fixed reminder text rarely benefits follow-up Q&A
Co-authored-by: huangrichao2020 <grdomai43881@gmail.com>
Address review feedback from #2794:
1. Use notify_session_id instead of receiver for correct group chat mapping
- Task creation should store the real session_id in action.notify_session_id
- Falls back to receiver for backward compatibility with old tasks
2. Add injection to all four execution branches:
- _execute_agent_task
- _execute_send_message
- _execute_tool_call
- _execute_skill_call (also fixed missing channel.send)
3. Add config switch and content truncation:
- scheduler_inject_to_session (default: true) to toggle the feature
- 2000 char limit prevents high-frequency tasks from bloating sessions
Fixes#2793