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: second-brain-mcp

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

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_noteVectorService.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

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.

  • Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.

  • Search and reason over your Obsidian-style Markdown vault, right from ChatGPT.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kris-atelier/noteharbor-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server