Skip to main content
Glama

NoteHarbor MCP

ObsidianノートをMCPとGraphQLで検索し、PostgreSQL/pgvectorで拡張するPythonの例です。個人のvault、実際のドキュメント、APIキーは含まれません。

NoteHarborの原本はMarkdownファイルです。検索に必要なデータだけを別に作成し、元のファイルはそのまま保持します。

このプロジェクトの基準

  • Local first: 元のMarkdownはローカルに残し、外部サービスは接続可能な選択肢としておきます。

  • Source preserving: 検索・埋め込み・同期の結果が原本を置き換えないようにします。

  • Privacy by boundary: 実際のvault、個人の記録、APIキーは公開プロジェクトの外に置きます。

  • Model neutral: 埋め込み生成器とVector DBを特定のベンダーに固定しません。

  • Small and replaceable: アダプターとサービス層を小さく保ちます。

Related MCP server: Notes RAG MCP Server

公開サンプル3つ

  1. MCP: upsert_vector, search_vectors ツール

  2. GraphQL: indexChunk Mutation, vectorSearch Query

  3. PostgreSQL/pgvector: SQLAlchemyモデル、Repositoryポート、Docker開発環境

運用サービスではなく、構造を確認するための例です。デフォルトのRepositoryはメモリ上で動作し、PostgreSQL/pgvectorへ移す箇所とSQL例をあわせて提供します。

クイックスタート

uv sync

MCPサンプルを実行:

uv run noteharbor

DockerでPostgreSQLを起動する

docker compose up -d postgres

docker-compose.yml は pgvector/pgvector イメージとPython MCP用の開発用PostgreSQLを定義します。

# PostgreSQL만
docker compose up -d postgres

# Python MCP까지
docker compose up --build python-mcp

POSTGRES_URL は実際のDB接続を設定するときに使う設定場所です。現在のデフォルト実行はモックです。

GraphQLサンプル

GraphQLスキーマは noteharbor/api/graphql_schema.py にあります。外部ASGIサーバーに get_schema() の結果をマウントできます。

処理例:

mutation {
  indexChunk(
    sourcePath: "sample.md"
    content: "Obsidian knowledge"
    embedding: [1.0, 0.0]
  )
}

query {
  vectorSearch(embedding: [0.9, 0.1], limit: 5) {
    sourcePath
    content
    score
  }
}

CRUDとORMスタイルの境界

Repositoryは検索だけでなく、NoteChunkの生成から削除までの全ライフサイクルを担当します。

  • Create/Upsert: MCP upsert_vector、GraphQL indexChunk → VectorService.index_chunk()

  • Read: MCP get_chunk・list_chunks、GraphQL noteChunk・noteChunks

  • Update: MCP・GraphQL updateChunk → 既存レコードを読み取ったうえで、変更フィールドのみ反映

  • Delete: MCP・GraphQL deleteChunk

  • Search: MCP search_vectors、GraphQL vectorSearch

現在のアダプターは動作確認用のインメモリモックです。実際の保存方法はSQLAlchemy ORM境界の背後に置きます。実際のPostgreSQLアダプターでは、次のようなセッション操作で MockPostgresVectorRepository を置き換えます。

実際のORMフローをコードで確認したい場合は、noteharbor/infrastructure/postgres/sqlalchemy_repository.py を参照してください。このアダプターは同じ VectorRepository ポートを実装しながら、Session.get、select、update、delete を使用します。デフォルト実行は依然としてモックなので、PostgreSQL接続なしでサンプルを読んでテストできます。

session.get(NoteChunkModel, chunk_id)
session.scalars(select(NoteChunkModel).limit(limit)).all()
session.execute(update(NoteChunkModel).where(NoteChunkModel.id == chunk_id).values(...))
session.execute(delete(NoteChunkModel).where(NoteChunkModel.id == chunk_id))

APIはSQLを直接扱わず、MCP/GraphQL → VectorService → VectorRepository → SQLAlchemy/pgvector の順でCRUDと検索を引き渡します。

目的

このリポジトリは、1つのMarkdown原本をMCPとGraphQLの両方から、同じベクトル検索サービスで参照するPythonアーキテクチャのサンプルです。

特定のembeddingベンダーやベクトルDBを決めるよりも、置き換えられる箇所を明確にする点を重視しています。

