ADK-agents/mcp_dev_agent/server.py

139 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Dev Agent MCP Server
将 Dev Agent 封装为 MCP 工具,供 CodeBuddy 等 MCP 客户端调用。
功能:
- run_dev_agent: 提交任务给 Dev Agent 执行,返回执行结果
- 支持指定 session_id 进行多轮对话
- 自动提取最终回复文本
环境变量配置:
- DEV_AGENT_API_URL: API Server 地址(默认 http://127.0.0.1:8001
- DEV_AGENT_APP_NAME: 调用的 agent 名称(默认 my_agent
- DEV_AGENT_USER_ID: 用户 ID默认 codebuddy
"""
import os
import sys
# 强制 UTF-8
os.environ["PYTHONUTF8"] = "1"
import httpx
from mcp.server.fastmcp import FastMCP
# 配置
DEV_AGENT_API_URL = os.getenv("DEV_AGENT_API_URL", "http://127.0.0.1:8001")
DEV_AGENT_APP_NAME = os.getenv("DEV_AGENT_APP_NAME", "my_agent")
DEV_AGENT_USER_ID = os.getenv("DEV_AGENT_USER_ID", "codebuddy")
# MCP Server
mcp = FastMCP(name=f"dev-agent-{DEV_AGENT_APP_NAME}")
@mcp.tool()
async def run_dev_agent(task: str, session_id: str = "default") -> str:
"""调用 Dev Agent全栈开发子 Agent执行开发任务。
Dev Agent 可以读写文件、运行终端命令、执行编译/构建/测试。
适用于代码编写、bug 修复、项目搭建、编译验证等开发子任务。
Args:
task: 要 Dev Agent 执行的任务描述,越详细越好
session_id: 可选,会话 ID用于多轮对话/续聊,默认 "default"
Returns:
Dev Agent 的执行结果
"""
if not task:
return "错误task 不能为空"
# 构造请求
payload = {
"appName": DEV_AGENT_APP_NAME,
"userId": DEV_AGENT_USER_ID,
"sessionId": session_id,
"newMessage": {
"role": "user",
"parts": [{"text": task}],
},
}
try:
async with httpx.AsyncClient(timeout=600.0) as client: # 10 分钟超时
response = await client.post(
f"{DEV_AGENT_API_URL}/run",
json=payload,
headers={"Content-Type": "application/json"},
)
if response.status_code != 200:
return (
f"调用 Dev Agent 失败HTTP {response.status_code}:\n"
f"{response.text[:500]}"
)
events = response.json()
except httpx.ConnectError:
return (
f"无法连接到 Dev Agent API Server{DEV_AGENT_API_URL}\n"
f"请确认 api_server.py 是否已启动。"
)
except Exception as e:
return f"调用 Dev Agent 出错: {e}"
# 从事件列表中提取最终回复
return _extract_final_response(events, session_id)
def _extract_final_response(events: list[dict], session_id: str) -> str:
"""从事件列表中提取 agent 的最终文本回复"""
if not events:
return "(无返回事件)"
final_text_parts = []
for event in events:
content = event.get("content", {})
role = content.get("role", "")
parts = content.get("parts", [])
author = event.get("author", "")
if role == "model" and author == DEV_AGENT_APP_NAME:
for part in parts:
if "text" in part:
final_text_parts.append(part["text"])
response = "\n".join(final_text_parts).strip()
if not response:
# 如果没有找到最终回复,返回事件摘要
summary = f"{len(events)} 个事件\n"
for e in events[-5:]:
content = e.get("content", {})
parts = content.get("parts", [])
role = content.get("role", "")
author = e.get("author", "")
part_types = [list(p.keys())[0] for p in parts]
summary += f" - [{role}] {author}: {part_types}\n"
response = f"(未提取到最终文本回复)\n{summary}"
# 附上 session_id 方便续聊
response += f"\n\n---\nsession_id: {session_id}"
return response
def main():
"""stdio 模式启动 MCP server"""
# Windows stdio 二进制模式
if sys.platform == "win32":
import msvcrt
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
mcp.run(transport="stdio")
if __name__ == "__main__":
main()