Skip to main content
Glama

NoteHarbor — TypeScript

Пример на TypeScript, который подключает Obsidian Markdown к инструментам MCP и расширяет его поиском через PostgreSQL/pgvector.

NoteHarbor разделяет интерфейс MCP и векторное хранилище. Клиент вызывает инструменты MCP, а сервис использует доменную модель и Repository для работы с хранилищем.

Архитектура

MCP Client
    │
    ▼
MCP Tools
(upsert_vector / search_vectors)
    │
    ▼
Vector Service
(src/application/vectorService.ts)
    │
    ▼
VectorRepository port
(src/domain/knowledge.ts)
    │
    ├── 현재 실행 어댑터: in-memory Map 목업
    │
    └── 운영 전환 지점: PostgreSQL + pgvector
        (src/infrastructure/postgres/)

Заметка превращается в поисковые данные в следующем порядке:

Obsidian Markdown
  → note chunk
  → externally generated embedding
  → note_chunks.embedding (pgvector)
  → cosine similarity search
  → MCP response

Related MCP server: Smart Connections MCP Server

Фактический процесс векторизации

upsert_vector — это инструмент для сохранения уже созданного embedding. А как Markdown превращается в chunk и вектор, можно посмотреть в index_note.

Markdown text
  → splitMarkdownIntoChunks()
  → EmbeddingProvider.embed(chunk)
  → normalized number[]
  → NoteChunk
  → VectorService.indexChunk()
  → VectorRepository.save()

Основной код разбит по следующим файлам:

  • chunker.ts: разбивает Markdown на абзацы и применяет максимальную длину

  • embeddingProvider.ts: детерминированный демонстрационный embedding-провайдер без API-ключа

  • indexingPipeline.ts: связывает создание chunk, embedding и сохранение

  • knowledge.ts: порты EmbeddingProvider и VectorRepository

  • index.ts: регистрирует инструменты MCP index_note, upsert_vector, search_vectors

Демонстрационный провайдер — это детерминированная реализация для проверки потока, а не модель для качественного смыслового поиска. В реальном сервисе к тому же порту EmbeddingProvider подключается внешняя или локальная модель.

Квантование применяется после создания embedding.

float embedding [-1, 1]
  → clamp
  → int8 = round(value / (1 / 127))
  → 저장: values + scale + zeroPoint
  → 복원: (int8 - zeroPoint) * scale

Пример использует симметричное скалярное INT8-квантование.

  • диапазон значений: [-1, 1]

  • диапазон квантования: [-127, 127]

  • scale: 1 / 127

  • zeroPoint: 0

  • Схема хранит embedding_int8, embedding_scale, embedding_zero_point вместе с исходным embedding

  • Сейчас поиск работает с восстановленным float-вектором, а ANN-индекс на основе квантования добавляется при выборе реального адаптера

Реализация находится в quantizer.ts и indexingPipeline.ts. Ответ index_note тоже содержит размерность embedding и число бит квантования.

Почему такая структура

NoteHarbor — это пример, который превращает Obsidian Markdown в доступные для поиска единицы знания и предоставляет эту возможность через инструменты MCP.

Простого строкового поиска недостаточно, чтобы находить связанные материалы с другими формулировками. Поэтому заметка разбивается на небольшие chunk, каждый chunk превращается в embedding, и поиск идёт по смысловой близости.

Квантование нужно, чтобы сделать embedding компактнее.

  • уменьшает память

  • уменьшает объём передаваемых данных

  • удобно для кэша и пакетной обработки в крупной базе знаний

  • но точность может быть чуть ниже, чем у исходного float

Поэтому у каждого компонента своя роль:

  1. Embedding: превращает смысл текста в числовой вектор

  2. Quantization: снижает точность вектора, чтобы уменьшить затраты на хранение

  3. Vector search: находит близкие векторы и возвращает связанные chunk

  4. MCP: открывает эту функцию как инструмент, который может вызывать LLM-клиент

INT8 выбран потому, что квантование и формат хранения легко объяснить на коде. В реальном сервисе нужно измерять качество поиска, экономию памяти и задержку, а затем выбирать между float32, float16, INT8 и binary.

Текущая реализация — это заглушка (mock) для проверки потока. Она не гарантирует качество смыслового поиска или производительность квантования. В реальной эксплуатации к порту embedding-провайдера подключается настоящая модель, а к порту репозитория — pgvector-адаптер.

Проектирование векторной БД

Единица хранения векторов — NoteChunk.

поле

значение

id

идентификатор из исходного пути и номера chunk