MCP / GraphQL API
  → VectorService
  → VectorRepository port
  → 현재: in-memory MockPostgresVectorRepository
  → 전환: PostgreSQL + pgvector

ベクトル化と検索の責任

現在のPythonサンプルは、embeddingを直接生成しません。indexChunk mutation と index で、外部から生成された list[float] embedding を受け取って保存します。

embedding生成を分離した理由は次のとおりです。

  • embeddingモデルをOpenAI・Voyage・ローカルモデルのうち1つに固定しないため

  • APIキーと個人データを公開サンプルから除外するため

  • API層と検索ストレージの責任を分離するため

  • 実サービスでは、chunking・embeddingのバッチパイプラインを別のワーカーに置き換えるため

検索は次の順序です。

사용자 query embedding
  → GraphQL vectorSearch 또는 MCP search_vectors
  → VectorService.search()
  → Repository.search()
  → cosine similarity 계산
  → score가 높은 NoteChunk 반환

現在の repository.py の MockPostgresVectorRepository は、このフローをメモリ上で再現します。実際のPostgreSQLアダプターでは、embedding <=> :query_embedding でコサイン距離を計算し、1 - distance をスコアとして返します。

なぜPostgreSQL + pgvectorなのか

NoteChunkはベクトルだけを持つ値ではなく、元のパス・チャンク番号・本文・メタデータも一緒に持ちます。PostgreSQLとpgvectorを組み合わせることで、関係条件とベクトル類似度検索を1つのストレージとSQL境界内で扱えます。

  • PostgreSQL: 原文メタデータ、状態、同期情報、トランザクション管理

  • pgvector: vector カラム、コサイン距離(<=>)、HNSWインデックス

  • Docker: 開発環境でpgvectorの実行条件を固定

  • SQLAlchemy Repository: ストレージの置き換え箇所

検索規模が大きくなると、専用ベクトルDBの方が適している場合があります。ここでは、ドキュメント情報とベクトル検索を1つのストレージで扱うフローを示します。

量子化の範囲

現在のPythonリポジトリには、量子化の実装を実装していません。入力embeddingはfloat値のまま保存・検索します。これはモデルに依存しないRepository境界を先に示すための選択です。

量子化を追加する場合には、EmbeddingQuantizer ポートを別途設けて、float32 → INT8/float16 → save & restore の段階を indexing worker と Repository adapter の間に配置します。量子化方法は検索品質・メモリ・遅延時間を測定してから決める必要があるため、このサンプルでは実装済みとは説明しません。

2つのNoteHarborサンプルの関係

  • noteharbor-mcp: TypeScript MCPツールとembedding・INT8量子化フロー

  • noteharbor-python: Python MCP・GraphQL APIとVectorService/Repository境界

2つのリポジトリは、同じ考えを異なる言語とAPI方法で表現した例であり、実際の個人vaultや本番データは含みません。

追加技術と採用理由

技術

採用理由

uv

Pythonの依存関係・仮想環境・lockファイルをすばやく再現し、Dockerでも同じ導入経路を利用するため

FastMCP

Python関数とMCPツールの接続を短く明確に示すため

Pydantic

API境界の入力モデルと設定を検証するため

SQLAlchemy

Repositoryが特定のSQL実行方法に固定されないよう、PostgreSQL adapter境界を置くため

psycopg

PythonからPostgreSQLへの接続を担当するため

pgvector Python package

SQLAlchemyモデルでPostgreSQLの vector 型を表現するため

Strawberry GraphQL

同じVectorServiceをGraphQL Query/Mutationとして公開する例を示すため

Docker Compose

pgvectorが有効化されたPostgreSQLとPython MCPの開発環境をまとめて再現するため

各技術は、API・サービス・ストレージ・開発環境の役割を分けて説明するために選びました。

プロジェクト構造

.
├── noteharbor/
│   ├── mcp_server.py
│   ├── api/graphql_schema.py
│   ├── application/vector_service.py
│   ├── domain/
│   └── infrastructure/
│       ├── notion/mock_adapter.py
│       └── postgres/
│           ├── models.py
│           ├── repository.py
│           └── sqlalchemy_repository.py
├── tests/test_vector_repository.py
├── Dockerfile
├── docker-compose.yml
└── pyproject.toml

ライセンス

MIT

Related MCP Connectors

Related MCP Servers