Windows 上自建服务跑多了,services.msc 就成了那个要用时才想起、打开又嫌烦的工具:想重启个 nginx,先 Win+R,再在三百多个服务里滚到 N 区,点开属性,确认,重启。我做了一个 uTools 插件 utools-service-manager 来省掉这套动作:把 Redis、Consul、nginx 这类常用服务加进白名单,uTools 里敲 svc 就能看实时状态、启停、改启动类型。更进一步,通过 uTools 内置的 MCP,Claude Code 这些 AI Agent 也能直接对它说「重启一下 nginx」「把 Redis 设为禁用」——一句话的事。
项目已开源:GitHub - canyuda/utools-service-manager,MIT 协议,需要 uTools 5+ 和 Windows。
从三个服务说起
起因是公司的一个项目:本地把项目跑起来之前,得先启动 nginx、Consul、Redis 三个服务。开发环境是 Windows,我把它们都注册成了 Windows 服务,管理是统一了,日常操作却麻烦——每次启停都要打开服务列表,在三百多个服务里翻那三个。
我的日常工具箱里常驻 uTools:切换 hosts、nvm 插件管理 Node 版本(公司有些前端项目必须用低版本 Node)、本地文件搜索,高频操作都收在搜索框里。顺手一搜 Windows 服务管理的插件——没有现成的,那就自己写一个。
动手之后翻文档,发现 uTools 还提供了 MCP 能力:插件可以把功能注册成 MCP 工具,暴露给 AI Agent 调用。这意味着 nginx、Consul、Redis 的启停可以直接在 Agent 里说一句「重启 nginx」就完成,人不用离开终端。插件因此从设计上就有两个入口:给人用的搜索框,给 Agent 用的 MCP。
两个入口,一套底层
插件实际上是给同一套服务控制层配了两个入口。
给人用的:uTools 关键词 服务管理 / svc 进入。主界面是白名单服务的卡片列表,每 2 秒自动轮询状态,状态变化时高亮提示。启动、停止、暂停、恢复、重启都是一键操作;启动类型支持自动 / 自动(延迟启动)/ 手动 / 禁用四档,修改前二次确认,「禁用」单独标红——把一个服务禁用之后忘了,排查起来能坑半天。加服务时可以对全机 310+ 个服务做关键字过滤,秒级出结果。
界面长这样:
主列表:顶部横幅在 uTools 未以管理员运行时给出引导;状态按运行中 / 已停止 / 已暂停筛选,右侧是启动类型标签和启停按钮。
展开详情:服务描述、二进制路径、运行账户一目了然,启动类型下拉直接改,全部控制操作集中在这里。
添加服务:全量服务关键字过滤,回车加入列表。
深色模式自动跟随 uTools 主题。
给 Agent 用的:通过 uTools 的 MCP Server 暴露 9 个工具,外加一个存储诊断工具。Claude Code / Codex 等支持 MCP 的 Agent 连上后就能调用:
1 | mcp__utools__restart_service { "name": "nginx" } |
一个方便的细节:插件窗口没开着也没关系,uTools 收到 MCP 调用会自动拉起插件。
Agent 侧的安全设计是四道闸:MCP 总开关默认关闭(插件设置里开);写操作另需「允许 Agent 写操作」子开关,也默认关闭;写操作受系统管理员权限限制,权限不足时返回明确的中文提示;每次工具调用写入审计日志(保留最近 50 条),设置页可查可清。只读工具(列表、搜索、详情)不受子开关限制, Agent 想先看看状态再决定动手,不需要额外授权。
服务控制层:PowerShell 查询 + sc.exe 控制
插件没有引任何系统级的原生模块,服务操作全靠 Windows 自带的两样东西。
查询走 PowerShell 的 CIM。白名单状态是一次单进程批量查询完成的:把服务名数组序列化后嵌进脚本,Get-CimInstance Win32_Service 逐个取,顺手读注册表的 DelayedAutostart 标志来区分「自动」和「自动(延迟启动)」——这个差异 Win32_Service 本身不直接给。结果用 ConvertTo-Json -Compress 一次性吐回。全量列表(添加页用)走另一条精简脚本,不读注册表,保证 310+ 服务的速度。
控制走 sc.exe。这里有两个实测出来的坑,都留在了代码注释里:
一是 sc config 的语法要求 start= auto——等号后面必须有空格。这种反直觉的写法用 argv 数组拼参数恰好能拼对:['config', name, 'start=', startType]。
二是 sc 的输出是 GBK 编码,按 utf8 解码会得到乱码。与其处理编码,干脆不回传 sc 的原始输出,只取退出码,映射成中文文案:5 是权限不足、1051 是被其他服务依赖无法停止、1053 是响应超时、1060 是服务不存在……常见的错误码全量收录在一张表里,用户看到的是「权限不足:请以管理员身份运行 uTools 后重试」,而不是 CreateService FAILED 5:。
管理员探测用的是 net session——只有管理员能成功执行,比读令牌的方法省事,8 秒超时兜底。
重启是最复杂的一个操作,值得单独贴出来:
1 | async function restartService (name) { |
坑在「停止」不是同步完成的:sc 停止指令返回 0 只代表命令被接受,服务状态可能还停在 STOP_PENDING。不等完全停止就 start,会得到一个 1056(服务已在运行)或者更诡异的中间态。所以重启的实现是:停止 → 容忍「本来就是停止状态」(错误码 1062)→ 每 800ms 查一次直到真正 Stopped → 再启动,全程 15 秒超时保护。
MCP 工具注册与前置闸门
工具声明在 plugin.json 的 tools 键里,每个工具带完整的 description 和 inputSchema(全部 additionalProperties: false)。描述写得比较啰嗦是故意的:Agent 选工具、填参数全靠这段文字,状态枚举、入参格式、「name 是服务名不是显示名」这类易错点都写进去了。plugin.json 里声明的键和 preload 里 registerTool 的调用必须一一对应。
preload 侧用一个小包装函数统一挂上前置闸门和审计:
1 | const wrap = (tool, write, fn) => { |
每个工具声明自己是不是写操作(write),guard 按需检查两级开关,任何异常都收口成 {ok: false, message} 返回给 Agent——MCP 工具抛异常对 Agent 的体验很差,统一的错误结构让它能读懂失败原因并向用户复述。
还有一个时序坑:uTools 的 API 桥注入时机可能晚于 preload 顶层执行,utools 拿不到就注册不了工具。解法是所有 uTools API 都走惰性获取(不缓存顶层引用),registerTool 注册失败时延迟重试(最多 20 次 × 250ms),桥什么时候到位什么时候注册。
持久化:双后端、四层兜底
uTools 提供两套本地存储:dbStorage(键值库)和 db(文档库)。插件在 preload 侧把两个都用了:写的时候两边尽力写,每边写入后立刻读回验证(JSON.stringify 比对);读的时候按序取,谁有数据用谁。文档库那边还处理了 _rev 冲突——put 失败时重读当前版本、合并重试。
渲染进程侧在这之上又加了两层:preload 桥(svcApi 转发,preload 侧自己还有双后端)和 localStorage(纯浏览器环境兜底)。所以白名单、设置、审计日志的读写实际是四层后端依次兜底,任何一层失败都把具体错误带回给调用方展示。
这个设计的起因是开发中真实踩过一次 dbStorage 写入后读不回来的问题,当时没有第二份数据,白名单直接丢了。存储层是插件的命根子——丢了白名单,用户加的服务全没了——所以宁可过度设计。事后还加了个 storage_debug 工具,把两个后端里所有键的原始内容 dump 出来,专门用于排查这类问题。
开发体验上的几个设计
- 浏览器演示模式:检测不到 uTools 环境时,
backend.js自动切到 demo 后端(内存数据 + localStorage),接口签名与真实后端完全对齐。前端开发调试不用碰 Windows 服务,也不用开 uTools,npm run dev起来就是完整界面。 - preload 纯 CJS:uTools 的 preload 跑在 CommonJS 环境(
public/preload/package.json里显式type=commonjs),而渲染进程是 Vite + ES Module。两边通过window.svcApi一套固定签名隔离,Vite 只构建渲染层,preload 原样拷贝。 - 原型先行:
prototype/里有可交互的 HTML 原型,浏览器打开后?variant=A|B|C切换三种布局方案。先在原型里把交互磨平,再写进 Vue 组件,避免在工程里来回改布局。 - 运行时依赖只有 Vue 一个,构建就是
vite build,产物dist/直接作为 uTools 开发者工具的项目目录。
现状与安装使用
插件刚完成开发,正在 uTools 插件市场的上架审核中,通过后普通用户在市场搜「服务管理」即可安装。想现在就体验,两条路:用 uTools 开发者工具直接跑,或自行构建 .upx 离线包安装。
开发者步骤:克隆仓库进 Windows服务/,npm install && npm run build,然后在 uTools 开发者工具里新建项目并指向 dist/,应用 ID 和插件密钥需要先创建应用获取。日常改前端用 npm run dev 走 5173 热更新;改 public/**(preload、plugin.json)后需要重新 build 并刷新项目。
一个前置条件要知道:修改启动类型、停止服务这类写操作需要以管理员身份运行 uTools,仅查看状态不需要。非管理员打开插件时会有横幅引导,只读功能完全可用。
小结
这个插件的出发点很小:少打开一次 services.msc。做完发现真正值得写的是两类问题——和 Windows 系统工具打交道时的兼容性细节(sc 的空格语法、GBK 输出、CIM 查询、停止是异步的),和 uTools 平台的工程约束(双存储后端、API 桥注入时序、preload 的 CJS 环境)。AI Agent 入口倒是最省事的部分:uTools 把 MCP Server 建好了,插件只需要把工具描述写清楚、把权限闸门立好,Agent 接进来就能用。
设计文档、uTools API 笔记都在仓库 docs/ 里,欢迎提 issue 和 PR。