sourcePath

исходный путь Obsidian Markdown

chunkIndex

номер chunk внутри документа

content

текст, который возвращается в результатах поиска

embedding

вектор, созданный внешней моделью embedding

metadata

расширяемые данные: теги, статус, атрибуты и т.д.

Рабочий пример SQL-схемы находится в src/infrastructure/postgres/schema.sql. В базовом примере используется 1536-мерный embedding и HNSW-индекс для cosine distance. При реальном использовании размерность подстраивается под конкретную модель embedding.

Поиск в PostgreSQL выглядит так:

SELECT id, source_path, chunk_index, content, metadata,
       1 - (embedding <=> $1::vector) AS score
FROM note_chunks
ORDER BY embedding <=> $1::vector
LIMIT $2;

Граница CRUD и ORM-стиля

Векторное хранилище — это не только поиск, но и репозиторий, управляющий жизненным циклом NoteChunk.

  • Create/Upsert: upsert_vector, index_note → VectorService.indexChunk()

  • Read: get_chunk, list_chunks

  • Update: update_chunk → читает существующий chunk, обновляет поля и квантованное представление

  • Delete: delete_chunk

  • Search: search_vectors, search_knowledge

Сейчас адаптером выступает Map в памяти. В production внутренности репозитория заменяются на Drizzle ORM и pg.

db.insert(noteChunks).values(row).onConflictDoUpdate(...)
db.select().from(noteChunks).where(eq(noteChunks.id, id)).limit(1)
db.update(noteChunks).set(values).where(eq(noteChunks.id, id))
db.delete(noteChunks).where(eq(noteChunks.id, id))

Инструменты MCP не выполняют SQL напрямую. Вызов идёт по цепочке MCP → VectorService → VectorRepository → Drizzle/pgvector.

Что входит в пример

  • чтение и поиск по Obsidian Markdown

  • инструменты MCP: index_note, search_knowledge, upsert_vector, search_vectors

  • слои: MCP → Service → Repository → pgvector

  • схема note_chunks на основе Drizzle ORM

  • схема PostgreSQL/pgvector и SQL для поиска

  • граница синхронизации с Notion

  • dev-сервер Smithery и пример запуска в Docker

Базовая конфигурация запускается без личных данных и без внешних учётных данных — используется заглушка в памяти. При подключении PostgreSQL Map-реализация в src/infrastructure/postgres/postgresVectorRepository.ts заменяется адаптером на Drizzle/pg. README и схема показывают, где именно происходит этот переход.

Версия на Python/GraphQL доступна в noteharbor-python.

Начало работы

npm install
npm run dev

Docker:

docker build -f Dockerfile.typescript -t noteharbor-mcp-ts .
docker run --rm -p 8081:8081 noteharbor-mcp-ts

Почему PostgreSQL + pgvector

Объекты поиска NoteHarbor — это не только векторы. Нужно одновременно работать с реляционными метаданными: исходный путь, номер chunk, теги, статус, информация о синхронизации.

Поэтому в этом примере не добавляется отдельная векторная БД, а в PostgreSQL хранятся вместе:

  • content: фрагмент исходного текста, который показывается в результатах поиска

  • metadata: теги, статус, информация об источнике

  • embedding: float-вектор для cosine-поиска

  • embedding_int8: квантованное представление для экономии памяти и трафика

Конкретные причины выбора pgvector:

  • Совместимость данных: векторная близость и условия по source_path, тегам, статусу комбинируются в одном SQL

  • Согласованность: метаданные исходного текста и поисковый индекс управляются в одной транзакционной границе

  • Простота эксплуатации: приложению не нужно отдельно обслуживать PostgreSQL и отдельную векторную БД

  • Возможности поиска: cosine distance (<=>) и HNSW-индекс доступны как расширение PostgreSQL

  • Путь масштабирования: сначала единое хранилище, а при росте можно выделить отдельный поисковый адаптер

При большом масштабе поиска выделенная векторная БД может подойти лучше. Здесь главное — показать поток, в котором исходный текст, метаданные и векторный поиск находятся в одной границе приложения.

Как работает поиск

Поиск не сравнивает исходные строки напрямую. Вопрос и chunk заметки помещаются в одно и то же пространство embedding, после чего сравнивается расстояние.

사용자 질문
  → query embedding 생성
  → INT8 양자화 후 복원
  → PostgreSQL/pgvector cosine distance 검색
  → 가까운 NoteChunk 반환
  → sourcePath·content·metadata와 함께 MCP 응답

