Skip to main content
Glama

NoteHarbor MCP

A Python example that queries Obsidian notes via MCP and GraphQL and extends them with PostgreSQL/pgvector. It does not include personal vaults, real documents, or API keys.

The original source of NoteHarbor is Markdown files. Only the data needed for search is created separately, and the originals are preserved as-is.

Principles of this project

  • Local first: Original Markdown stays local, and external services are optional connections.

  • Source preserving: Search, embedding, and sync results do not replace the originals.

  • Privacy by boundary: Real vaults, personal records, and API keys stay outside the public project.

  • Model neutral: Embedding generators and Vector DBs are not tied to any specific vendor.

  • Small and replaceable: Adapters and service layers are kept small.

Related MCP server: Notes RAG MCP Server

3 public samples

  1. MCP: upsert_vector, search_vectors tools

  2. GraphQL: indexChunk Mutation, vectorSearch Query

  3. PostgreSQL/pgvector: SQLAlchemy models, Repository ports, Docker development environment

These are examples for understanding the structure, not production services. The default Repository runs in memory, and the point where you can switch to PostgreSQL/pgvector and SQL examples are provided together.

Quick start

uv sync

Run the MCP sample:

uv run noteharbor

Starting PostgreSQL with Docker

docker compose up -d postgres

docker-compose.yml defines the pgvector/pgvector image and a development PostgreSQL for Python MCP.

# PostgreSQL만
docker compose up -d postgres

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

POSTGRES_URL is a configuration placeholder for when you attach a real DB connection. The current default run is a mock.

GraphQL sample

The GraphQL schema is in noteharbor/api/graphql_schema.py. You can mount the get_schema() result on an external ASGI server.

Example operations:

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 and ORM style boundaries

The Repository handles the full lifecycle of NoteChunk, from creation to deletion, not just search.

  • Create/Upsert: MCP upsert_vector, GraphQL indexChunkVectorService.index_chunk()

  • Read: MCP get_chunk·list_chunks, GraphQL noteChunk·noteChunks

  • Update: MCP·GraphQL updateChunk → reads the existing record, then applies only the changed fields

  • Delete: MCP·GraphQL deleteChunk

  • Search: MCP search_vectors, GraphQL vectorSearch

The current adapter is an in-memory mock for verifying behavior. The actual storage method sits behind the SQLAlchemy ORM boundary. In the real PostgreSQL adapter, MockPostgresVectorRepository is replaced with session operations like the following.

If you want to see the real ORM flow in code, check noteharbor/infrastructure/postgres/sqlalchemy_repository.py. This adapter implements the same VectorRepository port while using Session.get, select, update, and delete. The default run is still a mock, so you can read and test the example without a PostgreSQL connection.

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))

The API does not handle SQL directly; it passes CRUD and search in the order MCP/GraphQL → VectorService → VectorRepository → SQLAlchemy/pgvector.

Purpose

This repository is a Python architecture sample that queries a single Markdown source through the same vector search service from both MCP and GraphQL.

Rather than fixing a specific embedding vendor or vector DB, the focus is on making the replaceable points clear.

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

The current Python sample does not generate embeddings directly. The indexChunk mutation and upsert_vector tool receive externally generated list[float] embeddings and store them.

The reasons for separating embedding generation are as follows.

  • To avoid fixing the embedding model to one of OpenAI, Voyage, or a local model

  • To exclude API keys and personal data from the public sample

  • To separate the responsibilities of the API layer and the search repository

  • To allow replacing the chunking and embedding batch pipeline with a separate worker in a real service

Search follows this order:

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

The current MockPostgresVectorRepository in repository.py reproduces this flow in memory. In the real PostgreSQL adapter, cosine distance is calculated with embedding <=> :query_embedding, and 1 - distance is returned as the score.

Why PostgreSQL + pgvector

NoteChunk is not just a value with a vector; it also carries the original path, chunk sequence number, body, and metadata. Combining PostgreSQL and pgvector lets you handle relational conditions and vector similarity search within a single repository and SQL boundary.

  • PostgreSQL: original metadata, status, sync information, and transaction management

  • pgvector: vector column, cosine distance (<=>), HNSW index

  • Docker: fixes the pgvector runtime conditions in the development environment

  • SQLAlchemy Repository: the repository replacement point

As search scale grows, a dedicated vector DB may be more suitable. Here, the flow of handling document information and vector search in a single repository is shown.

Quantization scope

The current Python repo does not include a quantization implementation. Input embeddings are received and searched as float values as-is, which is a choice to first show a model-neutral Repository boundary.

If quantization is added, an EmbeddingQuantizer port would be placed separately, with the float32 → INT8/float16 → store/restore steps between the indexing worker and the Repository adapter. Since the quantization method should be decided after measuring search quality, memory, and latency, this sample does not overstate that it is implemented.

Relationship between the two NoteHarbor samples

  • noteharbor-mcp: TypeScript MCP tools with embedding and INT8 quantization flow

  • noteharbor-python: Python MCP·GraphQL API with VectorService/Repository boundaries

Both repos are examples of the same idea expressed in different languages and API approaches, and they do not include real personal vaults or production data.

Additional technologies and reasons for inclusion

Technology

Reason for inclusion

uv

To quickly reproduce Python dependencies, virtual environments, and lock files, and to use the same installation path in Docker

FastMCP

To show the connection between Python functions and MCP tools concisely and clearly

Pydantic

To validate input models and configuration at the API boundary

SQLAlchemy

To place a PostgreSQL adapter boundary so the Repository is not tied to a specific SQL execution method

psycopg

To handle PostgreSQL connections from Python

pgvector Python package

To represent the PostgreSQL vector type in SQLAlchemy models

Strawberry GraphQL

To create an example that exposes the same VectorService as GraphQL Query/Mutation

Docker Compose

To reproduce the development environment of PostgreSQL with pgvector enabled and Python MCP together

Each technology was chosen to explain the division of roles among API, service, repository, and development environment.

Project structure

.
├── 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

License

MIT

A
license - permissive license
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

  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides tools for ingesting documents into a local vector database and retrieving relevant information via semantic search, enabling retrieval-augmented generation for MCP clients.
    6
  • A
    license
    Not graded
    quality
    B
    maintenance
    Indexes local Markdown/text files into a SQLite database with vector embeddings and provides MCP tools for semantic search without cloud dependencies.
    GPL 3.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides MCP tools for semantic search over personal knowledge sources using pluggable embeddings and local vector indexing.
    1
    MIT

View all related MCP servers

Related MCP Connectors

  • Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.

  • Hosted MCP memory: save sessions/decisions once, search from Claude, Cursor, ChatGPT. EU-hosted FTS.

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

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-python'

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