/// FIST-Mbt MCP server: 暴露 14 tools + 2 resources + 2 prompts。
/// 接入 colmugx/mcp@0.17.4(2026-07-28 协议,Stdio/HTTP 双传输)。
/// —— 引擎(进程内共享的可变状态)——
/// 运行时优先接入 SQLite 内建数据库(本项目根目录 fist-mbt.db 持久化);
/// 打开失败(如缺 libsqlite3)自动回退内存版,保证 server 总能启动。
fn make_engine() -> FistEngine {
match SqliteStore::new() {
Some(s) => FistEngine::new(store = StoreBackend::Sql(s))
None => FistEngine::new()
}
}
/// 使用模块级 var,handler 闭包内直接引用(全局变量无需显式捕获)。
let engine : FistEngine = make_engine()
/// 模块级心跳登记表(M4 运维:ActivityState / no_signal watchdog)。
let heartbeats : Heartbeat = Heartbeat::new()
/// 默认时间戳(无系统时钟依赖,保证 demo 可重现)
fn now_default() -> String { "2026-09-05T00:00:00Z" }
/// —— JSON schema 辅助 ——
fn string_prop(desc : String) -> Json {
Json::object({
"type": Json::string("string"),
"description": Json::string(desc),
})
}
fn int_prop(desc : String) -> Json {
Json::object({
"type": Json::string("integer"),
"description": Json::string(desc),
})
}
fn schema(props : Map[String, Json], required : Array[String]) -> Json {
Json::object({
"type": Json::string("object"),
"properties": Json::object(props),
"required": Json::array(required.map(fn(s) { Json::string(s) })),
})
}
/// 从 JSON 参数中读取字符串,缺省回退默认值
fn get_str(args : Json, key : String, default~ : String = "") -> String {
match args {
Object(map) =>
match map.get(key) {
Some(String(s)) => s
_ => default
}
_ => default
}
}
/// 从 JSON 参数中读取整数,缺省回退默认值
fn get_int(args : Json, key : String, default~ : Int = 3) -> Int {
match args {
Object(map) =>
match map.get(key) {
Some(Number(n, ..)) => n.to_int()
_ => default
}
_ => default
}
}
/// —— 结果转换辅助 ——
fn to_result(r : Result[Task, String]) -> Result[@mcp.ToolResult, @mcp.MCPError] {
match r {
Ok(t) => Ok(@mcp.ToolResult::text(t.to_json().stringify()))
Err(e) => Err(@mcp_types.ToolError(e))
}
}
fn to_ids_result(
r : Result[Array[String], String],
) -> Result[@mcp.ToolResult, @mcp.MCPError] {
match r {
Ok(ids) => Ok(@mcp.ToolResult::text(Json::array(
ids.map(fn(s) { Json::string(s) }),
).stringify()))
Err(e) => Err(@mcp_types.ToolError(e))
}
}
fn to_unit_result(r : Result[Unit, String]) -> Result[@mcp.ToolResult, @mcp.MCPError] {
match r {
Ok(_) => Ok(@mcp.ToolResult::text("ok"))
Err(e) => Err(@mcp_types.ToolError(e))
}
}
/// —— overview 资源:任务统计概览 ——
fn overview_json() -> Json {
let tasks = engine.list_all()
let m : Map[String, Json] = Map([
("product", Json::string("FIST-Mbt")),
("version", Json::string("0.1.0")),
("principles", Json::number(7.0)),
("statuses", Json::string("待领取/已领取/拆分中/执行中/待验收/已完成/已归档")),
])
let counts : Map[String, Json] = Map([])
let status_names = [
"待领取", "已领取", "拆分中", "执行中", "待验收", "已完成", "已归档",
]
for s in status_names {
counts.set(s, Json::number(
tasks.filter(fn(t) { t.status_to_string() == s }).length().to_double(),
))
}
m.set("tasks_total", Json::number(tasks.length().to_double()))
m.set("tasks_by_status", Json::object(counts))
Json::object(m)
}
/// —— MCP server 构建与运行 ——
pub async fn run_server() -> Unit {
let now = now_default()
let s0 = @mcp.MCPServer::MCPServer("fist-mbt", "0.1.0")
let s1 = s0
// ---- tools ----
.tool(
"publish",
"发布根任务(仅限人类指挥官)。参数:project_dir 项目目录、description 任务描述、created_by 创建者身份(默认 human_steward)。",
schema(
{
"project_dir": string_prop("项目目录(必填)"),
"description": string_prop("任务描述(必填)"),
"created_by": string_prop("创建者身份(默认 human_steward)"),
"now": string_prop("时间戳(可选,默认内置)"),
},
["project_dir", "description"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let project_dir = get_str(args, "project_dir")
let description = get_str(args, "description")
let created_by = get_str(args, "created_by", default="human_steward")
let ts = get_str(args, "now", default=now)
let r = engine.publish(
project_dir~,
description~,
created_by~,
now=ts,
)
match r {
Ok(id) => Ok(@mcp.ToolResult::text(
Json::object({
"task_id": Json::string(id),
"message": Json::string("已发布根任务"),
}).stringify(),
))
Err(e) => Err(@mcp_types.ToolError(e))
}
},
)
.tool(
"plan",
"对指定任务拆分生成一层子任务(非原子任务才可拆)。参数:task_id 任务id、split_n 拆分数量(默认3)、by 执行者。",
schema(
{
"task_id": string_prop("父任务 id(必填)"),
"split_n": int_prop("拆分数量(默认3)"),
"by": string_prop("执行拆分的人"),
"now": string_prop("时间戳(可选)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let split_n = get_int(args, "split_n", default=3)
let by = get_str(args, "by", default="human_steward")
let ts = get_str(args, "now", default=now)
to_ids_result(engine.plan(
task_id~,
split_n~,
by~,
now=ts,
))
},
)
.tool(
"claim",
"认领任务(待领取 -> 已领取;随后 execute 进入执行中)。参数:task_id、assignee 认领人。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"assignee": string_prop("认领人(必填)"),
"now": string_prop("时间戳(可选)"),
},
["task_id", "assignee"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let assignee = get_str(args, "assignee")
let ts = get_str(args, "now", default=now)
to_result(engine.claim(task_id~, assignee~, now=ts))
},
)
.tool(
"execute",
"记录执行交付物。参数:task_id、deliverable 交付物描述。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"deliverable": string_prop("交付物描述(必填)"),
"now": string_prop("时间戳(可选)"),
},
["task_id", "deliverable"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let deliverable = get_str(args, "deliverable")
let ts = get_str(args, "now", default=now)
to_result(engine.execute(task_id~, deliverable~, now=ts))
},
)
.tool(
"submit",
"提交验收(执行中 -> 待验收)。参数:task_id。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"now": string_prop("时间戳(可选)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let ts = get_str(args, "now", default=now)
to_result(engine.submit(task_id~, now=ts))
},
)
.tool(
"verify",
"验收通过(待验收 -> 已完成,父任务自动上卷)。参数:task_id、verifier 验收人。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"verifier": string_prop("验收人(必填)"),
"now": string_prop("时间戳(可选)"),
},
["task_id", "verifier"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let verifier = get_str(args, "verifier")
let ts = get_str(args, "now", default=now)
to_result(engine.verify(task_id~, verifier~, now=ts))
},
)
.tool(
"archive",
"归档任务(仅限人类指挥官)。参数:task_id、by 操作者。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"by": string_prop("操作者(默认 human_steward)"),
"now": string_prop("时间戳(可选)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let by = get_str(args, "by", default="human_steward")
let ts = get_str(args, "now", default=now)
to_result(engine.archive(task_id~, by~, now=ts))
},
)
.tool(
"list",
"列出全部任务,可按状态过滤。参数:status(可选,中文状态名)。",
schema(
{
"status": string_prop("按状态过滤(可选):待领取/已领取/执行中/待验收/已完成/已归档"),
},
[],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let status_str = get_str(args, "status")
let tasks = if status_str == "" {
engine.list_all()
} else {
match TaskStatus::from_string(status_str) {
Some(st) => engine.list_by_status(st)
None => return Err(@mcp_types.ToolError(
"未知状态: \{status_str}",
))
}
}
Ok(@mcp.ToolResult::text(Json::array(
tasks.map(fn(t) { t.to_json() }),
).stringify()))
},
)
.tool(
"get",
"查询单个任务详情。参数:task_id。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
match engine.get_task(task_id) {
Some(t) => Ok(@mcp.ToolResult::text(t.to_json().stringify()))
None => Err(@mcp_types.ToolError("task not found: \{task_id}"))
}
},
)
.tool(
"delete",
"删除已归档任务。参数:task_id。",
schema(
{
"task_id": string_prop("任务 id(必填,仅已归档可删)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
to_unit_result(engine.delete_task(task_id))
},
)
.tool(
"task_plan_deep",
"AO 式递归拆解:对指定任务拆出整棵多层子任务树并写库。参数:task_id 任务id、split_n 每层默认拆分数(默认3)、by 拆解拳长、spec 可选 omega spec JSON(laws 作切割依据、fingerprint 作验收基准)、now 时间戳。",
schema(
{
"task_id": string_prop("父任务 id(必填,非原子任务)"),
"split_n": int_prop("每层默认拆分数(默认3,有 spec.laws 时以 laws 为准)"),
"by": string_prop("执行拆解的拳长身份(默认 leader)"),
"spec": string_prop("可选 omega spec JSON 字符串"),
"now": string_prop("时间戳(可选)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let split_n = get_int(args, "split_n", default=3)
let by = get_str(args, "by", default="leader")
let spec_raw = get_str(args, "spec")
let ts = get_str(args, "now", default=now)
let spec : Json? = if spec_raw == "" {
None
} else {
try {
Some(@json.parse(spec_raw))
} catch {
_ => return Err(@mcp_types.ToolError("spec 不是合法 JSON"))
}
}
match engine.plan_deep(
task_id~,
split_n~,
by~,
spec=spec,
now=ts,
) {
Ok(j) => Ok(@mcp.ToolResult::text(j.stringify()))
Err(e) => Err(@mcp_types.ToolError(e))
}
},
)
.tool(
"conflicts_check",
"claim 冲突检测(AO --no-takeover):认领前检查任务是否已被他人/本人持有。参数:task_id、assignee。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"assignee": string_prop("拟认领人(必填)"),
},
["task_id", "assignee"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let assignee = get_str(args, "assignee")
match check_claim(engine, task_id, assignee) {
Ok(_) => Ok(@mcp.ToolResult::text(Json::object({
"task_id": Json::string(task_id),
"assignee": Json::string(assignee),
"ok": Json::boolean(true),
"message": Json::string("可安全认领(待领取)"),
}).stringify()))
Err(e) => Err(@mcp_types.ToolError(e))
}
},
)
.tool(
"heartbeat",
"活动信号上报(AO ActivityState):任务执行前/执行中周期性上报,超时静默将触发 heal 回滚。参数:task_id、signal(可选默认 active)、now。",
schema(
{
"task_id": string_prop("任务 id(必填)"),
"signal": string_prop("信号(可选):pre-execution/active/submit,默认 active"),
"now": string_prop("时间戳(可选,默认内置)"),
},
["task_id"],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
let signal = get_str(args, "signal", default="active")
let ts = get_str(args, "now", default=now)
match engine.get_task(task_id) {
None => Err(@mcp_types.ToolError("task not found: \{task_id}"))
Some(_) => {
heartbeats.beat(task_id, signal~, now=ts)
Ok(@mcp.ToolResult::text(Json::object({
"task_id": Json::string(task_id),
"signal": Json::string(signal),
"last_seen": Json::string(heartbeats.last_seen_of(task_id)),
}).stringify()))
}
}
},
)
.tool(
"heal",
"no_signal 看护(AO watchdog):心跳超时静默的活跃任务(执行中/已领取/拆分中)回滚为已领取待重派。参数:now、timeout_sec(可选默认600)。",
schema(
{
"now": string_prop("当前时间戳(默认内置)"),
"timeout_sec": int_prop("超时秒数(可选默认600)"),
},
[],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let ts = get_str(args, "now", default=now)
let timeout_sec = get_int(args, "timeout_sec", default=600)
match heal_stale_tasks(engine, heartbeats, ts, timeout_sec=timeout_sec) {
Ok(healed) => Ok(@mcp.ToolResult::text(Json::object({
"healed": Json::array(healed.map(fn(s) { Json::string(s) })),
"count": Json::number(healed.length().to_double()),
"message": Json::string("超时静默任务已回滚待重派"),
}).stringify()))
Err(e) => Err(@mcp_types.ToolError(e))
}
},
)
.tool(
"task_cleanup",
"归档清理:删除超过保留期的已归档任务。参数:now、retention_days(可选默认30)。",
schema(
{
"now": string_prop("当前时间戳(默认内置)"),
"retention_days": int_prop("归档保留天数(可选默认30)"),
},
[],
),
async fn(args : Json) -> Result[@mcp.ToolResult, @mcp.MCPError] {
let ts = get_str(args, "now", default=now)
let retention_days = get_int(args, "retention_days", default=30)
match cleanup_archived(engine, ts, retention_days=retention_days) {
Ok(removed) => Ok(@mcp.ToolResult::text(Json::object({
"removed": Json::array(removed.map(fn(s) { Json::string(s) })),
"count": Json::number(removed.length().to_double()),
"message": Json::string("已清理过期归档任务"),
}).stringify()))
Err(e) => Err(@mcp_types.ToolError(e))
}
},
)
// ---- resources ----
.resource(
"fist://principles",
"FIST 七条金条",
"FIST-Mbt 的七条不可动摇原则(机器可读 JSON)",
"application/json",
async fn() -> Result[@mcp_resource.ResourceReadResult, @mcp.MCPError] {
Ok({
uri: "fist://principles",
content: @mcp_resource.ResourceContent::Text(
principles_json().stringify(),
),
})
},
)
.resource(
"fist://overview",
"任务概览",
"当前任务总数与各状态统计",
"application/json",
async fn() -> Result[@mcp_resource.ResourceReadResult, @mcp.MCPError] {
Ok({
uri: "fist://overview",
content: @mcp_resource.ResourceContent::Text(
overview_json().stringify(),
),
})
},
)
// ---- prompts ----
.prompt(
"fist:check_in",
"任务打卡引导:认领/开工前的检查清单",
[
{
name: "task_id",
description: Some("要认领/开工的任务 id"),
required: Some(true),
},
],
async fn(args : Json) -> Result[@mcp_types.GetPromptResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
Ok({
description: Some("FIST 任务打卡检查清单"),
messages: [
{
role: "user",
content: @mcp_types.ContentItem::Text(
"请为任务 \{task_id} 打卡。检查:1) 我已确认任务描述与交付标准;2) 我使用 claim 认领、execute 记录交付物;3) 完成后用 submit 提交验收。",
),
},
],
})
},
)
.prompt(
"fist:verify",
"验收引导:提交后的验收清单",
[
{
name: "task_id",
description: Some("要验收的任务 id"),
required: Some(true),
},
],
async fn(args : Json) -> Result[@mcp_types.GetPromptResult, @mcp.MCPError] {
let task_id = get_str(args, "task_id")
Ok({
description: Some("FIST 任务验收清单"),
messages: [
{
role: "user",
content: @mcp_types.ContentItem::Text(
"请验收任务 \{task_id}。检查:1) 交付物是否满足描述标准;2) 使用 verify 通过;3) 通过后可用 archive 归档。",
),
},
],
})
},
)
// run_stdio 内部 raise TransportError,入口捕获后退出
s1.run_stdio() catch {
e => println("fist-mbt server error: \{e}")
}
}