first commit

This commit is contained in:
handsomeAq 2026-07-29 15:14:45 +08:00
commit 7fe37768c4
7 changed files with 309 additions and 0 deletions

1
.codebuddy/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
db/

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
*.egg-info/
dist/
build/
*.egg
# Virtual environment
.venv/
venv/
env/
# IDE
.idea/
.vscode/
*.swp
*.swo
# Environment
.env
.env.local
# OS
.DS_Store
Thumbs.db
# Project specific
*.db

1
mcp_tools/__init__.py Normal file
View File

@ -0,0 +1 @@
# mcp_tools package

View File

@ -0,0 +1 @@
# command_executor package

View File

@ -0,0 +1,127 @@
"""
终端命令执行 MCP Server
通过 MCP 协议暴露 run_command 命令 Dev Agent 使用
"""
import asyncio
import subprocess
import shlex
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
def log(msg):
"""写日志到 stderr不污染 MCP stdio 协议通道"""
print(msg, file=sys.stderr, flush=True)
app = Server("command-executor")
@app.list_tools()
async def list_tools():
return [
Tool(
name="run_command",
description="在终端中执行一条命令,返回输出结果。适用于编译、构建、运行测试等场景。",
inputSchema={
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "要执行的命令,如 'npm run build''npm test'",
},
"cwd": {
"type": "string",
"description": "命令执行的工作目录,默认使用当前目录",
},
"timeout": {
"type": "integer",
"description": "超时时间(秒),默认 300 秒",
"default": 300,
},
},
"required": ["command"],
},
),
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name != "run_command":
raise ValueError(f"Unknown tool: {name}")
cmd = arguments.get("command", "")
cwd = arguments.get("cwd") or os.getcwd()
timeout = int(arguments.get("timeout", 300))
if not cmd:
return [TextContent(type="text", text="错误:命令不能为空")]
log(f" [command] {cmd}")
log(f" [cwd] {cwd}")
log(f" [timeout] {timeout}s")
log(f" [PATH] {os.environ.get('PATH', 'N/A')[:200]}")
log(f" [where npm] {__import__('shutil').which('npm.cmd')}")
try:
# 使用异步 subprocess避免阻塞 asyncio 事件循环
# 在 Windows 上npm 是 .cmd 文件,需要通过 shell 执行
proc = await asyncio.create_subprocess_shell(
cmd,
cwd=cwd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout_bytes, stderr_bytes = await asyncio.wait_for(
proc.communicate(),
timeout=timeout,
)
stdout = stdout_bytes.decode("utf-8", errors="replace")
stderr = stderr_bytes.decode("utf-8", errors="replace")
output_parts = []
if stdout:
output_parts.append(f"[stdout]\n{stdout}")
if stderr:
output_parts.append(f"[stderr]\n{stderr}")
output = "\n".join(output_parts) if output_parts else "(无输出)"
max_len = 10000
if len(output) > max_len:
output = output[:max_len] + f"\n\n...(输出已截断,共 {len(output)} 字符)"
status = "成功" if proc.returncode == 0 else f"失败 (退出码 {proc.returncode})"
log(f" [result] {status}")
return [TextContent(type="text", text=f"命令执行{status}\n{output}")]
except asyncio.TimeoutError:
# 超时后杀进程
proc.kill()
await proc.wait()
log(f" [error] 超时")
return [TextContent(type="text", text=f"命令执行超时({timeout}秒): {cmd}")]
except Exception as e:
log(f" [error] {e}")
return [TextContent(type="text", text=f"命令执行出错: {e}")]
async def main():
log("MCP 命令执行服务器启动中...")
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream, write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
asyncio.run(main())

2
my_agent/__init__.py Normal file
View File

@ -0,0 +1,2 @@
# my_agent package
from . import agent

146
my_agent/agent.py Normal file
View File

@ -0,0 +1,146 @@
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from google.adk.tools.function_tool import FunctionTool
from mcp.client.stdio import StdioServerParameters
import os
import asyncio
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# --- 使用 vLLM 端点的智能体 ---
api_base_url = os.getenv("VLLM_API_BASE", "https://9router.aqroid.cn/v1")
model_name = os.getenv("VLLM_MODEL", "")
api_key = os.getenv("VLLM_API_KEY", "")
# Agent 可访问的工作目录
WORKSPACE_DIR = os.getenv("AGENT_WORKSPACE_DIR", r"D:\nzy\workspace_git")
# --- 文件系统 MCP 工具 ---
filesystem_mcp = McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="npx",
args=[
"-y",
"@modelcontextprotocol/server-filesystem",
os.path.abspath(WORKSPACE_DIR),
],
),
timeout=30.0,
),
)
# --- 终端命令执行工具Python 原生,绕开 MCP 通信问题)---
async def run_command(command: str, cwd: str = None, timeout: int = 300) -> str:
"""
在终端中执行一条命令返回输出结果
Args:
command: 要执行的命令 'npm run build''python -m pytest'
cwd: 命令执行的工作目录默认使用 AGENT_WORKSPACE_DIR
timeout: 超时时间默认 300
Returns:
命令执行结果stdout + stderr + 状态
"""
if not command:
return "错误:命令不能为空"
work_dir = cwd or os.path.abspath(WORKSPACE_DIR)
if not os.path.isdir(work_dir):
return f"错误:工作目录不存在 {work_dir}"
try:
proc = await asyncio.create_subprocess_shell(
command,
cwd=work_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout_bytes, stderr_bytes = await asyncio.wait_for(
proc.communicate(), timeout=timeout
)
except asyncio.TimeoutError:
proc.kill()
await proc.wait()
return f"命令执行超时({timeout}秒): {command}"
except Exception as e:
return f"命令执行出错: {e}"
stdout = stdout_bytes.decode("utf-8", errors="replace")
stderr = stderr_bytes.decode("utf-8", errors="replace")
parts = []
if stdout:
parts.append(f"[stdout]\n{stdout}")
if stderr:
parts.append(f"[stderr]\n{stderr}")
output = "\n".join(parts) if parts else "(无输出)"
max_len = 10000
if len(output) > max_len:
output = output[:max_len] + f"\n\n...(输出已截断,共 {len(output)} 字符)"
status = "成功" if proc.returncode == 0 else f"失败 (退出码 {proc.returncode})"
return f"命令执行{status}\n{output}"
# 注册为 ADK 工具
run_command_tool = FunctionTool(run_command)
root_agent = LlmAgent(
model=LiteLlm(
model=model_name,
api_base=api_base_url,
api_key=api_key if api_key else None,
custom_llm_provider="openai",
),
name="dev_agent",
description="全栈开发子 Agent可以读写文件、浏览目录、执行开发任务。",
instruction=(
"你是一个全栈开发子 AgentDev Agent由主控调度执行具体的开发任务。\n"
"\n"
"## 工作流程\n"
"1. 先理解任务需求和项目上下文\n"
"2. 使用文件系统工具浏览项目结构、读取相关文件\n"
"3. 编写或修改代码\n"
"4. 使用 run_command 工具运行编译/构建/测试,确保代码可正常工作\n"
"5. 验证结果后,按指定格式报告完成情况\n"
"\n"
"## 工作边界\n"
"- 所有文件操作限定在分配的工作目录范围内\n"
"- 你拥有的工具:文件系统操作(读/写/列目录)、终端命令执行\n"
"- 你可以自主完成代码编写、bug 修复、样式调整、接口修改、简单重构\n"
"- 需要上报的情况:\n"
" • 架构设计或重大技术选型决策\n"
" • 依赖包版本不兼容导致的编译/运行时错误(需要升级/降级依赖时)\n"
" • 工具调用异常、环境配置问题、命令超时等非代码问题\n"
" • 超出你能力范围或不确定的问题\n"
"\n"
"## 编译/构建守则\n"
"- 写完代码后,优先运行编译或构建命令验证\n"
"- 编译报错时,先判断错误类型:\n"
" • 代码语法/逻辑错误 → 自行修复后重试\n"
" • 依赖缺失或版本不兼容 → 上报,由主控决定处理方式\n"
" • 环境/工具问题 → 上报\n"
"- 连续修复 3 次仍无法通过编译时,上报当前状态和所有错误信息\n"
"- 只有编译通过后才算任务完成\n"
"\n"
"## 报告格式\n"
"完成任务后,结构化报告:\n"
"**状态**:成功 / 部分完成 / 失败(需上报)\n"
"**修改的文件**:列出所有修改的文件路径\n"
"**变更摘要**:简述做了什么\n"
"**验证结果**:编译/测试是否通过,如有警告需列出\n"
"**需要主控关注**:如有需要上报的问题,详细说明"
),
tools=[filesystem_mcp, run_command_tool],
)