总结之Agent Windows 终端控制

Windows 终端控制学习笔记(Agent + MCP)

一、本章概览

本模块演示如何让 AI Agent 通过 MCP 工具控制 Windows 终端

文件内容
mcp_server.pyMCP Server:封装 6 个 PowerShell 操作工具
test.pyMCP Client + Agent:加载工具后让 Agent 自主操作终端

MCP stdio

subprocess

pyautogui

psutil

用户指令

LangGraph Agent

MCP Server

PowerShell 后台执行

PowerShell GUI 操作

进程管理

二、架构设计

2.1 为什么用 MCP 而不是直接操作

Agent 本身没有操作操作系统的能力,它只能"思考"和"调用工具"。通过 MCP 协议,我们将终端操作封装为标准化工具,Agent 只需决定"调用哪个工具、传什么参数"。

这带来的好处:

优势说明
安全隔离Agent 不直接接触系统调用,所有操作经过 MCP Server
可验证通过日志追踪 Agent 调用了哪些工具,证明是通过工具执行的
可扩展新增终端能力只需在 MCP Server 注册新工具
跨平台同一套 Agent 代码,换个 MCP Server 就能控制 Linux/macOS

2.2 两种 PowerShell 操作方式

MCP Server 提供两种操作 PowerShell 的方式,适用于不同场景:

方式技术特点适用场景
后台执行subprocess不可见,直接返回结果执行命令获取输出
GUI 操作pyautogui模拟键盘操作,可见窗口需要可视化操作

GUI 操作

Agent 调用 run_powershell_script

pyautogui 激活窗口

pyautogui.write 输入命令

pyautogui.press enter

后台执行

Agent 调用 execute_powershell_command

subprocess.run

返回 stdout/stderr


三、MCP Server 实现

3.1 工具列表

MCP Server 共注册了 6 个工具

工具名功能底层技术
get_powershell_processes获取所有 PowerShell 进程列表psutil
close_powershell关闭所有 PowerShell 进程psutil.terminate()
close_powershell_process关闭指定 PID 的进程psutil.Process(pid).terminate()
open_powershell打开新的 PowerShell 窗口subprocess + Start-Process
run_powershell_script向窗口发送命令(GUI 可见)pyautogui.write()
execute_powershell_command后台执行命令并返回结果subprocess.run()

3.2 工具注册方式

使用 @mcp.tool() 装饰器注册 MCP 工具,参数描述通过 pydantic.Field 提供:

from mcp.server.fastmcp import FastMCP
from typing import Annotated
from pydantic import Field

mcp = FastMCP("windows-terminal-control")

@mcp.tool()
def open_powershell(
    working_directory: Annotated[str, Field(description="可选的工作目录,为空则使用当前目录")] = ""
) -> str:
    """打开新的 PowerShell 窗口"""
    # ...

@mcp.tool()
def execute_powershell_command(
    command: Annotated[str, Field(description="要执行的 PowerShell 命令")]
) -> str:
    """直接执行 PowerShell 命令并返回结果(不通过 GUI)"""
    # ...

3.3 核心实现代码

后台执行命令(subprocess)
import subprocess

def run_powershell_command(command: str, capture_output: bool = True):
    """执行 PowerShell 命令"""
    cmd = ["powershell", "-Command", command]
    result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
    return result.stdout.strip(), result.stderr.strip(), result.returncode
GUI 操作(pyautogui)
import pyautogui

def activate_powershell_window():
    """激活 PowerShell 窗口"""
    windows = pyautogui.getWindowsWithTitle('Windows PowerShell')
    if windows:
        window = windows[0]
        window.activate()
        time.sleep(0.5)
        return True
    return False

# 向窗口发送命令
pyautogui.hotkey('ctrl', 'c')           # 清空当前输入行
pyautogui.press('end')                  # 确保光标在末尾
pyautogui.write(script, interval=0.02)  # 逐字符输入命令
pyautogui.press('enter')                # 按 Enter 执行
进程管理(psutil)
import psutil

def get_powershell_processes():
    """获取所有 PowerShell 进程"""
    processes = []
    for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
        if proc.info['name'] and 'powershell' in proc.info['name'].lower():
            processes.append({
                'pid': proc.info['pid'],
                'name': proc.info['name'],
            })
    return processes

3.4 启动 MCP Server

