Factograph MCP
by megaumnick
README.md
# 🧠 Factograph MCP v4
Граф знаний с файловым вводом и AI-обогащением через локальный Ollama (Qwen3:14b) или Anthropic API.
**Архитектура:** MCP-сервер живёт на Ubuntu-сервере, читает файлы локально, а LLM-инференс уходит по сети на Windows-машину с видеокартой (Ollama).
---
## Что нового в v4
Три изменения, сделанные по итогам реальной сессии в `mcphost`:
1. **Файлы без расширения больше не отбрасываются.** Раньше `ingest_file`/`ingest_directory`/`list_server_files` помечали такие файлы как `supported: false`. Дампы технической документации (например `install core`, `multiapn` без `.txt`) теперь читаются как обычный текст автоматически — расширение не обязательно.
2. **Новый инструмент `auto_link_collection`** — массовое связывание документов одним вызовом. Решает конкретный сценарий: пользователь просит *"установи связи между всеми документами"*, а модели нужно было самой спланировать цикл "обогатить каждый → сравнить" — задача, с которой 14B-модель не справлялась. Теперь это один детерминированный вызов на сервере.
3. **Описания `enrich_document`/`find_related` обновлены** — явно указывают, что для массового связывания нужно использовать `auto_link_collection`, а не вызывать их в цикле по одному документу.
### Если у тебя 0 связей после импорта
Это не баг и не "функция не поддерживается" (как иногда отвечает модель) — это значит, что у документов ещё не извлечены сущности, а без них Jaccard-сравнению просто не с чем работать. Раньше для этого нужно было вызвать `enrich_document` на каждый документ вручную; теперь `auto_link_collection` делает это сама перед расчётом связей.
---
## Установка на Ubuntu Server
```bash
git clone https://github.com/megaumnick/factograph-mcp # или просто распакуй архив
cd factograph-mcp
npm install
npm run build
cp .env.example .env
nano .env # вписать IP Windows-машины и разрешённые папки
```
### Зависимости для PDF/DOCX (опционально)
`pdf-parse` и `mammoth` лежат в `optionalDependencies` — если `npm install` их не поставил:
```bash
npm install pdf-parse mammoth
```
---
## Настройка Ollama на Windows 11
1. Установи Ollama, стяни модель:
```powershell
ollama pull qwen3:14b
```
2. **Разреши сетевой доступ** (по умолчанию Ollama слушает только localhost):
Через переменную окружения перед запуском —
```powershell
$env:OLLAMA_HOST = "0.0.0.0:11434"
ollama serve
```
Или навсегда: Win+R → `sysdm.cpl` → Advanced → Environment Variables →
добавить `OLLAMA_HOST=0.0.0.0:11434` → перезапустить Ollama.
3. **Открой порт в Windows Firewall**:
```powershell
New-NetFirewallRule -DisplayName "Ollama" -Direction Inbound -LocalPort 11434 -Protocol TCP -Action Allow
```
4. Узнай IP машины: `ipconfig` → IPv4 Address (например `192.168.1.50`).
5. Проверь с Ubuntu-сервера:
```bash
curl http://192.168.1.50:11434/api/tags
```
Должен вернуться список моделей.
Либо тем же самым через MCP-инструмент:
```
ping_ollama {}
```
---
## Установка mcphost (хост на Ubuntu Server)
> **Важно:** разработка `mcphost` остановлена, репозиторий заархивирован автором — проект пометили как замороженный (без новых фич и фиксов), а преемником назван `Kit` (тот же автор, более новая архитектура). Для текущей задачи `mcphost` всё ещё рабочий вариант — последний релиз стабилен и именно его используют примеры в этом README — но если в будущем что-то перестанет собираться или захочется новых возможностей, стоит посмотреть на `Kit` в репозиториях `mark3labs` на GitHub.
### 1. Установи Go (если ещё нет)
```bash
sudo apt update
sudo apt install golang-go
go version # проверка
```
### 2. Установи mcphost
```bash
go install github.com/mark3labs/mcphost@latest
```
Бинарник ставится в `~/go/bin`. Добавь эту папку в `PATH`, если её там нет:
```bash
echo 'export PATH=$PATH:~/go/bin' >> ~/.bashrc
source ~/.bashrc
mcphost --help # проверка, что бинарник нашёлся
```
Альтернатива без сборки — скачать готовый бинарник со страницы [Releases](https://github.com/mark3labs/mcphost/releases) под свою архитектуру, не устанавливая Go вовсе.
### 3. Настрой подключение к Ollama для самого mcphost
Это **отдельная** переменная окружения от той, что в `.env` нашего MCP-сервера — `mcphost` сам общается с Ollama напрямую для самого чата с моделью, а `.env` факторграфа отвечает только за то, как `enrich_document`/`synthesize_cluster` достают Ollama. Их обычно указывают на один и тот же адрес, но задаются они раздельно:
```bash
export OLLAMA_HOST=http://192.168.1.50:11434
```
Для других провайдеров (если когда-нибудь понадобятся) — `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY` тем же способом.
### 4. Создай конфиг `~/.mcp.json`
```json
{
"mcpServers": {
"factograph": {
"command": "node",
"args": ["/home/user/factograph-mcp/dist/index.js"],
"env": {
"OLLAMA_HOST": "http://192.168.1.50:11434",
"OLLAMA_MODEL": "qwen3:14b",
"ALLOWED_ROOTS": "/home/user/documents:/mnt/data",
"AUTO_SAVE_PATH": "/home/user/.document-pinboard/graph.json"
}
}
}
}
```
### 5. Запусти
```bash
mcphost --model ollama:qwen3:14b --config ~/.mcp.json
```
При старте в логе должно появиться что-то вроде `Loaded 25 tools from MCP servers` — если число другое, проверь, не упал ли factograph-сервер при старте (смотри stderr — там пишет `[factograph-mcp] запущен`).
---
## Конфигурация `.env`
```bash
OLLAMA_HOST=http://192.168.1.50:11434
OLLAMA_MODEL=qwen3:14b
# Папки, из которых MCP разрешено читать файлы (через :)
ALLOWED_ROOTS=/home/user/documents:/mnt/data
# Авто-сохранение графа на диск при старте/остановке сервера
AUTO_SAVE_PATH=/home/user/.document-pinboard/graph.json
```
Если `OLLAMA_HOST` не задан, но задан `ANTHROPIC_API_KEY` — сервер автоматически переключится на Claude API как fallback.
---
## Подключение к Claude Desktop
Если хочешь использовать тот же MCP-сервер не через `mcphost`, а из Claude Desktop:
```json
{
"mcpServers": {
"factograph": {
"command": "node",
"args": ["/home/user/factograph-mcp/dist/index.js"],
"env": {
"OLLAMA_HOST": "http://192.168.1.50:11434",
"OLLAMA_MODEL": "qwen3:14b",
"ALLOWED_ROOTS": "/home/user/documents:/mnt/data",
"AUTO_SAVE_PATH": "/home/user/.document-pinboard/graph.json"
}
}
}
}
```
Конфиг для `mcphost` (`~/.mcp.json`) — смотри раздел «Установка mcphost» выше, формат `mcpServers` идентичен.
---
## Все инструменты (25 штук)
### Слой 1 — Пинборд (8)
`pin_document` · `list_pins` · `search_pins` · `get_pin` · `update_pin` · `unpin` · `list_collections` · `export_collection`
### Слой 2 — Граф знаний (10)
`link_documents` · `get_connections` · `traverse_graph` · `enrich_document` · `find_by_entity` · `find_related` · **`auto_link_collection`** · `get_facts` · `synthesize_cluster` · `graph_stats`
### Слой 3 — Файлы на сервере + диск-хранилище (7)
| Инструмент | Описание |
|---|---|
| `ingest_file` | Прочитать файл (PDF/DOCX/код/текст, в т.ч. без расширения) и добавить в базу |
| `ingest_directory` | Массовый импорт папки (рекурсивно, с фильтром расширений) |
| `list_server_files` | Посмотреть содержимое директории на сервере |
| `ping_ollama` | Проверить связь с Windows-машиной и список моделей |
| `save_graph` | Сохранить весь граф (узлы+связи+факты) в JSON на диск |
| `load_graph` | Загрузить граф из JSON (merge, skip/overwrite конфликтов) |
| `export_edges` | Связи → CSV (Excel) или DOT (Graphviz-визуализация) |
---
## auto_link_collection — как это работает
```
auto_link_collection {
collection: "research", // опционально — иначе вся база
use_ai: true, // обогатить документы без сущностей перед связыванием
min_jaccard: 0.1 // порог схожести для авто-связей
}
```
Шаг 1 — для каждого документа в наборе проверяется, есть ли у него извлечённые сущности. Если нет и `use_ai: true` — документ прогоняется через `enrich_document` (Ollama/Anthropic) автоматически.
Шаг 2 — для всех документов набора считается Jaccard-пересечение сущностей, создаются связи `similar_to` (Jaccard ≥ 0.25) или `shares_topic` (ниже).
Возвращает сводку: сколько документов обогащено, сколько связей создано, и сами связи (до 50 штук в ответе). Если связей всё равно 0 — подсказка в поле `hint` рекомендует понизить `min_jaccard`.
---
## Типичный рабочий процесс
```
1. Узнать что лежит на сервере
list_server_files { path: "/mnt/data/papers" }
2. Массовый импорт (без авто-обогащения на этом шаге — дешевле и быстрее)
ingest_directory {
path: "/mnt/data/papers",
recursive: true,
extensions: ["pdf", "md"], // не указывать — заберёт и файлы без расширения
collection: "research"
}
3. Связать все документы коллекции одним вызовом
auto_link_collection { collection: "research" }
4. Исследовать граф
traverse_graph { doc_id: "...", max_depth: 3 }
find_by_entity { entity: "transformer" }
5. Сохранить граф на диск (бэкап / версионирование)
save_graph { path: "/home/user/backups/graph-2026-06-19.json" }
6. Визуализировать
export_edges { path: "/tmp/graph.dot", format: "dot" }
# затем на сервере: dot -Tsvg /tmp/graph.dot > graph.svg
```
---
## Диск-хранилище графа: как это устроено
Граф **всегда** живёт в SQLite (`~/.document-pinboard/pins.db`) — это основной источник истины.
JSON-снэпшоты через `save_graph`/`load_graph` — это:
- **Бэкапы** — на случай порчи БД
- **Версионирование** — `git add graph.json` для истории изменений графа
- **Перенос** — скопировать граф на другую машину без переноса всего SQLite-файла
- **AUTO_SAVE_PATH** — если задан в `.env`, граф автоматически грузится при старте сервера и сохраняется при остановке (SIGINT/SIGTERM)
`load_graph` по умолчанию работает в режиме `skip` — не трогает существующие записи при совпадении ID, что делает его безопасным для повторного запуска.
---
## Безопасность
- `ALLOWED_ROOTS` ограничивает `ingest_file`/`ingest_directory`/`list_server_files` только указанными директориями. Если оставить пустым — доступ к всей файловой системе сервера (не рекомендуется на боевом сервере).
- Все пути проходят через `safePath()`, который резолвит `..`-трюки и directory traversal.
---
## Известные ограничения
- **Tool-calling у Qwen3:14b не идеален.** Модель иногда домысливает параметры, которых нет в схеме (например, пыталась вызвать `ingest_file` с `paths` вместо `path`). `mcphost` сам отдаёт ошибку обратно модели и она обычно восстанавливается на следующей попытке — но это вероятностное поведение, не гарантия.
- **Описания тулов — это намёк, а не команда.** Несмотря на явные формулировки в `auto_link_collection` про "не вызывай по одному", модель технически может всё равно выбрать ручной цикл по `enrich_document`. Если заметишь такое поведение — попроси прямо: *"используй auto_link_collection"*.
- Авто-связи (`similar_to`/`shares_topic`) считаются только по пересечению сущностей. Если у документов мало общих именованных сущностей, но они тематически близки — Jaccard может не найти связь. В этом случае выручит `synthesize_cluster` или ручной `link_documents`.
---
## Структура файлов
```
src/
├── types.ts ← PinnedDocument schema
├── pdf-parse.d.ts ← минимальные типы для pdf-parse (он их не публикует)
├── db.ts ← DocumentDB (docs + FTS5)
├── processor.ts ← URL fetch, HTML→text
├── ingest.ts ← чтение файлов с диска (PDF/DOCX/код/текст, поддержка файлов без расширения) + сканирование папок
├── graph-types.ts ← Connection, Fact, Relation
├── graph.ts ← GraphDB (связи, сущности, факты, BFS, Jaccard, настраиваемый autoConnectByEntities)
├── ollama.ts ← HTTP-клиент Ollama (JSON mode, /no_think для Qwen3)
├── enricher.ts ← AI-обогащение (Ollama приоритетно, Anthropic fallback)
├── graph-store.ts ← save/load графа в JSON, экспорт CSV/DOT
├── file-tools.ts ← MCP-инструменты слоя 3 (ingest_*, save_graph, ...)
└── index.ts ← MCP-сервер, все 25 инструментов (включая auto_link_collection)
```
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues