ADK-agents/mcp_dev_agent/server.py
2026-07-30 14:29:14 +08:00

184 lines
5.8 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 进行多轮对话
- 自动提取最终回复文本
启动方式MCP 配置):
{
"mcpServers": {
"dev-agent": {
"command": "python",
"args": ["d:/nzy/workspace_python/agent/mcp_dev_agent/server.py"],
"env": {
"DEV_AGENT_API_URL": "http://127.0.0.1:8001",
"DEV_AGENT_APP_NAME": "dev_agent",
"DEV_AGENT_USER_ID": "codebuddy"
}
}
}
}
"""
import os
import json
import asyncio
import sys
from typing import Any
import httpx
from mcp.server import Server
from mcp.types import Tool, TextContent
from mcp.server.stdio import stdio_server
# 配置
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", "dev_agent")
DEV_AGENT_USER_ID = os.getenv("DEV_AGENT_USER_ID", "codebuddy")
# MCP Server
server = Server("dev-agent-mcp")
@server.list_tools()
async def list_tools() -> list[Tool]:
"""列出可用工具"""
return [
Tool(
name="run_dev_agent",
description=(
"调用 Dev Agent全栈开发子 Agent执行开发任务。\n"
"Dev Agent 可以读写文件、运行终端命令、执行编译/构建/测试。\n"
"适用于代码编写、bug 修复、项目搭建、编译验证等开发子任务。\n"
"传参说明task 是任务描述session_id 可选,不传则创建新会话。"
),
inputSchema={
"type": "object",
"properties": {
"task": {
"type": "string",
"description": "要 Dev Agent 执行的任务描述,越详细越好",
},
"session_id": {
"type": "string",
"description": "可选,会话 ID用于多轮对话/续聊",
},
},
"required": ["task"],
},
),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
"""调用工具"""
if name == "run_dev_agent":
return await _run_dev_agent(arguments)
else:
raise ValueError(f"Unknown tool: {name}")
async def _run_dev_agent(args: dict[str, Any]) -> list[TextContent]:
"""调用 Dev Agent API 执行任务"""
task = args.get("task", "").strip()
session_id = args.get("session_id") or "default"
if not task:
return [TextContent(type="text", text="错误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 [TextContent(
type="text",
text=f"调用 Dev Agent 失败HTTP {response.status_code}:\n{response.text[:500]}"
)]
events = response.json()
except httpx.ConnectError:
return [TextContent(
type="text",
text=f"无法连接到 Dev Agent API Server{DEV_AGENT_API_URL}\n"
f"请确认 api_server.py 是否已启动。"
)]
except Exception as e:
return [TextContent(type="text", text=f"调用 Dev Agent 出错: {e}")]
# 从事件列表中提取最终回复
result_text = _extract_final_response(events, session_id)
return [TextContent(type="text", text=result_text)]
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
async def main():
"""stdio 模式启动 MCP server"""
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, None)
if __name__ == "__main__":
# 确保 Windows 下 stdio 用二进制模式
if sys.platform == "win32":
import msvcrt
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
asyncio.run(main())