# stdio 模式(Agent 以子进程启动)
python -m app.terminal_control.mcp_server stdio

# Streamable HTTP 模式(远程访问)
python -m app.terminal_control.mcp_server streamable-http

四、MCP Client + Agent 测试

4.1 完整代码流程

import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver

async def run():
    # 1. 创建 MCP 客户端,通过 stdio 连接 Server
    client = MultiServerMCPClient({
        "terminal": {
            "command": sys.executable,
            "args": [MCP_SERVER_PATH, "stdio"],
            "transport": "stdio",
        }
    })

    # 2. 加载 MCP 工具(自动转换为 LangChain Tool)
    tools = await client.get_tools()
    # tools = [get_powershell_processes, close_powershell, open_powershell, ...]

    # 3. 创建 Agent(带内存记忆)
    memory = MemorySaver()
    agent = create_react_agent(
        model=llm, tools=tools, checkpointer=memory,
    )

    # 4. 发送指令,Agent 自主决定调用哪个工具
    result = await agent.ainvoke(
        {"messages": [("user", "请打开一个新的 PowerShell 窗口")]},
        config=RunnableConfig(configurable={"thread_id": "terminal-001"}),
    )

4.2 Agent 决策流程

"请打开一个新的 PowerShell 窗口" 为例:

PowerShellMCP ServerAgent (LLM)用户PowerShellMCP ServerAgent (LLM)用户"打开一个新的 PowerShell 窗口"Thought: 需要打开窗口Action: open_powershell("")Start-Process powershell窗口已打开"PowerShell 已打开,当前进程数: 2""已为您打开了一个新的 PowerShell 窗口"

"执行 Get-Location 命令" 为例:

PowerShellMCP ServerAgent (LLM)用户PowerShellMCP ServerAgent (LLM)用户"执行 Get-Location"Thought: 需要执行命令并获取结果Action: execute_powershell_command("Get-Location")subprocess: Get-Location"D:\\ap\\my-agent-server""命令执行成功: D:\\ap\\my-agent-server""当前目录是 D:\\ap\\my-agent-server"

4.3 预定义测试任务

序号任务Agent 调用的工具
1打开新的 PowerShell 窗口open_powershell
2执行 Get-Locationexecute_powershell_command
3执行 Get-Dateexecute_powershell_command
4查看 PowerShell 进程get_powershell_processes
清理关闭第一个进程close_powershell_process

五、工具调用日志追踪

5.1 为什么需要日志

Agent 操作终端时,用户看不到 Agent 到底做了什么。通过追踪 messages 中的 AIMessage.tool_callsToolMessage,可以验证 Agent 确实是通过 MCP 工具执行的。

5.2 日志提取原理

Agent 执行后的 result["messages"] 包含完整的消息链:

messages = [
    HumanMessage(content="打开 PowerShell"),          # 用户输入
    AIMessage(tool_calls=[                            # Agent 决定调用工具
        {"name": "open_powershell", "args": {""}}
    ]),
    ToolMessage(content="PowerShell 已打开"),          # 工具返回结果
    AIMessage(content="已为您打开了 PowerShell 窗口"),  # Agent 最终回复
]

5.3 日志打印代码

from langchain_core.messages import AIMessage, ToolMessage

_printed_indices: set[int] = set()

def print_tool_call_logs(messages):
    """从 messages 中提取工具调用记录"""
    print("  [MCP 工具调用日志]")
    call_count = 0

    for idx, msg in enumerate(messages):
        if idx in _printed_indices:
            continue
        _printed_indices.add(idx)

        if isinstance(msg, AIMessage) and msg.tool_calls:
            for tc in msg.tool_calls:
                call_count += 1
                print(f"  调用[{call_count}]: {tc['name']}({tc['args']})")

        elif isinstance(msg, ToolMessage):
            content = str(msg.content)[:200]  # 截断过长内容
            print(f"    返回: {content}")

    if call_count == 0:
        print("  (本次未调用工具,Agent 直接回复)")

5.4 日志输出示例

--- 任务 1/4: 请打开一个新的 PowerShell 窗口 ---
  [MCP 工具调用日志]
  调用[1]: open_powershell({'working_directory': ''})
    返回: PowerShell 已打开,当前运行进程数: 2
[Agent 回复] 已成功打开一个新的 PowerShell 窗口。

