mcptree

mcptree

An MCP server that adds declarative decision trees to FastMCP, enabling deterministic branching logic for runbooks and triage flows with a full audit trail.

Category
访问服务器

README

mcptree

CI License: MIT Python

FastMCP made tools easy. mcptree makes decisions declarative.

MCP gives servers tools, resources, prompts, and elicitation — but no primitive for branching logic. If you want an agent to follow a runbook, a triage flow, or an escalation checklist, today you either bake the logic into a giant system prompt (the agent may skip a step, or hallucinate one) or hand-write a state machine per server. mcptree is a small open protocol plus a FastMCP-style framework for publishing that logic as data: a YAML decision tree that a built-in engine walks deterministically, consulting the agent only where the outside world — a tool result, a human answer, a genuine judgment call — is actually needed.

The division of labor: the agent does the acting, the tree does the deciding. Every step is recorded in an append-only trace, so "why did the agent escalate this?" always has an answer.

One honest boundary: the engine is deterministic given reported inputs. Tool results are reported by the agent, not observed by the server — the trace records what was reported and through which channel (elicited human answers are marked source: "elicitation"), so it is testimony you can audit, not surveillance.

  • SPEC.md — the normative wire protocol (v0.2), for anyone implementing a conforming server or client in any language.
  • docs/use-case.md — a captioned walkthrough of the incident tree below, call by call, with real envelope JSON.

See it work

examples/incident_response.py is a real two-process crash-proof-resume demo: each phase spawns its own mcptree serve stdio subprocess, sharing nothing but the sessions directory on disk. Process 1 starts the tree and reports a failing health check; process 2 — a brand-new OS process run later — resumes the exact same session by id, reports the logs, makes the judgment call, and reaches outcome: remediate with the full audit trace. Real terminal output, no mocking:

<img src="docs/screenshots/04-demo-phase1-start.png" width="100%" alt="Process 1 (phase1): tree_start opens a session on incident-triage, tree_answer reports a 503 health check and auto-advances to inspect_logs, then the process exits."/>

<img src="docs/screenshots/05-demo-phase2-resume.png" width="100%" alt="Process 2 (phase2), a brand-new OS process: tree_status resumes the same session_id from disk, reports OOM logs, classifies the error, reaches outcome: remediate, and tree_trace prints the full audit path."/>

Run it yourself: python3 examples/incident_response.py phase1, then in a separate invocation python3 examples/incident_response.py phase2 <session_id> (the first phase prints the exact command). More shots — mcptree validate and mcptree viz — are in docs/test-evidence.md.

Quickstart A — mount onto an existing FastMCP server

One line adds the whole protocol (five tools, session persistence, an audit trail) to any FastMCP server you already have:

from fastmcp import FastMCP
from mcptree import DecisionTrees

mcp = FastMCP("incident-bot")
DecisionTrees(mcp, "trees/")   # mounts tools, sessions, audit trail

Quickstart B — instant server from a directory of trees

mcptree isn't published to PyPI yet — install from source, from a checkout of this repo:

pip install -e .
mcptree serve trees/

Other CLI commands:

mcptree validate trees/       # lint trees in CI — node-level errors
mcptree viz trees/incident.yaml   # render a tree as a mermaid diagram

The flagship tree

trees/incident.yaml is a production-incident triage runbook exercising all five node types (abridged and annotated below — the unmodified file is in the repo at trees/incident.yaml, and appears verbatim in SPEC.md):

mcptree: "0.1"
id: incident-triage
title: Production incident triage
entry: check_health

nodes:
  check_health:
    type: action                    # instruct the agent to call a tool it has
    tool: http_get
    args: { url: "https://api.example.com/health" }
    result: { capture: health }     # reported result stored into session facts
    next: branch_on_status

  branch_on_status:
    type: condition                 # pure data branch — engine-evaluated, no round trip
    on: health.status               # fact path
    branches:
      - when: { eq: 200 }
        then: all_clear
      - when: { gte: 500 }
        then: inspect_logs
    default: ask_maintenance        # v0.1: every condition node requires a default

  classify_error:
    type: judgment                  # the ONLY place the model decides, and only among declared options
    prompt: Classify the dominant error in the logs.
    evidence: [logs]
    options:
      - { id: oom,     then: remediate_oom }
      - { id: db_conn, then: remediate_db }
      - { id: unknown, then: escalate }
    require_rationale: true         # rationale recorded in the trace

  all_clear:
    type: terminal
    outcome: resolved
    summary: "Service healthy; no action required."

  # ... inspect_logs, ask_maintenance, remediate_oom, remediate_db, escalate
  # in trees/incident.yaml

Rendered with mcptree viz trees/incident.yaml (real output, pasted verbatim):

