MCP任务文件服务器
一个模型上下文协议(MCP)服务器,它动态地将Taskfile.yml任务作为单独的MCP工具公开,允许AI助手发现并执行Taskfile中定义的任何任务。
为什么
- Taskfile中已经定义了构建、linting等的标准做法。允许助手直接执行这些任务。
- 本地、CI和AI之间的对等性。
- 看起来是个有趣的主意。
特性
- 动态任务发现:在运行时自动从Taskfile.yml中发现所有任务
- 个人任务工具:每个任务都成为自己的MCP工具,具有适当的模式
- 变量模式生成:自动提取任务变量以进行正确的参数验证
- 本机任务执行:直接使用go任务库(不执行子流程)
- 多根支持:通过MCP发现根 根 功能,从每个根目录加载任务文件
- 自动重新加载:监视每个根的已解析本地Taskfile图的更改,并自动向连接的客户端重新公开更新的工具
- MCP协议合规性:使用官方的Go MCP SDK完全符合规范
需求
- 转到1.25或更高版本
安装
go install github.com/rsclarke/mcp-taskfile-server@latest这个地方 mcp-taskfile-server 二进制in $GOBIN (或 $GOPATH/bin).
用法
运行服务器
服务器通过JSON-RPC在stdin/stdout上进行通信:
mcp-taskfile-server根发现
客户端握手完成后,服务器调用 roots/list 以发现从哪些目录加载任务文件。支持MCP的客户端 根 能力可以提供一个或多个 file:// 根URI。如果客户端不支持根(返回JSON-RPC -32601),服务器回退到当前工作目录。
每个根目录都应该是直接包含该工作区顶级任务文件的目录。服务器仅检查支持的任务文件名的确切根目录,而不遍历父目录。
等效的本地文件URI别名在进入服务器状态之前会被规范化,因此以下值 file:///repo 和 file://localhost/repo 共享一个内部根标识。
当根在运行时发生变化时(notifications/roots/list_changed),服务器会区分根集,删除已删除的根(取消其每个根的监视器goroutine并注销其工具),并加载任何新添加的根。
初始任务文件加载失败的根将保留为 卸载占位符:记录工作目录,并监视根目录中的标准任务文件名,以便自动拾取启动后创建或修复的任务文件。占位符根部露出零MCP工具。
动态工具发现
服务器会自动发现每个根目录的Taskfile.yml中的所有任务,并将每个任务作为单独的MCP工具公开。 不包含公共任务的根仍然可以成功加载;它们只是在公共任务出现之前不公开任何MCP工具。
每个工具自动包括:
- 任务特定变量:从具有适当默认值的任务定义中提取
- 适当的描述:使用Taskfile.yml中的任务描述
工具名称映射
任务文件任务名称可以包含以下字符 :, *、空间、, /,以及不是MCP有效工具名称的非ASCII文本。服务器会自动清理导出的工具名称,以符合 MCP工具名称规范 ([a-zA-Z0-9_.-],最大长度 128).
命名规则
| 转换 | 示例任务名称 | MCP工具名称 |
|---|---|---|
| 科隆→ 下划线 | db:migrate | db_migrate |
| 命名空间(包括) | docs:serve | docs_serve |
| 深命名空间 | uv:run:dev:lint-imports | uv_run_dev_lint-imports |
| 保留前导点 | uv:.venv | uv_.venv |
| 单个通配符 | start:* | start |
| 多个通配符 | deploy:*:* | deploy |
| 混合命名空间+通配符 | uv:add:* | uv_add |
| 砍杀→ 下划线 | build/dev | build_dev |
| 空间→ 下划线 | release prod | release_prod |
| 非ASCII码→ 下划线 | café | caf_ |
当工具名称与原始任务名称不同时,原始任务名称将包含在工具描述中以便于发现。 如果经过净化的工具名称超过128个字符,则会被截断并给出确定性哈希后缀。 如果在清理和可选的根前缀后,多个任务解析为相同的最终MCP工具名称,则所有冲突的任务都将从MCP暴露中排除。
多根前缀
带着 单根,工具名称没有前缀(如上所示)。当客户提供 多重根,每个工具名称都以经过净化的根目录基名形式作为前缀,以避免冲突:
| 根目录 | 任务 | MCP工具名称 |
|---|---|---|
/home/user/frontend | build | frontend_build |
/home/user/backend | build | backend_build |
/home/user/frontend | lint:* | frontend_lint |
前缀来源于包含非字母数字字符的目录基名(除 _, -, .)用下划线代替。如果添加或删除根,使得计数超过1↔N边界,所有工具都会相应地重新注册,无论是否带有前缀。
通配符任务
任务文件 通配符任务 (例如。 start:*)作为所需工具暴露 MATCH 参数。 MATCH 是一个JSON字符串数组,每个字符串有一个条目 * 任务名称中的段;模式集 minItems 和 maxItems 因此,在处理程序运行之前,错误的arity调用会导致验证失败。服务器将每个条目替换为相应的 * 在调用时重建完整的任务名称。
对于定义为的任务 start:*,调用该工具:
{"name": "start", "arguments": {"MATCH": ["web"]}}执行 task start:web.
对于具有多个通配符的任务(例如。 deploy:*:*),按顺序为每个通配符段提供一个数组元素。空字符串将被拒绝:
{"name": "deploy", "arguments": {"MATCH": ["api", "production"]}}执行 task deploy:api:production.
突破性变化:接受以前的版本MATCH作为单个逗号分隔的字符串(例如。"api,production").更新客户端以发送JSON字符串数组。值现在可以安全地包含逗号。
工具结果形状
每次任务调用都会返回一个 CallToolResult 最多三个 TextContent 块,以便客户端可以独立渲染或过滤流:
- 状态块 (始终存在):一行总结,如
Task构建exited with status 0失败的任务会显示由报告的底层退出代码go-task(例如。Task失败exited with status 7: ...);非执行失败(如安装错误)会退回到Taskfailed:. - 标准块 (如果非空):捕获的标准输出,带
Meta: {"stream": "stdout"}. - Stderr块 (如果非空):捕获的标准误差,带
Meta: {"stream": "stderr"}.
IsError 设置为 true 每当底层任务返回错误时,客户端都可以对失败做出反应,而无需解析状态行。
MCP集成
此服务器实现了模型上下文协议,可以与任何兼容MCP的客户端或AI助手一起使用。服务器:
- 请求根 握手后从客户端发送;如果不受支持,则回退到工作目录
- 动态发现 每个根目录的Taskfile.yml中的所有任务
- 对任务名称进行消毒 转换为有效的MCP工具名称,以实现严格的客户端兼容性
- 显示每个任务 作为具有正确JSON模式的单独MCP工具
- 自动提取 参数验证的任务变量
- 对根本变化做出反应 通过在运行时添加/删除根和重新同步工具
- 以本机方式执行任务 使用go任务库(无子流程调用)
- 提供全面 错误处理和反馈
自动重新加载
服务器使用以下命令解析每个根的Taskfile图 go-task,然后使用以下命令监视该图中每个本地任务文件的父目录 fsnotify每个根都有自己的观察程序goroutine,由每个服务器拥有 watch.Manager,因此添加或删除根只会生成或取消该根的观察者,而不会干扰其他根。当修改、添加或删除某个监视的任务文件时,服务器会自动:
- 重新加载并重新解析根目录的任务文件图
- 将更新的工具集与当前注册的工具进行比较
- 通过MCP SDK添加新工具和删除过时工具
- 通知已连接的客户端更改(
notifications/tools/list_changed)
每次重新加载后,观察者都会重新读取根的观察状态,以便新包含的本地任务文件开始被观察,删除的任务文件停止被观察。文件系统事件被取消暂停(约200毫秒),以避免快速编辑过程中的冗余重新加载。观察程序在根的生命周期内运行,并在根删除或服务器关闭时取消。
如果根任务文件无效或被删除,则根将被替换为一个新的占位符,该占位符保留了工作目录和监视集,但没有加载任务文件,因此根的工具将被撤回,直到恢复有效的任务文件。
日志记录
服务器向以下对象发送结构化JSON日志 标准错误 使用 log/slog通过stdio MCP传输,stderr是诊断的唯一安全通道;stdout是为JSON-RPC流量保留的。
每一行都是一个JSON对象,至少包含:
time,level,msgservice和version此服务器的- 一
event命名特定事件的字段(例如。root.load_failed,tools.collision,watcher.reload_failed) - 上下文字段(如适用):
root_uri,tool_name,error
默认级别为 info.设置 MCP_TASKFILE_LOG_LEVEL 环境变量为以下之一 debug, info, warn,或 error 更改它(不区分大小写)。未确认的值回落到 info.
MCP_TASKFILE_LOG_LEVEL=debug mcp-taskfile-serverMCP记录能力
- 标准 是真理的源泉,由
MCP_TASKFILE_LOG_LEVEL. - MCP转发 由客户端设置的阈值通过以下方式进行门控
logging/setLevel。在客户端提高级别之前,SDK会抑制每条记录,因此连接的客户端除非选择加入,否则不会看到日志噪音。
结构化属性(event, root_uri, tool_name, error等)作为通知逐字转发 data payload,以便客户端可以对其进行过滤。MCP臂仅在客户端握手完成后连接;较早发出的记录仅到达stderr。
注: 服务器是围绕每个进程的单个MCP会话构建的--Server.Runoverstdio只绑定一个,因此带内日志流始终明确地限定为“此客户端”。如今,多租户HTTP部署已超出范围;如果添加,推荐的模式是*mcp.Server每次会话通过NewStreamableHTTPHandlersgetServer工厂,这保持了1:1不变。
错误处理
服务器处理各种错误情况:
- 缺少Taskfile.yml
- 任务名称无效
- 任务执行失败
- 无效的MCP请求
所有错误均按照MCP错误响应格式返回。
安全考虑
此服务器执行任务文件中定义的任意命令。仅在受信任的环境中使用它,并确保您的任务文件不包含恶意命令。
发展
包布局
服务器被拆分为小型、单一用途的包 internal/:
internal/server--编排者。拥有*Server值、根映射、已注册的工具映射、生成计数器和连接到MCP SDK的生命周期处理程序(HandleInitialized,HandleRootsChanged).internal/roots--加载并表示任务文件根。拥有Root值类型、URI规范化和任务文件图解析。不依赖于MCP SDK。internal/tools--纯粹的计划。将根快照转换为MCP形状的工具,处理名称净化、冲突检测、多根前缀、通配符MATCH模式以及编排器应用的计划/差异。internal/exec--每次调用任务执行处理程序,包括stdout/stderr捕获和状态/流TextContent块返回给客户。internal/watch--每个根fsnotify观察者生命周期。A.watch.Manager每个根URI最多生成一个goroutine并公开Apply/Reconcile/Shutdown.internal/logging--结构化日志原语:stderr处理程序构造、MCPlogging手臂和aFanoutHandler它将记录镜像到两个接收器。
生命周期
- 服务器安装程序:MCP服务器是通过以下方式创建的
mcp.NewServer()使用InitializedHandler和RootsListChangedHandler。编排器由以下部分构成server.New()并通过以下方式连接到MCP服务器SetToolRegistry. - 记录仪接线:
SetLogger()交换活动*slog.Logger原子;HandleInitialized()通过绑定到活动会话的MCP臂扩展它,以便后续记录通过以下方式到达客户端notifications/message. - 根发现:
HandleInitialized()电话ListRoots在会话中(返回工作目录)-32601)以及initializeRoots()加载它们。HandleRootsChanged()电话replaceRoots()将实时设置与客户端的更新列表进行对账。 - 快照/计划/应用:
syncTools()遵循三阶段模式——锁定状态下的快照,调用tools.BuildPlan如果没有锁,则重新获取锁,验证生成,并将diff应用于MCP注册表。 - 世代守护者:每个状态突变(根添加/删除、根重新加载、占位符交换)都会增加一个生成计数器。如果在构建计划时运行另一个变异器,则丢弃过时的计划——该变异器将产生自己的同步。
- 工具生成:对于每个非内部任务,
tools.CreateToolForTask使用提取的变量和(对于通配符任务)a构建MCP工具MATCH数组模式大小为通配符计数。 - 处理程序创建:在规划过程中,每个任务都会通过以下方式获得一个每次调用的处理程序
exec.NewHandler(workdir, taskName)绑定到根目录的工作目录。 - 本地执行:处理程序构建一个新的
task.Executor每次调用(静默模式,捕获的stdout/stderr)并调用executor.Run()从go任务库中返回aCallToolResult使用上面描述的status/stdout/stderr块。 - 观看:
watch.Manager.Apply()为每个新添加的运行的根生成一个goroutinewatch.Watch()。在每个取消公告的文件系统事件中,观察者通过以下方式调用编排器Server.ReloadRoot(),通过以下方式重建根roots.Build()并触发另一个syncTools().
关键组件
internal/server
New():构造一个带有丢弃记录器和新鲜记录器的空编排器watch.Manager根在客户端握手后加载。SetLogger()/SetToolRegistry():将结构化记录器和MCP服务器(用作工具注册表)连接到编排器中。HandleInitialized():将MCP记录臂安装到活动记录器上,然后调用initializeRootsFromSession().HandleRootsChanged():列出会话的根、调用replaceRoots(),同步工具,并应用每根观察器差异。initializeRoots()/replaceRoots():将根映射与所需的一组MCP根进行协调。两者均返回areconcileResult添加/删除规范URI,以便调用者可以驾驶syncTools()和watch.Manager.Apply()锁外面。ReloadRoot():通过以下方式重建单个根roots.Build()并重新同步工具。失败时,根将通过以下方式替换为占位符disableRootToolsLocked()因此,在恢复任务文件之前,其工具将被撤回。syncTools():通过生成验证来协调快照/计划/应用循环,以安全地更新已注册的工具。snapshotToolStateLocked():在锁定状态下捕获当前根映射和生成以供使用tools.BuildPlan.RootWatchState()/Shutdown():实施watch.StateProvider合同;Shutdown取消每个根监视器并等待它们退出。
internal/roots
Build()/Load():解析工作目录的任务文件图,设置task.Executor,并返回已填充的*Root.NewUnloaded():返回占位符*Root对于当前无法加载Taskfile的目录;仍然会监视目录中的标准任务文件名。CanonicalRootURI()/DirToURI():规范当地file://URI,因此等效别名共享一个身份。loadTaskfileWatchSet():解析本地Taskfile图,并导出要监视的父目录和确切的Taskfile路径。
internal/tools
BuildPlan():从中计算所需的MCP工具集和处理程序StateSnapshot而不改变编排者;记录并排除冲突的工具名称。CreateToolForTask():为单个任务生成MCP工具定义、架构和描述。Diff():通过序列化架构字节将旧注册工具与所需工具进行比较,并返回过时/添加的名称列表。- 命名助手:
RootPrefix()消毒助手执行规则 工具名称映射.
internal/exec
NewHandler():返回一个mcp.ToolHandler绑定到根工作目录和任务名称。处理程序解析通配符MATCH参数,使用捕获的流运行任务,并生成状态/stout/stderrTextContent阻碍。
internal/watch
Manager:为每个根观察者goroutines生成和跟踪。Apply()执行添加/去除差异;Reconcile()收敛到期望的URI集;Shutdown()取消每个观察者,等待他们耗尽。Watch():每个根fsnotify循环。呼叫StateProvider.RootWatchState()对于要订阅的目录和修改后会触发取消暂停重新加载的任务文件路径,请通过StateProvider.ReloadRoot().
internal/logging
NewLogger():标准JSON*slog.Logger门控由MCP_TASKFILE_LOG_LEVEL并标记为service/version.InstallMCP():包装现有的记录器,以便每条记录也作为MCP转发notifications/message在活跃的会议上。FanoutHandler:将单个记录分派给多个slog.Handlers(stderr+MCP),而不耦合它们的生命周期。
关键依赖项
- Go MCP SDK:官方MCP协议实施
- go任务:本机Taskfile.yml解析和执行
- Fsnotify:用于自动重新加载的跨平台文件系统通知
服务器使用go-task库的本机API进行解析和执行,确保与Taskfile.yml功能的最大兼容性。