--- 任务 2/4: 在 PowerShell 中执行 Get-Location ---
  [MCP 工具调用日志]
  调用[1]: execute_powershell_command({'command': 'Get-Location'})
    返回: 命令执行成功: D:\ap\my-agent-server
[Agent 回复] 当前目录是 D:\ap\my-agent-server。

六、依赖说明

依赖作用安装
pyautoguiGUI 自动化,模拟键盘操作 PowerShell 窗口pip install pyautogui
psutil进程管理,获取/关闭 PowerShell 进程pip install psutil
mcpMCP Server 框架pip install mcp
langchain-mcp-adaptersMCP → LangChain Tool 适配器pip install langchain-mcp-adapters
langgraphAgent 框架(create_react_agent)pip install langgraph

注意: pyautogui 是 GUI 自动化工具,操作时会实际控制鼠标和键盘。运行时请不要手动操作鼠标,避免冲突。可通过 pyautogui.FAILSAFE = True 启用安全模式(鼠标移到屏幕左上角时中断)。


七、run_powershell_script vs execute_powershell_command

这两个工具都能执行 PowerShell 命令,但实现方式完全不同:

维度execute_powershell_commandrun_powershell_script
技术subprocess.run 后台执行pyautogui GUI 模拟键盘
可见性不可见,无窗口可见,在 PowerShell 窗口中输入
返回值直接返回 stdout只返回"命令已发送"
获取结果有完整输出无法获取输出
适用场景需要命令执行结果需要可视化操作

Agent 通常会选择 execute_powershell_command,因为它能返回执行结果。


八、关键 API 速查

# ---- MCP Server ----
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("server-name")

@mcp.tool()
def my_tool(param: Annotated[str, Field(description="参数描述")]) -> str:
    """工具描述"""
    return "结果"

mcp.run(transport="stdio")

# ---- MCP Client ----
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
    "name": {
        "command": sys.executable,
        "args": ["mcp_server.py", "stdio"],
        "transport": "stdio",
    }
})
tools = await client.get_tools()

# ---- Agent ----
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(model=llm, tools=tools, checkpointer=MemorySaver())
result = await agent.ainvoke(
    {"messages": [("user", "你的指令")]},
    config={"configurable": {"thread_id": "001"}},
)

# ---- 提取工具调用日志 ----
from langchain_core.messages import AIMessage, ToolMessage
for msg in result["messages"]:
    if isinstance(msg, AIMessage) and msg.tool_calls:
        for tc in msg.tool_calls:
            print(f"调用: {tc['name']}({tc['args']})")
    elif isinstance(msg, ToolMessage):
        print(f"返回: {msg.content}")
于2024年4月-2025年9月期间,研究团队在贵州习水国家级自然保护区制定39条样线,涵盖灌木林、常绿阔叶林、针叶林、常绿落叶阔叶混交林、针阔混交林等不同植被类型,每条样线分春夏秋冬4个季节采集样品,用真菌采集软件记录经纬度、海拔、采集地点、时间、生境等信息,使用佳能相机(R6 mark Ⅱ)对大型真菌进行拍照,并采集标本,标本存放于贵州省生物研究所大型真菌标本馆(HGAMF)。 通过形态学初步鉴定,结合分子生物学最终鉴定,参考已]报道的中国毒蘑菇名录开展毒蘑菇的认定。 调查到保护区内有毒真菌7目25科64种,导致中毒的主要类型有急性肾衰竭型、神经精神型和胃肠炎型。最终形成贵州习水国家级自然保护区大型有毒真菌图片数据集,它由以下2个部分组成。 (1)附件1包含78张原始照片(.JPG),照片名字包括了大型有毒真菌的拉丁名和中文名,若无中文名的直接用拉丁名。 (2)附件2是一个压缩文件,包含了2张工作表,其中一张表是大型有毒真菌39条样线的信息,另一张表是大型有毒真菌的中毒类型。 照片采用佳能相机R6 mark Ⅱ拍摄,物种鉴定通过多种文献核实,并经两位以上专家鉴定确认。该数据集可为研究地及周边的普通人识别有毒大型真菌提供参考,通过及时的图片对比,能有效避免误采误食大型有毒真菌,同时为因误食大型真菌可能引发的身体损伤进行了总结,能为患者及时治疗提供参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值