Skip to main content
Glama

NoteHarbor MCP — TypeScript

It is a TypeScript example that connects Obsidian Markdown through MCP tools and extends it with PostgreSQL/pgvector search.

NoteHarbor separates the MCP interface and the vector store. Clients call MCP tools, and the service uses the store through domain models and Repository.

Architecture

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

Notes become search data in the following order.

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

Related MCP server: second-brain-mcp

Actual vectorization flow

upsert_vector is a tool that stores an already-created embedding. The process of turning Markdown into chunks and vectors can be seen in index_note.

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

The main code is split across the following files.

  • chunker.ts: Divides Markdown by paragraph and applies a maximum length

  • embeddingProvider.ts: A deterministic demo embedding provider that works without an API key

  • indexingPipeline.ts: Connects chunk creation, embedding, and storage

  • knowledge.ts: Ports for EmbeddingProvider and VectorRepository

  • index.ts: Registers the index_note, upsert_vector, search_vectors MCP tools

The demo provider is a deterministic implementation for verifying the flow. It is not a model that provides semantic search quality; in a real service, you connect an external or local model to the same EmbeddingProvider port.

Quantization is applied after embedding generation.

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

The sample uses symmetric scalar INT8 quantization.

  • Value range: [-1, 1]

  • Quantization range: [-127, 127]

  • scale: 1 / 127

  • zeroPoint: 0

  • Defines a schema to store embedding_int8, embedding_scale, and embedding_zero_point along with the original embedding

  • The current reference search uses a restorable float vector; the quantized ANN index will be added when an actual adapter is selected

The implementation is in quantizer.ts and indexingPipeline.ts. The index_note response also includes the embedding dimension and quantization bit width.

Why it is structured this way

NoteHarbor is an example that turns Obsidian Markdown into searchable knowledge units and exposes that capability as MCP tools.

Simple string search alone makes it hard to find related content with different wording. So notes are split into smaller chunks, and each chunk is converted into an embedding so that semantically close content can be searched.

Quantization is a choice for keeping these embeddings in a more compact representation.

  • Reduces memory and storage space

  • Reduces the amount of vector data transferred

  • Favorable for caching and batch processing in large knowledge bases

  • Precision may be lower than the original float instead

So the roles of each component are divided as follows.

  1. Embedding: Represents the meaning of text as a numerical vector

  2. Quantization: Reduces storage cost by lowering vector precision

  3. Vector search: Finds nearby vectors and returns relevant chunks

  4. MCP: Exposes this capability as a tool that LLM clients can call

The reason INT8 was chosen in this project is that the quantization principle and storage format are easy to explain in code. In a real service, you should measure search quality, memory savings, and latency, then choose one of float32, float16, INT8, or binary.

The current implementation is a mockup for verifying the flow. It does not guarantee semantic search quality or quantization performance, and in production you need to connect an embedding provider and a pgvector adapter.

Vector DB design

The vector storage unit is NoteChunk.

Field

Description

id

Identifier created from the original path and chunk order

sourcePath

Original Obsidian Markdown path

chunkIndex

Chunk order within the document

content

Text returned as a search result

embedding

Vector generated by an external embedding model

metadata

Extended information such as tags, status, and source attributes

A runnable SQL design example is available in src/infrastructure/postgres/schema.sql. The default example uses a 1,536-dimensional embedding and an HNSW index for cosine distance; adjust it to match the dimensions of your actual embedding model.

Search is translated into the following form in 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 and ORM-style boundaries

The vector store is not search-only; it is a Repository that manages the lifecycle of NoteChunk.

  • Create/Upsert: upsert_vector, index_noteVectorService.indexChunk()

  • Read: get_chunk, list_chunks

  • Update: update_chunk → reads an existing chunk and updates changed fields along with its quantized representation

  • Delete: delete_chunk

  • Search: search_vectors, search_knowledge

The current execution adapter is a Map. In a production environment, you can replace the Repository's inner implementation with Drizzle ORM and 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 tools do not execute SQL directly; they pass CRUD and search through MCP → VectorService → VectorRepository → Drizzle/pgvector.

Included samples

  • Obsidian Markdown listing, reading, and search

  • The index_note, search_knowledge, upsert_vector, and search_vectors MCP tools

  • The MCP → Service → Repository → pgvector layer

  • Drizzle ORM-based note_chunks schema mapping

  • PostgreSQL/pgvector schema and search SQL

  • Notion sync boundary

  • Smithery development server and Docker run examples

The current default execution is a memory mockup that does not include personal data or external credentials. For a PostgreSQL connection, replace the Map implementation in src/infrastructure/postgres/postgresVectorRepository.ts with a Drizzle/pg-based adapter. The README and schema are samples that openly show that transition point.

