- Go 99.7%
- Dockerfile 0.3%
| cmd | ||
| docs | ||
| internal | ||
| .dockerignore | ||
| .gitignore | ||
| .golangci.yml | ||
| .woodpecker.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
MCP Semantic Task Planner
MCP-сервер для управления задачами AI-агентов с построением планов по зависимостям (DAG), семантическим (векторным) поиском и привязкой к фрагментам документации.
Два режима работы:
- local — однопользовательский: работает с
./docs/roadmap.json(stdio или HTTP) - shared — многопользовательский multi-tenant: register/unregister сессий, Lazy Load, fsnotify, раздельные Control и Data Plane
Возможности
- 4 инструмента (
search_tasks,get_tasks,upsert_task,delete_task) с полнымinputSchema,enumдля статусов,requiredдля обязательных полей - Планирование через directed acyclic graph (DAG): зависимости задач, проверка циклов,
get_tasks(status: "ready")для выбора следующей задачи - Три режима просмотра: плоский список (
full), дерево зависимостей (tree), цепочка родителей до корня (ancestors) - Семантический (векторный) поиск с настраиваемым порогом
min_score(через OpenAI-совместимый embedding API) + text fallback compact=true— режим краткого вывода без длинных полей (description, doc_references и т.д.)- Пагинация (
limit+offset) вget_tasksиsearch_tasks - Состояния задач:
pending → in_progress ⇄ on_hold → completed / failed
Быстрый старт (local mode)
Требования
- Go 1.26+
- Embedding-сервер (совместимый с OpenAI API
/v1/embeddings) — опционально, без него поиск работает по точному совпадению
Запуск
# из исходников (stdio — для подключения к MCP-агенту)
go run ./cmd/mcp-semantic-task-planner
# HTTP вместо stdio
go run ./cmd/mcp-semantic-task-planner --transport http
# с портом 9090
go run ./cmd/mcp-semantic-task-planner --transport http --port 9090
# с embedding-сервером
EMBEDDING_URL=http://localhost:8000 go run ./cmd/mcp-semantic-task-planner
Сборка
# обычная
go build -o mcp-semantic-task-planner ./cmd/mcp-semantic-task-planner
# с версией
go build -ldflags "-X git.ymnuktech.ru/ymnuk/mcp-semantic-task-planner/internal/version.Version=v0.1.0" \
-o mcp-semantic-task-planner ./cmd/mcp-semantic-task-planner
Подключение к MCP-агентам (Cline, Roo, Claude Desktop)
mcp.json:
{
"mcpServers": {
"task-planner": {
"command": "/path/to/mcp-semantic-task-planner",
"args": ["--mode", "local", "--transport", "stdio"],
"env": {
"EMBEDDING_URL": "http://localhost:8000"
}
}
}
}
Для разработки:
{
"mcpServers": {
"task-planner": {
"command": "go",
"args": ["run", "./cmd/mcp-semantic-task-planner", "--mode", "local", "--transport", "stdio"]
}
}
}
Архитектура
┌──────────────────────────────────┐
│ HTTP Router │
│ (http.ServeMux + middleware) │
│ │
│ /mcp ────────────────────────────│───▶ Control Plane
│ /mcp/{session_id} ──────────────│───▶ Data Plane
│ GET /health ────────────────────│───▶ JSON health
│ GET /metrics ───────────────────│───▶ Prometheus
└──────────────────────────────────┘
│
┌────────────────┼──────────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ Control MCP Server │ │ Session MCP Server │
│ (один на весь сервер) │ │ (свой для каждой │
│ │ │ зарегистрированной │
│ • register │ │ сессии) │
│ • unregister │ │ │
│ • list │ │ • search_tasks │
└─────────────────────────┘ │ • get_tasks │
│ • upsert_task │
│ • delete_task │
└─────────────────────────┘
│
▼
┌─────────────────────┐
│ session.Session │
│ • Load / Unload │
│ • SearchTasks │
│ • UpsertTask │
│ • DAG (deps) │
│ • fsnotify │
│ • State Machine │
└─────────────────────┘
Режимы
| local | shared | |
|---|---|---|
| Путь к плану | ./docs/roadmap.json (жёстко) |
--path-root + путь из register |
| БД | SQLite (./docs/planner.db) |
SQLite (--db-path) |
| Source of Truth | roadmap.json (Git-friendly) |
roadmap.json (Git-friendly) |
| Lazy Load | Нет (всегда Loaded) | Да (Loaded ↔ Unloaded) |
| fsnotify | Да | Per-session |
| Сессии | Одна, ID="local" |
Множественные |
| Мониторинг | GET /health, GET /metrics |
GET /health, GET /metrics |
| Транспорт | stdio (default) или http | Только http |
Инструменты MCP
| Инструмент | local | shared | Эндпоинт (shared) | Описание |
|---|---|---|---|---|
search_tasks |
да | да | POST /mcp/{session_id} |
Поиск задач по тексту (query обязателен). Векторный поиск при min_score > 0 + embedding сервер, иначе text fallback. Результат: {results: [{task, score}]} |
get_tasks |
да | да | POST /mcp/{session_id} |
Получение задач. view: full (список), tree (дерево от task_id), ancestors (цепочка родителей от task_id). compact=true — краткий вывод |
upsert_task |
да | да | POST /mcp/{session_id} |
Создание/обновление задачи. task_id пустой → авто-генерация. Статусы: pending, in_progress, completed, failed, on_hold |
delete_task |
да | да | POST /mcp/{session_id} |
Soft-delete (archived=true, можно восстановить через upsert_task с archived:false) |
register |
— | да | POST /mcp |
Создать сессию для проекта (path обязателен) |
unregister |
— | да | POST /mcp |
Удалить сессию, выгрузка из RAM |
list |
— | да | POST /mcp |
Список активных сессий |
Режимы просмотра (get_tasks view)
| Значение | Поведение | task_id |
|---|---|---|
"" / "full" |
Плоский список с фильтрацией по status, limit, offset |
не требуется |
"tree" |
Рекурсивное дерево: кто зависит от этой задачи (fan-out) | требуется |
"ancestors" |
Сама задача + цепочка родителей до корня в BFS-порядке (fan-in). Полезно понять, почему задача не в ready |
требуется |
Компактный режим (compact: true)
При compact=true поля description, doc_references, prompt_template, result_summary, created_at, updated_at опускаются из результата. Работает для всех значений view: full, tree, ancestors.
// compact: false (по умолчанию)
{"id":"t1","name":"Setup DNS","status":"completed","description":"Configure SPF and DKIM","created_at":"2026-06-05T10:00:00Z",...}
// compact: true
{"id":"t1","name":"Setup DNS","status":"completed"}
Семантический поиск (search_tasks min_score)
При наличии embedding-сервера (EMBEDDING_URL) search_tasks выполняет векторный поиск по косинусной близости:
// поиск с порогом 0.7
{"query":"настроить DNS","min_score":0.7}
// результат с баллами
{"results": [
{"task":{"id":"t1","name":"Setup DNS",...}, "score":0.85},
{"task":{"id":"t2","name":"Configure MX",...}, "score":0.72}
]}
Параметры:
min_score: 0.0(или omit) — используется--min-scoreиз конфига (по умолчанию0.4)min_score: 0.6— явный порог- Если embedding сервер недоступен — автоматический fallback на текстовый поиск (substring match)
Пагинация
limit + offset работают в search_tasks и get_tasks(view: "full"):
{"query":"DNS","limit":10,"offset":20}
State Machine (shared mode)
[Start] → register() → StateLoaded (RAM + fsnotify + roadmap)
│
(IDLE_TIMEOUT)
▼
StateUnloaded (RAM cleared, fsnotify stopped)
│
(incoming tool call)
▼
Lazy Load → read roadmap.json → StateLoaded
Статусы задач
pending ──→ in_progress ──→ completed
│ ↑
│ │
└─────→ on_hold
└─────→ failed
pending— задача создана, ожидает выполненияin_progress— выполняетсяon_hold— отложена (не блокирует dependants для ready, не возвращается вreadyчерезGetReadyTasks)completed— выполнена (разблокирует dependants)failed— не может быть выполнена
Конфигурация
CLI-флаги
| Флаг | По умолчанию | Описание |
|---|---|---|
--mode |
local |
local или shared |
--transport |
stdio |
stdio или http |
--port |
8085 |
HTTP порт |
--path-root |
./docs |
Корень для roadmap.json сессий (local: ./docs/roadmap.json) |
--db-path |
./data/cache.db |
Файл SQLite (для local по умолчанию ./docs/planner.db) |
--idle-timeout |
30m |
Таймаут простоя сессии (shared) |
--embedding-url |
http://localhost:8080 |
URL embedding-сервера |
--embedding-model |
text-embedding-3-small |
Модель эмбеддингов |
--llm-api-key |
`` | API-ключ для embedding-сервера |
--min-score |
0.4 |
Порог схожести по умолчанию (0.1–1.0) |
Все флаги также поддерживаются через переменные окружения (например, MODE=shared, EMBEDDING_URL=http://...).
Docker
# Сборка
docker build --build-arg VERSION=v0.1.0 -t mcp-semantic-task-planner .
# Запуск (local mode, HTTP)
docker run -p 8085:8085 \
-v ./docs:/docs \
mcp-semantic-task-planner -- --transport http
# Запуск (shared mode)
docker run -p 8085:8085 \
-v ./projects:/projects \
mcp-semantic-task-planner -- \
--mode shared --transport http
Docker Compose
# Запуск с embedding-сервером
docker compose up
Тесты
# unit + integration
go test -race ./...
# с покрытием
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
Demo
# генерация 84 задач с DAG, дерево, поиск, on_hold
go run ./cmd/demo
Скрипт:
- Создаёт 84 задачи по 11 этапам с cross-stage fan-in зависимостями
- Показывает дерево этапов 6 и 10
- Выполняет поиск по тексту
- Завершает Этап 0 и показывает новые ready
- Откладывает Этап 4 (on_hold) и показывает, что ready сократились
- Выводит задачи со статусом on_hold
Промпты MCP
Сервер регистрирует MCP-промпты, доступные через prompts/list и prompts/get. Они делятся на статические (инструкции) и динамические (текущее состояние задач).
agent-instructions
Статический промпт с полной инструкцией по использованию data-plane инструментов. Доступен в local mode и в каждой data-plane сессии shared mode.
prompts/get("agent-instructions") → полный текст инструкции (см. ниже)
session-management
Статический промпт для управления сессиями в shared mode. Доступен только на control plane (эндпоинт /mcp).
prompts/get("session-management") → инструкция по register/unregister/list
Содержимое:
Для управления сессиями в shared режиме используй MCP-инструменты сервера
mcp-semantic-task-planner-control.Этот сервер работает в multi-tenant режиме: каждая сессия — изолированное рабочее пространство, привязанное к директории проекта. Все data-plane инструменты (
search_tasks,get_tasks,upsert_task,delete_task) вызываются через конкретную сессию.Управление сессиями
register(path: "project-a", description: "optional description")— создать сессию. Сервер вернётsession_id. Сохрани его для последующих вызовов.unregister(session_id: "...")— закрыть сессию и освободить ресурсы (RAM, fsnotify, idle timer).list— показать все активные сессии (id, path, state).После регистрации используй полученный
session_idдля data-plane вызовов через эндпоинт/mcp/{session_id}. Внутри сессии доступны инструменты:search_tasks,get_tasks,upsert_task,delete_task. Полное описание этих инструментов — в промптеagent-instructionsвнутри сессии.Обработка ошибок Если сессия не найдена — проверь
session_idчерез list и зарегистрируй заново при необходимости.Завершение работы После завершения работы с проектом всегда вызывай
unregister, чтобы освободить ресурсы сервера.
task-status (динамический)
Сводка по проекту: общее количество задач, разбивка по статусам, N первых ready задач. Помогает агенту быстро оценить состояние, не вызывая get_tasks.
prompts/get("task-status", {limit: "5"})
**Сводка проекта**
Всего: 34 задачи
Ready: 5 ← можно брать в работу
In progress: 3
Completed: 21
Failed: 1
On hold: 4
**Ближайшие ready (5):**
t1 setup-dns pending
t2 configure-ssl pending
t3 deploy-staging pending
t4 smoke-tests pending
t5 load-tests pending
Аргументы:
| Аргумент | Тип | По умолчанию | Описание |
|---|---|---|---|
limit |
string (int) | "5" |
Сколько ready задач показать в сводке |
ready-tasks (динамический)
Плоский пагинируемый список задач, готовых к выполнению (все зависимости completed, статус pending, не on_hold). Позволяет агенту выбирать следующую задачу без вызова get_tasks(status: "ready").
prompts/get("ready-tasks", {limit: "10", offset: "0", compact: "true"})
**Ready tasks (страница 1 из 3, всего 25):**
t1 setup-dns pending
t2 configure-ssl pending
t3 deploy-staging pending
Аргументы:
| Аргумент | Тип | По умолчанию | Описание |
|---|---|---|---|
limit |
string (int) | "10" |
Сколько ready задач вернуть |
offset |
string (int) | "0" |
Смещение для пагинации |
compact |
string (bool) | "false" |
"true" — только id, name, status |
blocked-tasks (динамический)
Задачи со статусом pending, у которых есть незавершённые зависимости (не ready). Помогает агенту понять, что тормозит прогресс и какие зависимости нужно разблокировать.
prompts/get("blocked-tasks", {limit: "10", offset: "0"})
**Blocked tasks (страница 1 из 2, всего 12):**
t15 deploy-prod ждёт: setup-dns, configure-ssl
t20 load-test ждёт: deploy-staging
t21 monitoring-setup ждёт: deploy-staging
Аргументы:
| Аргумент | Тип | По умолчанию | Описание |
|---|---|---|---|
limit |
string (int) | "10" |
Сколько заблокированных задач вернуть |
offset |
string (int) | "0" |
Смещение для пагинации |
Пример использования (для MCP-агентов)
Рекомендуемый рабочий промпт для системной инструкции агента. Содержимое также доступно через prompts/get("agent-instructions"):
Для работы с задачами используй MCP-инструменты сервера
mcp-semantic-task-planner:Обзор 0. Начни с
get_tasks(status: "all")— посмотреть все задачи (статусы, прогресс, зависимости). Или сразуget_tasks(status: "ready")— увидеть, что готово к выполнению.
get_tasks(status: "all")— посмотреть все задачи (статусы, прогресс).get_tasks(status: "ready")— задачи, готовые к выполнению (все зависимости completed, статус pending, не on_hold).Создание задач 3. Перед созданием новой задачи через
upsert_taskиспользуйsearch_tasks, чтобы убедиться, что такая задача ещё не существует. 4.upsert_task(name: "...", description: "...")— создать новую задачу (ID сгенерируется из названия). Статус по умолчаниюpending. 5.upsert_task(name: "...", dependencies: ["parent-task-1", "parent-task-2"])— создать задачу с зависимостями.Управление статусами 6.
upsert_task(task_id: "...", status: "in_progress")— начать выполнение. 7.upsert_task(task_id: "...", status: "completed", result_summary: "...")— завершить (разблокирует dependants). 8.upsert_task(task_id: "...", status: "on_hold")— отложить (не блокирует dependants, задача уходит из ready). 9.upsert_task(task_id: "...", status: "failed", result_summary: "...")— отметить как невыполнимую.Редактирование 10. При использовании
upsert_taskпередавай только изменяемые поля, чтобы случайно не затереть существующие данные (например, не отправляй пустой description, если меняешь только status). 11.upsert_task(task_id: "...", dependencies: ["new-dep-1"])— добавить/изменить зависимости существующей задачи. 12.upsert_task(task_id: "...", dependencies: [])— убрать все зависимости (задача станет ready, если статус pending). 13.upsert_task(task_id: "...", name: "...", description: "...")— изменить название или описание.Диагностика блокировок 14.
get_tasks(task_id: "...", view: "ancestors")— цепочка родителей до корня: понять, какие зависимости ещё не выполнены. 15.get_tasks(task_id: "...", view: "tree", compact: true)— дерево dependants: понять, кого затронет изменение статуса (компактно). 16.search_tasks(query: "...")илиsearch_tasks(query: "...", min_score: 0.6)— найти задачи по смыслу.Удаление и восстановление 17.
delete_task(task_id: "...")— soft-delete (archived). Задача скрыта, но данные сохранены. 18.get_tasks(status: "all", include_archived: true)— посмотреть архив. 19.upsert_task(task_id: "...", archived: false)— восстановить из архива.Работа с большим количеством задач 20.
get_tasks(status: "ready", limit: 5, compact: true)— первые 5 готовых задач в кратком виде. 21. Используйcompact: trueкогда нужно только id, name, status, dependencies без описаний и дат. 22. Для параллельной работы — каждая готовая задача изget_tasks(status: "ready")может выполняться в отдельном subagent. 23. После каждого изменения проверяйget_tasks(status: "ready")— возможно, появились новые готовые.Обработка ошибок 24. Если MCP-инструмент возвращает ошибку, проанализируй сообщение об ошибке, исправь параметры запроса и повтори попытку. Не игнорируй ошибки.
Shared mode 25. Если сервер запущен в режиме
shared, предварительно зарегистрируй сессию черезregister(path: "..."), получи session_id и используй его для всех последующих data-plane вызовов. По окончании —unregister.
Мониторинг
При HTTP-транспорте доступны:
GET /health— JSON со статусом, режимом, uptime, количеством сессий, версиейGET /metrics— Prometheus-метрики (mcp_http_requests_total,mcp_http_request_duration_seconds,mcp_active_sessions+ Go runtime)
Лицензия
MIT