Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for: - projects-meta-mcp v2.25.0 (TypeScript/Node) - wiki-graph v0.3.1 (TypeScript/Node) - interns-mcp v0.3.3 (Python/FastMCP) .gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc) bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
projects-meta-mcp — Design
Date: 2026-04-29 Status: Approved (брейнсторм завершён, спека к ревью) Owner: vitya
Цель
Дать Claude (и любому MCP-клиенту) единый, дешёвый и offline-устойчивый доступ к двум видам мета-информации, разбросанным по множеству проектов и нескольких машин:
- Статусы задач — свод
.tasks/STATUS.mdвсех проектов в Gitea. - Общие знания — поиск и чтение страниц общей вики (
projects-wiki), отфильтрованных по домену текущего проекта.
Не цели (явный YAGNI)
- ❌ Удалённый MCP-процесс на Synology (HTTP/SSE-транспорт). Откладывается до момента, когда «truly always-on» станет реальной болью.
- ❌ Карантин
_pending/для projects-wiki. Решение по промоушену принимается в моменте. - ❌ Sparse-checkout всех проектов на каждую машину. API + кэш проще.
- ❌ Серверный агрегатор / git-hook на стороне Gitea. Клиентский sync проще.
- ❌ Embedding/семантический поиск projects-wiki. Достаточно поиска по тексту/тегам.
- ❌ Управление зависимостями между проектами, GANTT, deadlines. Только статусы.
Архитектура
┌──────────────────┐
│ Gitea (Synology)│
│ - все проекты │
│ - projects-wiki │
│ - projects-meta-│
│ mcp (этот код)│
└────────┬─────────┘
│ pull / API
▼
┌────────────────── каждая машина ─────────────────┐
│ │
│ ~/projects/.wiki/ ← git clone │
│ ~/.cache/projects-mcp/ ← JSON-кэш STATUS │
│ │
│ sync-script (cron / руками): │
│ - git pull в projects-wiki │
│ - Gitea API → собрать STATUS.md → JSON-кэш │
│ - offline → no-op, кэш остаётся │
│ │
│ mcp-server (локальный, stdio): │
│ Tools: │
│ tasks.aggregate() │
│ tasks.search(q) │
│ knowledge.search(q, domain?) │
│ knowledge.get(slug) │
│ knowledge.suggest_promote() │
└──────────────────────────────────────────────────┘
Принцип: MCP в hot path читает только локальные файлы. Сетевые вызовы — только в sync-script.
Компоненты
1. projects-wiki — отдельный git-репозиторий на Gitea
Имя репо: projects-wiki. Клонируется на каждую машину под ~/projects/.wiki/.
Структура:
~/projects/.wiki/
├── CLAUDE.md ← правила промоушена + список доменов
├── README.md
├── cross/ ← кросс-доменные знания (git, vscode, claude-code, общие гочи)
├── node/ ← Node.js / TypeScript / npm / yarn
├── embedded/ ← микроконтроллеры, PlatformIO, STM32, Arduino
├── web/ ← фронтенд, браузер, HTTP, Nginx
└── ... ← новые домены добавляются по мере появления проектов
Frontmatter каждой страницы:
---
title: Windows yarn requires exec()
domain: node # обязательное: node | embedded | web | cross | ...
shared_at: 2026-04-29 # дата промоушена
applies_to: [windows] # опц. — платформенные ограничения
source_project: npm-mcp # опц. — откуда промоутили
---
Тело — стандартный concept: Why + Когда / Как чинить.
projects-wiki/CLAUDE.md содержит:
- Полный список валидных доменов и что в каждый кладётся.
- Эвристику промоушена (см. ниже).
- Чёрный список (имена проектов, бизнес-доменов — никогда не упоминаются в shared).
2. sync-script
Bash или Node-скрипт. На каждой машине, запускается cron'ом / руками / git-хуком.
Шаги:
-
Pull projects-wiki:
git -C ~/projects/.wiki pull --quietOffline → silently fail (exit 0), кэш не трогается.
-
Скачать список репозиториев пользователя через Gitea API:
GET https://git.kzntsv.site/api/v1/users/OpeItcLoc03/repos?limit=50С токеном (в
~/.config/projects-mcp/auth.toml). -
Параллельно (limit 10) скачать
STATUS.mdкаждого:GET https://git.kzntsv.site/api/v1/repos/OpeItcLoc03/{repo}/raw/.tasks/STATUS.md404 → у проекта нет тасок, пропустить. Сетевая ошибка → пропустить, оставить старое значение в кэше.
-
Собрать в
~/.cache/projects-mcp/tasks.jsonатомарно (write totasks.json.tmp, rename):{ "synced_at": "2026-04-29T12:00:00Z", "synced_from": "https://git.kzntsv.site", "machine": "vitya-desktop", "projects": [ { "name": "npm-mcp", "default_branch": "main", "fetched_at": "2026-04-29T12:00:01Z", "active_tasks": [ {"slug": "wiki-self-ingest", "status": "active", "next": "..."} ], "all_tasks_count": 5, "raw": "<полный STATUS.md>" } ], "errors": [{"project": "books", "reason": "network"}] } -
Exit 0 даже при частичных ошибках. Логи в
~/.cache/projects-mcp/sync.log.
3. mcp-server
Stateless Node-процесс, stdio-транспорт MCP. Читает только локальные файлы. Не делает сетевых вызовов.
Tool surface
| Tool | Аргументы | Возвращает |
|---|---|---|
tasks.aggregate() |
(filter?: { status?, project? }) |
Список активных задач по всем проектам. Только мета (slug, project, status, next-action), не полный текст. |
tasks.search(query) |
query: string |
Поиск по STATUS.md-кэшу (substring + per-task title). |
tasks.get(project) |
project: string |
Полный STATUS.md одного проекта. |
knowledge.search(query, opts?) |
query: string, opts?: { domain?: string|"all", limit?: number } |
До 10 заголовков + 1-строчное описание. По умолчанию фильтр зависит от detected domain текущего проекта (см. ниже). |
knowledge.get(slug) |
slug: string (например node/windows-yarn-exec) |
Полная страница (frontmatter + тело). |
knowledge.suggest_promote() |
() |
Возвращает кандидатов на промоушен из текущего проекта. См. логику ниже. |
meta.status() |
() |
Возвращает диагностику: когда был последний sync, кэш-stale-возраст, сколько проектов, сколько shared-страниц. |
Two-step паттерн
search всегда возвращает только индекс (заголовок + 1 строка). get возвращает полный текст. Минимизирует токены — модель сама решает что углублять.
Detect domain текущего проекта
При запуске MCP получает cwd от клиента. Алгоритм:
- Если в
cwdестьpackage.json→node. - Если есть
platformio.ini/*.ino/CMakeLists.txtс упоминаниемarm-none-eabiили embedded SDK →embedded. - Если есть
next.config.*/vite.config.*/index.htmlбезpackage.json(статика) →web. - Иначе →
unknown.
Логика фильтрации knowledge.search:
- Detected domain — конкретный (
node/embedded/web) → показываемdomain == detected || domain == "cross". - Detected domain
unknown→ фильтра нет (видны все домены). - Опциональный аргумент
opts.domainпользователя/Claude переопределяет:"all"снимает фильтр явно, конкретное имя меняет таргет.
cross в frontmatter — это тег страницы (универсальное знание). Detected unknown — это режим поиска (фильтра нет). Не путать.
Логика выносится в отдельный модуль domain-detector (тестируется юнит-тестами).
knowledge.suggest_promote()
Логика:
- Прочесть все
concepts/*.mdв<cwd>/.wiki/concepts/. - Для каждого применить эвристику:
- Включить как кандидата, если страница: упоминает платформу/тулинг (Windows, yarn, Node, MCP, git, Docker), не упоминает доменные имена из чёрного списка
projects-wiki/CLAUDE.md. - Усилить сигнал, если в
projects-wikiесть страница с близким заголовком в другом домене (= знание подтверждено повторно).
- Включить как кандидата, если страница: упоминает платформу/тулинг (Windows, yarn, Node, MCP, git, Docker), не упоминает доменные имена из чёрного списка
- Вернуть список кандидатов:
{ slug, current_path, suggested_domain, confidence, reason }. - Решение принимает пользователь (Claude в чате предлагает строкой
_Кандидат на shared: ... Промоутить?_, ждётy/n).
Промоушен ≠ автомат. Tool только возвращает кандидатов, ничего не пишет.
4. Bootstrap
На новой машине (пример Windows + Git Bash; на других ОС адаптировать пути):
# один раз:
GITEA=https://git.kzntsv.site
OWNER=OpeItcLoc03
git clone $GITEA/$OWNER/projects-meta-mcp ~/.local/projects-meta-mcp
cd ~/.local/projects-meta-mcp && npm install && npm run build
git clone $GITEA/$OWNER/projects-wiki ~/projects/.wiki
mkdir -p ~/.config/projects-mcp
cp ~/.local/projects-meta-mcp/auth.toml.example ~/.config/projects-mcp/auth.toml
# отредактировать auth.toml — заполнить gitea_token
node ~/.local/projects-meta-mcp/dist/sync.js # первый sync
# зарегистрировать MCP в claude code config (см. ниже)
Формат auth.toml:
gitea_url = "https://git.kzntsv.site"
gitea_user = "OpeItcLoc03"
gitea_token = "..." # personal access token, scope: read:repository
Регистрация в Claude Code (~/.claude.json или per-project) — пути в Windows-формате с прямыми слэшами:
{
"mcpServers": {
"projects-meta": {
"command": "node",
"args": ["C:/Users/vitya/.local/projects-meta-mcp/dist/server.js"]
}
}
}
Поведение в offline
| Ситуация | Что работает | Что не работает |
|---|---|---|
| Сеть есть, синк свежий | Всё | — |
| Сеть есть, sync не запускался N часов | Всё (кэш отдаёт старое) | Свежесть других машин |
| Сеть пропала | tasks.*, knowledge.search/get — всё работает |
knowledge.suggest_promote работает; git push после промоушена откладывается |
| Кэш отсутствует (новая машина без bootstrap) | Только meta.status() (отдаёт diagnostic с пустым кэшем) |
Всё остальное |
meta.status() всегда возвращает возраст кэша, чтобы Claude мог предупредить пользователя «данные старше 24 часов».
Авторизация
- Gitea API token: scope
read:repository(иwrite:repositoryдля пуша projects-wiki). - Хранится в
~/.config/projects-mcp/auth.toml(gitignore'd). - MCP-сервер сам не читает токен — только
sync-script. MCP оперирует уже скачанными локальными файлами.
Тестирование
- Unit:
domain-detector, парсерSTATUS.md, эвристика промоушена. - Integration: sync-script против локального Gitea (Docker fixture). Проверка: rate limit, 404 на отсутствующий
STATUS.md, частичный отказ. - MCP smoke test: запустить сервер, дёрнуть каждый tool через MCP-клиент-fixture.
Открытые вопросы (не блокеры)
- Cron vs git-hook vs руками — определится при первом использовании. Стартовать с «руками + npm script», добавить cron когда станет лень.
- Уведомление о stale-кэше — порог в часах? Стартовать с 24, корректировать.
- Multi-user — пока однопользовательский (
OpeItcLoc03). Если кто-то ещё начнёт пользоваться — переделатьusers/{owner}/reposв параметр изauth.toml. projects-wikiGC — раз в полгода аудит, какие страницы не использовались. Будет отдельный toolmeta.audit_shared(months)в v2.
Дальнейшие шаги
- Создать репозиторий
projects-meta-mcpна Gitea, запушить этот скелет. - Создать репозиторий
projects-wikiна Gitea с минимальной структурой (CLAUDE.md,cross/,node/). - Перейти к фазе writing-plans: расписать пошаговый план реализации (sync-script сначала, MCP-сервер вторым, эвристика промоушена третьим).