The Python/GraphQL version is available at noteharbor-python.

Getting Started

npm install
npm run dev

Docker:

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

Why PostgreSQL + pgvector

NoteHarbor's search targets are not just vector data. You also need to handle relational metadata such as source path, chunk order, tags, status, and sync information.

So instead of adding a separate vector DB, this sample keeps the following together in PostgreSQL.

  • content: Original text snippet to display as a search result

  • metadata: Tags, status, and source information

  • embedding: Float vector for cosine search

  • embedding_int8: Quantized representation to reduce storage and transfer costs

The specific reasons for choosing pgvector are as follows.

  • Data combination: You can combine vector similarity with source_path, tag, and status conditions in a single SQL statement

  • Consistency: You can manage source metadata and the search index within the same transaction boundary

  • Operational simplicity: The application does not have to operate PostgreSQL and a separate vector DB separately

  • Search features: You can use cosine distance (<=>) and HNSW indexes as PostgreSQL extensions

  • Scaling path: Start with a single store at first, and when scale grows, you can separate a search-only adapter

As search scale grows, a dedicated vector DB may be more suitable. The value here is in showing a flow that handles source text, metadata, and vector search within a single application boundary.

How search works

Search does not directly compare source strings; it places the question and note chunks in the same embedding space and then compares distances.

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

The runnable sample is the search_knowledge MCP tool.

  1. index_note splits Markdown into chunks and stores the embedding of each chunk.

  2. The user sends a natural-language query.

  3. A query embedding is generated using the same EmbeddingProvider.

  4. The query is quantized and restored in the same way as the stored vectors.

  5. VectorRepository.search() sorts nearby chunks by cosine similarity.

  6. Search results include the source path, chunk content, metadata, and score.

search_vectors is a low-level tool that takes an already-created embedding directly, while search_knowledge is an application-level tool that connects a natural-language question to search results.

The current mock Repository computes cosine similarity in an in-memory Map. Once you switch to a PostgreSQL adapter, it will use pgvector's <=> operator and LIMIT search behind the same port.

Additional technologies and why they were introduced

Technology

Reason for inclusion

Node.js ESM

To run the TypeScript MCP sample simply in the current Node runtime style

MCP SDK

To register the index_note, search_knowledge, upsert_vector, and search_vectors tools as a standard MCP server

Zod

To validate MCP inputs and configuration values at runtime

Drizzle ORM

To provide a type-safe schema and query boundary when connecting the PostgreSQL adapter

pg

The driver to use when switching to a real PostgreSQL connection adapter

Smithery CLI

To provide a run path for developing and verifying the MCP server

Docker

To pin down Node/MCP execution conditions in local and deployment environments

chokidar·fast-glob

Handles Markdown vault change detection and file discovery

gray-matter·marked

To treat Markdown frontmatter and body as knowledge chunks

dotenv

To separate local environment settings from code

Not all dependencies are core to vector search. Some are supporting technologies for Obsidian and Notion integration; the core of the search path is MCP SDK → Vector Service → Repository → pgvector.

Why these technologies were chosen

Technology

Reason chosen

Obsidian Markdown

Chosen because the source is a plain-text file, giving high ownership and portability, and keeping the knowledge source from being tied to a specific SaaS

MCP

Chosen to expose the same knowledge tool through a standard interface without building separate integration code for each LLM client

TypeScript

Chosen because it integrates naturally with the MCP SDK and lets you manage tool input and output boundaries with types

PostgreSQL

Chosen to consistently manage document metadata, status, and search results in one store while securing a production migration path

pgvector

Chosen to handle source metadata and vector search together inside PostgreSQL without adding a separate vector DB

Notion adapter

Chosen to show a boundary for syncing knowledge fragments to an external workspace when needed, rather than using Notion as the source store

Docker

Chosen to keep PostgreSQL and MCP execution conditions consistent across local and deployment environments

The key is not adding many tools. It is preserving the source as Markdown, keeping derived search data in PostgreSQL/pgvector, and exposing only the necessary functionality to LLMs via MCP.

Therefore, this project is not a system that uses Obsidian, Notion, and PostgreSQL all as source stores.

  • Obsidian Markdown: source knowledge

  • PostgreSQL/pgvector: derived search index

  • Notion: optional external sync target

  • MCP: LLM access boundary

Design points

  • Preserve original Markdown

  • Separate MCP tools from the service layer

  • NoteChunk domain model and VectorRepository port

  • Replaceable storage boundary with PostgreSQL/pgvector

  • Excludes real personal vault and credentials

This project is a public example for understanding the structure of MCP and knowledge search.

License

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