Skip to main content
Glama

NoteHarbor MCP

一个 Python 示例,用于通过 MCP 和 GraphQL 查询 Obsidian 笔记,并使用 PostgreSQL/pgvector 进行扩展。不包含个人 vault、真实文档和 API 密钥。

NoteHarbor 的原始形态是 Markdown 文件。仅为搜索所需的数据单独创建内容,原始文件则原样保留。

本项目的准则

  • 本地优先(Local first):原始 Markdown 保留在本地,外部服务仅作为可连接的选项。

  • 保留源文件(Source preserving):搜索、嵌入、同步的结果不会取代原始内容。

  • 边界隐私(Privacy by boundary):实际 vault、个人记录和 API 密钥均保留在公开项目之外。

  • 模型中立(Model neutral):不将 embedding 生成器和 Vector DB 绑定到特定厂商。

  • 小而可替换(Small and replaceable):保持适配器和服务层足够小。

Related MCP server: Notes RAG MCP Server

三个公开示例

  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 是接入真实数据库连接时使用的配置占位。当前默认执行为 mock。

GraphQL 示例

GraphQL schema 位于 noteharbor/api/graphql_schema.py。可以将 get_schema() 的结果挂载到外部 ASGI 服务器上。

示例操作:

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。默认执行为基于 mock,因此无需连接 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 和检索。

目的

本仓库是一个 Python 架构示例,展示同一个 Markdown 源文件如何通过 MCP 和 GraphQL 两侧由同一个向量检索服务进行查询。

与其预先固定某个特定 embedding 厂商或某个特定向量数据库,更注重于明确标注出可替换的切入点。

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

向量化与检索的职责

当前 Python 示例不会直接生成 embedding。由 indexChunk mutation 和 upsert_vector 工具接收外部生成的 list[float] embedding 保存。

将 embedding 生成分开的原因如下:

  • 为了不将 embedding 模型固定为 OpenAI、Voyage 或本地模型之一

  • 为了排除 API 密钥和个人数据不进入公开示例

  • 为了分离 API 层与检索存储的职责

  • 为了在实际服务中把 chunking·embedding 批处理流水线替换为独立 worker

检索按以下顺序进行:

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

目前 repository.py 中的 MockPostgresVectorRepository 在内存中再现此流程。真实 PostgreSQL 适配器使用 embedding <=> :query_embedding 计算余弦距离,并以 1 - distance 作为 score 返回。

为什么选择 PostgreSQL + pgvector

NoteChunk 不只是包含向量的值,还同时具有原始路径、chunk 序号、正文和元数据。将 PostgreSQL 与 pgvector 结合,可以在同一个存储库与 SQL 边界内处理关系型条件和向量相似度检索。

  • PostgreSQL:原始元数据、状态、同步信息和事务管理

  • pgvector:vector 列、余弦距离(<=>)、HNSW 索引

  • Docker:在开发环境中固定 pgvector 的运行条件

  • SQLAlchemy Repository:替换存储的切入点

检索规模扩大时,专用向量数据库可能更合适。这里展示的是在同一个存储中处理文档信息与向量检索的流程。

量化的范围

当前的 Python 仓库中尚未加入量化实现。输入 embedding 以 float 值原样接收并检索,这是为了优先展示模型中性的 Repository 边界。

如需加入量化,会单独设置 EmbeddingQuantizer 端口,并由 float32 → INT8/float16 → 存储与恢复 放置于 indexing worker 与 Repository adapter 之间。由于量化方式必须在搜索质量、内存和延迟时间测量后再确定,因此本示例并不夸张地声称已经实现。

两个 NoteHarbor 示例的关系

  • noteharbor-mcp :TypeScript MCP 工具和 embedding、INT8 量化流程

  • noteharbor-python:Python MCP 与 GraphQL API,以及 VectorService/Repository 边界

两个仓库是以不同语言和 API 方式实现同一想法的示例,不包含实际个人 vault 或生产环境中的数据。

使用的技术以及原因

技术

使用原因

uv

快速复现 Python 依赖、虚拟环境和 lock 文件,并在 Docker 中沿用相同的安装路径

FastMCP

简洁明了地展示 Python 函数与 MCP 工具的连接方式

Pydantic

用于验证 API 边界的输入模型和配置

SQLAlchemy

使 Repository 不绑定特定 SQL 执行方式,提供前端 PostgreSQL 适配器边界

psycopg

用于在 Python 中处理 PostgreSQL 连接

pgvector Python package

用于以 SQLAlchemy 模型表示 PostgreSQL 的 vector 类型

Strawberry GraphQL

提供将同一个 VectorService 暴露为 GraphQL Query/Mutation 的示例

**postgreSQL **

用于复现已启用 pgvector 的 PostgreSQL 与 Python 开发环境

每种技术都按 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