Рабочий пример — инструмент MCP search_knowledge.

  1. index_note разбивает Markdown на chunk и сохраняет embedding каждого chunk.

  2. Пользователь отправляет запрос на естественном языке query.

  3. Тот же EmbeddingProvider создаёт embedding запроса.

  4. Запрос квантуется и восстанавливается тем же способом, что и сохранённые векторы.

  5. VectorRepository.search() сортирует chunk по cosine similarity.

  6. В результатах — исходный путь, содержимое chunk, метаданные и score.

search_vectors — низкоуровневый инструмент, который принимает уже готовый embedding. search_knowledge — прикладной инструмент, который связывает естественный вопрос с результатами поиска.

Сейчас мок-репозиторий вычисляет cosine similarity в памяти через Map. После перехода на PostgreSQL-адаптер за тем же портом будут использоваться оператор <=> из pgvector и поиск с LIMIT.

Дополнительные технологии и зачем они здесь

технология

зачем используется

Node.js ESM

чтобы просто запускать TypeScript MCP-пример в текущей среде выполнения Node

MCP SDK

чтобы регистрировать инструменты index_note, search_knowledge, upsert_vector, search_vectors как стандартный MCP-сервер

Zod

для проверки входных данных MCP и конфигурации во время выполнения

Drizzle ORM

чтобы при подключении PostgreSQL-адаптера получить типобезопасную границу схемы и запросов

pg

драйвер для перехода на реальный адаптер подключения PostgreSQL

Smithery CLI

предоставляет путь запуска для разработки и проверки MCP-сервера

Docker

фиксирует условия запуска Node/MCP в локальной и развёрнутой среде

chokidar·fast-glob

отвечает за отслеживание изменений в vault Markdown и обход файлов

gray-matter·marked

для работы с frontmatter Markdown и телом заметки как с единицами знания

dotenv

чтобы отделить локальные настройки окружения от кода

Не все зависимости — ядро векторного поиска. Часть — вспомогательные технологии для интеграции с Obsidian и Notion. Центр поискового пути: MCP SDK → Vector Service → Repository → pgvector.

Почему выбраны эти технологии

технология

причина выбора

Obsidian Markdown

исходник — обычные текстовые файлы: высокая управляемость и переносимость, знание не привязано к конкретному SaaS

MCP

чтобы не писать отдельную интеграцию под каждого LLM-клиента, а открыть один и тот же инструмент знаний через стандартный интерфейс

TypeScript

естественная связка с MCP SDK, границы входа и выхода инструментов управляются типами

PostgreSQL

чтобы согласованно хранить метаданные документов, статусы и результаты поиска в одном хранилище и иметь путь к production

pgvector

чтобы без отдельной векторной БД работать с метаданными исходного текста и векторным поиском внутри PostgreSQL

Notion adapter

чтобы показать границу: Notion не является исходным хранилищем, но при необходимости фрагменты знаний синхронизируются во внешний workspace

Docker

чтобы сделать условия запуска PostgreSQL и MCP одинаковыми в локальной и развёрнутой среде

Суть не в том, чтобы навесить как можно больше инструментов. Суть в том, что исходник сохраняется в Markdown, производные данные для поиска лежат в PostgreSQL/pgvector, а LLM через MCP получает только нужные функции.

Поэтому этот проект — не система, где Obsidian, Notion и PostgreSQL одновременно являются источниками.

  • Obsidian Markdown: исходное знание

  • PostgreSQL/pgvector: производный индекс для поиска

  • Notion: опциональная внешняя цель синхронизации

  • MCP: граница доступа для LLM

Ключевые проектные решения

  • сохранение исходного Markdown

  • разделение инструментов MCP и сервисного слоя

  • доменная модель NoteChunk и порт VectorRepository

  • граница хранилища, заменяемая на PostgreSQL/pgvector

  • без личного vault и учётных данных

Это публичный пример для изучения структуры MCP и поиска по знаниям.

Лицензия

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Connects AI assistants to an Obsidian vault as a semantic knowledge graph, enabling graph navigation, semantic search, and content operations through MCP.
    5 npm
    466
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Exposes Obsidian notes as a semantic search and RAG knowledge base over MCP, enabling AI assistants to index, retrieve, and analyze personal notes via natural language.
    7
    1
    -
  • A
    license
    B
    quality
    A
    maintenance
    Enables local hybrid search over Obsidian and Markdown vaults via MCP, combining vector retrieval, full-text search, reranking, graph navigation, and safe CRUD while keeping data local.
    43
    1
    MIT