flowchart TD
  check_health["check_health<br/>action: http_get"]
  branch_on_status{"branch_on_status<br/>on health.status"}
  inspect_logs["inspect_logs<br/>action: get_logs"]
  classify_error[["classify_error<br/>judgment"]]
  ask_maintenance["ask_maintenance<br/>ask"]
  all_clear(["all_clear<br/>resolved"])
  remediate_oom(["remediate_oom<br/>remediate"])
  remediate_db(["remediate_db<br/>remediate"])
  escalate(["escalate<br/>escalate"])
  check_health --> branch_on_status
  branch_on_status -- eq 200 --> all_clear
  branch_on_status -- gte 500 --> inspect_logs
  branch_on_status -- default --> ask_maintenance
  inspect_logs --> classify_error
  classify_error -- oom --> remediate_oom
  classify_error -- db_conn --> remediate_db
  classify_error -- unknown --> escalate
  ask_maintenance -- yes --> all_clear
  ask_maintenance -- no --> escalate

What 0.2 adds

  • Asks reach the human. When the client supports MCP elicitation, ask prompts go straight to the human via the client's own UI, recorded in the trace as source: "elicitation" — the model can no longer silently answer the human's questions. Unsupported or declined → the agent relays, exactly as before. Opt out with DecisionTrees(..., elicit=False) or mcptree serve --no-elicit. (SPEC §5.8)
  • Data flows into actions. {{ path }} placeholders in action args, prompts, and terminal summaries resolve from captured facts — runbooks parameterized by what they learn. (SPEC §2.9)
  • Composite predicates. all: / any: / not: compose the leaf operators, e.g. when: { all: [ { gte: 1 }, { lte: 2 } ] }. (SPEC §2.4)
  • Judgment capture. A judgment's chosen option can be captured as a fact and branched on downstream. (SPEC §2.3)

All four live in trees/deploy.yaml, a production deploy gate with its own golden-trace tests. Trees declaring mcptree: "0.1" (like the incident tree above) still load and behave exactly as before.

An envelope walk

Every tree_start/tree_answer/tree_status call returns one self-describing JSON "step envelope" — an agent that lost all context can call tree_status and keep going correctly, with no prompt memory required. Three real calls against trees/incident.yaml (session_id and timestamps are from an actual run; see docs/use-case.md for the full walk through to a terminal outcome):

1. tree_start("incident-triage") — first node is an action:

{
  "session_id": "ses_23d02c087aca",
  "tree_id": "incident-triage",
  "node": "check_health",
  "step": 1,
  "error": null,
  "outcome": null,
  "evidence": {},
  "state": "awaiting_tool_result",
  "instruction": "Call tool `http_get` with the args in `expects.args`, then report its result via tree_answer (set is_error=true if the call failed).",
  "expects": {
    "kind": "tool_result",
    "tool": "http_get",
    "args": { "url": "https://api.example.com/health" },
    "schema": null
  }
}

2. tree_answer(session_id, step=1, value={"status": 503}) — the engine auto-advances through branch_on_status (a condition node, no round trip) straight to the next action:

{
  "session_id": "ses_23d02c087aca",
  "tree_id": "incident-triage",
  "node": "inspect_logs",
  "step": 2,
  "error": null,
  "outcome": null,
  "evidence": {},
  "state": "awaiting_tool_result",
  "instruction": "Call tool `get_logs` with the args in `expects.args`, then report its result via tree_answer (set is_error=true if the call failed).",
  "expects": {
    "kind": "tool_result",
    "tool": "get_logs",
    "args": { "service": "api", "lines": 200 },
    "schema": null
  }
}

3. tree_answer(session_id, step=2, value={"lines": ["java.lang.OutOfMemoryError: heap"]}) — lands on the judgment node, with the captured logs surfaced as evidence:

{
  "session_id": "ses_23d02c087aca",
  "tree_id": "incident-triage",
  "node": "classify_error",
  "step": 3,
  "error": null,
  "outcome": null,
  "evidence": { "logs": { "lines": ["java.lang.OutOfMemoryError: heap"] } },
  "state": "awaiting_judgment",
  "instruction": "Classify the dominant error in the logs.",
  "expects": {
    "kind": "enum",
    "options": ["oom", "db_conn", "unknown"],
    "require_rationale": true
  }
}

From here, tree_answer(session_id, step=3, value="oom", rationale="OutOfMemoryError in captured logs") lands on the remediate_oom terminal node with state: "done" and outcome: "remediate" — see docs/use-case.md for that step and the full audit trace.

Status

v0.2. Python 3.11+, MIT licensed. Runtime dependencies: fastmcp and pyyaml. 91 tests passing (python3 -m pytest). See SPEC.md for the full protocol and docs/use-case.md for the end-to-end walk.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选