Skip to main content
Glama
shogo-hs

guidepost-mcp

by shogo-hs

guidepost-mcp

Сервер, который проводит агента по дереву-схеме через MCP. Когда агент отправляет метку ответа, сервер возвращает что нужно проверить или подсказать дальше на основе правил. Когда дерево пройдено до конца — сопровождение завершено.

Агент с самозацикливанием гибок, но при одном и том же запросе каждый раз проходит разным путём. Для процедур, где нельзя ошибиться, — таких как возврат средств или подтверждение личности, — это не выдерживает ни аудита, ни решений об эскалации. Места, где нужно принимать решение, фиксируются снаружи в виде дерева-схемы, а агенту доверяется только формулировка слов.

Это не только для клиентской поддержки. Словарь дерева-схемы не привязан к домену, поэтому его можно использовать для любой работы, где процедуры выполняются по порядку.

  顧客の発話                      guidepost-mcp (MCP · 8127)
      │                    ┌──────────────────────────┐
  ┌───▼────────┐  values   │ flows/*.yaml ← 起動時に   │
  │  エージェント  ├──────────▶│   メモリ常駐(読むだけ)  │
  │             │◀──────────┤ engine = 純関数で遷移     │
  └────────────┘  next     │ SQLite = runs / steps    │
      │  発話をラベルに        └──────────┬───────────────┘
      │  落とすのはこちら側                │ 読み取り専用
      ▼                              ┌───▼──────────┐
   顧客へ返す                          │ Web UI (SSR)  │ いま樹形図のどこにいるか
                                     └───────────────┘

Интерпретация естественного текста в метки — на стороне агента. Сервер только смотрит на полученную метку и переходит дальше, поэтому вызовов LLM не прибавляется. Замеры (Raspberry Pi 5): вычисление перехода p95 0,006 мс, с SQLite p95 0,76 мс, сквозной путь через HTTP MCP p95 9,1 мс. Один ход голосового сопровождения занимает около 2,5 секунды, так что даже с HTTP это 0,4%. Разбивка и то, как закладывался этот бюджет, — в docs/research/voice-agent-latency-budget.md.

Как писать дерево-схему

flows/<flow_id>.yaml — это одно дерево-схема. Узлов всего четыре вида.

kind

Роль

Ветвление

ask

Задаёт один вопрос для проверки и ветвится по метке ответа

да

tell

Передаёт одну подсказку

нет

collect

Собирает несколько независимых пунктов в любом порядке

нет

end

Конечный узел. Содержит outcome

нет

id: payment_failed
title: 支払いが失敗した
entry: n_error_code
max_unmatched: 3          # 聞き直しの上限
max_branch_fanout: 3      # これより枝が多いと畳まない
branch_depth: 2           # 枝を辿って結末を探す深さ
on_unknown: broaden       # 分からないと言われたとき。broaden / escalate
on_stuck: 原因が絞れないため、決済窓口の担当者に引き継ぐ

nodes:
  - id: n_error_code
    kind: ask
    say: 決済画面に出ているエラーコードを確認する     # 逐語原稿ではなく「何を伝えるか」
    accepts:                                      # ラベル → そのラベルに落とす条件
      E01: カードが拒否された
      E02: 残高不足・限度額超過
    next:
      E01: n_card_age
      E02: n_balance
      __other__: n_symptom        # 想定外のラベルの逃がし先(任意)
      __unknown__: n_generic      # 分からないときの逃がし先(任意)

  - id: n_identity
    kind: collect
    say: 本人確認に必要な情報を集める
    on_unknown: escalate          # 重要な手続きなので畳ませない
    slots:
      order_id:
        ask: 注文番号を聞く
        required: true            # 埋まらないと進めない
      phone: 登録の電話番号を聞く   # 短い書き方(任意扱い)
    next: n_verify

  - id: n_resolved
    kind: end
    outcome: resolved
    say: 解消したことを確認し、対応を締める

say — это не дословный сценарий, а материал «что передать». Формулировку агент подстраивает под ситуацию. Держите 1 узел = 1 вопрос для проверки или 1 подсказка.

После написания — проверяйте. Недостижимые узлы и метки без адресата на момент написания работают, поэтому о них не узнаешь до момента выполнения.

uv run guidepost-mcp lint flows/          # CI でも回している
uv run guidepost-mcp show payment_failed --flows flows   # 樹形図を木で表示
uv run guidepost-mcp drafts               # 預かっている草案の一覧
uv run guidepost-mcp approve refund_request   # 草案を検査し直して flows/ へ移す
uv run guidepost-mcp discard refund_request   # 草案を捨てる

Related MCP server: sop-mcp

Инструменты MCP

Инструмент

Роль

guide_flows(category)

Без аргументов — список категорий и количество, с category — список флоу внутри неё

guide_start(flow_id, subject, agent)

Начинает run. Возвращает run_id + первый узел + индекс

guide_answer(run_id, choice, values, utterance)

Отправляет ответ. Возвращает одно из 5 состояний ниже

guide_state(run_id)

Текущее место, пройденный путь, собранные значения. Для восстановления и передачи

guide_revise(run_id, to_node, clear)

Вернуться назад. Отбрасывает исправленные значения

guide_close(run_id, outcome, reason)

Закрыть, не дойдя до конца

guide_draft(yaml)

Сдать дерево-схему как черновик. Возвращает замечания lint. В сопровождении не участвует

Выбор входа — в два этапа, регистрация дерева-схемы — только черновиком

Когда деревьев-схем становится много, вернуть все сразу нельзя, поэтому guide_flows() сначала возвращает категории (определены в flows/categories.yaml) и количество по каждой, а guide_flows(category=...) возвращает список флоу внутри категории. Если ни одна категория не подходит — не подгоняйте насильно, а ведите сопровождение без дерева-схемы.

guide_draft только принимает дерево-схему на хранение; оно не попадает ни в flows/, ни в guide_flows(). Оно становится официальным только после того, как человек пропустит его через guidepost-mcp approve. Генерация дерева-схемы — это работа вне этого сервера, и она не предполагается во время сопровождения (обоснование — в docs/adr/0011-draft-intake.md).

Ответы накапливаются, и автоматически продвигается настолько, насколько заполнено

Если клиент сказал сразу: «У меня E01, и карта трёхлетней давности», — не хочется переспрашивать по одному вопросу, когда ответы уже есть. Если передать в values всё, что известно, — продвижение идёт, пока заполнено, и останавливается на первом незаполненном узле.

  収集済み: {n_error_code: E01, n_card_age: over_1y}

  n_error_code ──E01──▶ n_card_age ──over_1y──▶ n_expiry_check ──?──▶ …
   ✓ 聞かずに通過        ✓ 聞かずに通過           ▲ ここで止まる

Пропущенные узлы возвращаются как skipped. Чтобы агент знал id дальнейших узлов, guide_start передаёт индекс (по одной строке о том, что спрашивает каждый узел / слот) только один раз.

Произвольный порядок в collect работает по тому же механизму. В каком бы порядке ни заполняли — узел проходится, как только всё собрано.

Не останавливаться на «не знаю»

То, что обратившийся человек не знает ответа, — обычное дело. Сколько ни переспрашивай — не появится.

   guide_answer
        ├─ ラベルが accepts にある ──────────▶ advanced / completed
        ├─ accepts に無い ──────────────────▶ unmatched(聞き直す)
        │                                       │ max_unmatched 回で下へ
        └─ choice="__unknown__" ──────────┐   │
                                          ▼   ▼
                               next.__unknown__ があるか
                                  ├─ ある ─▶ advanced(逃がし先へ)
                                  └─ 無い ─▶ on_unknown は
                                              ├─ escalate ─▶ stalled(有人へ)
                                              └─ broaden ──▶ 枝を畳めるか
                                                   ├─ できる ─▶ branched
                                                   └─ 無理 ───▶ stalled

branched — это состояние без фиксации ветки, когда рядом выводится итог каждой ветви. Возвращается материал для «Если E01 — обратитесь в компанию по карте, если E02 — проверьте остаток». Если есть узел, где все ветви сходятся, он попадает в common, и можно подвести итог: «В любом случае в конце — ○○».

Для процедур, где сопровождение с неясностью наносит вред, — таких как возврат средств или подтверждение личности, — напишите on_unknown: escalate, чтобы не сворачивать. Узлы, которые нельзя свернуть, lint называет поимённо, так что достаточно обработать только их.

Запуск

uv sync
uv run uvicorn guidepost_mcp.web:app --host 127.0.0.1 --port 8127
uv run python scripts/mcp_smoke.py            # 実プロトコルで 1 周辿る
uv run python scripts/mcp_smoke.py --parallel # 2 本の run を交互に進める

Web UI — на http://127.0.0.1:8127/. Там перечислены текущие run, а на /r/<run_id> на дереве-схеме цветом выделены текущее место, пройденный путь, узлы, пропущенные с опережением, и узлы со свёрнутыми ветвями.

Со стороны агента подключение идёт как HTTP MCP.

from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient(
    {
        "guidepost-mcp": {"url": "http://127.0.0.1:8127/mcp/", "transport": "streamable_http"},
    }
)
tools = await client.get_tools()

Версии

Официальный источник версии — один version в pyproject.toml, по SemVer (пока 0.x, в minor могут входить разрушающие изменения). История изменений — в CHANGELOG.md.

Что считается разрушающим изменением, определено в docs/adr/0009-versioning.md. Суть: YAML-схема дерева-схемы и контракт инструментов MCP (имена инструментов, аргументы, ключи возвращаемых значений, 5 значений status); текст инструкций в next не входит.

version в flows/*.yaml — это версия процедуры сопровождения и не связан с версией этой библиотеки.

Обоснование дизайна

Почему интерпретация меток — на стороне агента, почему ветви сворачиваются на «не знаю», почему Web UI сделан только для чтения — всё записано с обоснованием в docs/adr/ (список — в docs/adr/README.md). Материалы, которые послужили основой для решений, — в docs/research/.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides browser automation capabilities using Playwright's accessibility tree, enabling LLMs to interact with web pages through structured data without screenshots or vision models. It's designed for specialized agentic loops that benefit from persistent state and iterative reasoning over page structure.
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to execute multi-step Standard Operating Procedures step by step, with enforcement of completion at each step, making LLM behavior predictable and auditable.
    5
    3
    Apache 2.0