NoteHarbor MCP
Connects Obsidian Markdown notes as searchable knowledge chunks via MCP tools, enabling semantic vector search and CRUD operations through a layered service architecture.
README
NoteHarbor MCP — TypeScript
Obsidian Markdown을 MCP 도구로 연결하고 PostgreSQL/pgvector 검색으로 확장하는 TypeScript 예제입니다.
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
실제 벡터화 흐름
upsert_vector는 이미 만든 embedding을 저장하는 도구입니다. Markdown이 chunk와 벡터로 바뀌는 과정은 index_note에서 확인할 수 있습니다.
Markdown text
→ splitMarkdownIntoChunks()
→ EmbeddingProvider.embed(chunk)
→ normalized number[]
→ NoteChunk
→ VectorService.indexChunk()
→ VectorRepository.save()
주요 코드는 다음 파일에 나뉘어 있습니다.
- chunker.ts: Markdown을 paragraph 기준으로 나누고 최대 길이를 적용
- embeddingProvider.ts: API 키 없이 동작하는 결정적 데모 임베딩 provider
- indexingPipeline.ts: chunk 생성·임베딩·저장을 연결
- knowledge.ts:
EmbeddingProvider와VectorRepository포트 - index.ts:
index_note,upsert_vector,search_vectorsMCP 도구 등록
데모 provider는 흐름을 확인하기 위한 결정적 구현입니다. 의미 기반 검색 품질을 제공하는 모델은 아니며, 실제 서비스에서는 같은 EmbeddingProvider 포트에 외부나 로컬 모델을 연결합니다.
양자화는 임베딩 생성 뒤에 적용합니다.
float embedding [-1, 1]
→ clamp
→ int8 = round(value / (1 / 127))
→ 저장: values + scale + zeroPoint
→ 복원: (int8 - zeroPoint) * scale
샘플은 대칭형 scalar INT8 양자화를 사용합니다.
- 값 범위:
[-1, 1] - 양자화 범위:
[-127, 127] scale:1 / 127zeroPoint:0- 원본 임베딩과 함께
embedding_int8,embedding_scale,embedding_zero_point를 저장하도록 스키마를 정의 - 현재 참조 검색은 복원 가능한 float vector를 사용하며, 양자화 ANN 인덱스는 실제 adapter 선택 시 추가
구현은 quantizer.ts와 indexingPipeline.ts에 있습니다. index_note 응답에도 임베딩 차원과 양자화 비트 수가 포함됩니다.
왜 이렇게 구성했는가
NoteHarbor는 Obsidian Markdown을 검색 가능한 지식 단위로 바꾸고, 그 기능을 MCP 도구로 제공하는 예제입니다.
단순 문자열 검색만으로는 표현이 다른 관련 내용을 찾기 어렵습니다. 그래서 노트를 작은 chunk로 나누고, 각 chunk를 embedding으로 바꿔 의미가 가까운 내용을 검색할 수 있게 합니다.
양자화는 이 embedding을 더 작은 표현으로 보관하기 위한 선택입니다.
- 메모리와 저장 공간을 줄임
- 벡터 전송량을 줄임
- 대규모 지식베이스에서 캐시와 배치 처리에 유리
- 대신 원본 float보다 정밀도가 낮아질 수 있음
그래서 각 구성요소의 역할을 다음처럼 나눴습니다.
- Embedding: 텍스트의 의미를 수치 벡터로 표현
- Quantization: 벡터의 정밀도를 낮춰 저장 비용을 줄임
- Vector search: 가까운 벡터를 찾아 관련 chunk를 반환
- MCP: 이 기능을 LLM 클라이언트가 호출할 수 있는 도구로 노출
이 프로젝트에서 INT8을 선택한 이유는 양자화 원리와 저장 형태를 코드로 설명하기 쉽기 때문입니다. 실제 서비스에서는 검색 품질, 메모리 절감, 지연시간을 측정한 뒤 float32·float16·INT8·binary 중 하나를 선택해야 합니다.
현재 구현은 흐름을 확인하기 위한 목업입니다. 의미 기반 검색 품질이나 양자화 성능을 보장하지 않으며, 실제 운영에서는 embedding provider와 pgvector adapter를 연결해야 합니다.
벡터 DB 설계
벡터 저장 단위는 NoteChunk입니다.
| 필드 | 의미 |
|---|---|
id |
원본 경로와 chunk 순번으로 만든 식별자 |
sourcePath |
Obsidian Markdown 원본 경로 |
chunkIndex |
문서 안의 chunk 순번 |
content |
검색 결과로 되돌릴 텍스트 |
embedding |
외부 임베딩 모델이 생성한 벡터 |
metadata |
태그·상태·원본 속성 등 확장 정보 |
실행 가능한 SQL 설계 예시는 src/infrastructure/postgres/schema.sql에 있습니다. 기본 예시는 1,536차원 임베딩과 cosine distance용 HNSW 인덱스를 사용하며, 실제 임베딩 모델의 차원에 맞춰 조정합니다.
검색은 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의 수명주기를 관리하는 Repository입니다.
- Create/Upsert:
upsert_vector,index_note→VectorService.indexChunk() - Read:
get_chunk,list_chunks - Update:
update_chunk→ 기존 chunk를 읽고 변경 필드와 양자화 표현을 함께 갱신 - Delete:
delete_chunk - Search:
search_vectors,search_knowledge
현재 실행 어댑터는 Map입니다. 운영 환경에서는 Repository 안쪽 구현을 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 순서로 CRUD와 검색을 넘깁니다.
포함된 샘플
- Obsidian Markdown 목록·읽기·검색
index_note,search_knowledge,upsert_vector,search_vectorsMCP 도구MCP → Service → Repository → pgvector계층- Drizzle ORM 기반
note_chunks스키마 매핑 - PostgreSQL/pgvector 스키마와 검색 SQL
- Notion 동기화 경계
- Smithery 개발 서버와 Docker 실행 예시
현재 기본 실행은 개인 데이터와 외부 자격증명을 포함하지 않는 메모리 목업입니다. PostgreSQL 연결에서는 src/infrastructure/postgres/postgresVectorRepository.ts의 Map 구현을 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 순번, 태그, 상태, 동기화 정보 같은 관계형 메타데이터를 함께 다뤄야 합니다.
그래서 이 샘플에서는 별도 벡터 DB를 추가하기보다 PostgreSQL에 다음을 함께 둡니다.
content: 검색 결과로 보여줄 원문 조각metadata: 태그·상태·원본 정보embedding: cosine 검색용 float vectorembedding_int8: 저장·전송 비용을 줄이기 위한 양자화 표현
pgvector를 선택한 구체적인 이유는 다음과 같습니다.
- 데이터 결합: 벡터 유사도와
source_path, 태그, 상태 조건을 한 SQL에서 조합할 수 있음 - 일관성: 원문 메타데이터와 검색 인덱스를 같은 트랜잭션 경계에서 관리할 수 있음
- 운영 단순성: 애플리케이션이 PostgreSQL과 별도 벡터 DB를 각각 운영하지 않아도 됨
- 검색 기능: cosine distance(
<=>)와 HNSW 인덱스를 PostgreSQL 확장으로 사용할 수 있음 - 확장 경로: 초기에는 단일 저장소로 시작하고, 규모가 커질 때 검색 전용 adapter를 분리할 수 있음
검색 규모가 커지면 전용 벡터 DB가 더 적합할 수 있습니다. 여기서는 원문, metadata, 벡터 검색을 한 애플리케이션 경계에서 다루는 흐름을 보여주는 데 의미가 있습니다.
검색은 어떻게 동작하는가
검색은 원문 문자열을 직접 비교하지 않고, 질문과 노트 chunk를 같은 embedding 공간에 놓은 뒤 거리를 비교합니다.
사용자 질문
→ query embedding 생성
→ INT8 양자화 후 복원
→ PostgreSQL/pgvector cosine distance 검색
→ 가까운 NoteChunk 반환
→ sourcePath·content·metadata와 함께 MCP 응답
실행 가능한 샘플은 search_knowledge MCP 도구입니다.
index_note가 Markdown을 chunk로 나누고 각 chunk의 embedding을 저장합니다.- 사용자가 자연어
query를 보냅니다. - 같은
EmbeddingProvider로 query embedding을 생성합니다. - query를 저장 벡터와 같은 방식으로 양자화·복원합니다.
VectorRepository.search()가 cosine similarity 기준으로 가까운 chunk를 정렬합니다.- 검색 결과에는 원본 경로, chunk 내용, metadata, score가 포함됩니다.
search_vectors는 이미 만들어진 embedding을 직접 받는 저수준 도구이고, search_knowledge는 자연어 질문부터 검색 결과까지 연결하는 응용 수준 도구입니다.
현재 목업 Repository는 메모리 Map에서 cosine similarity를 계산합니다. PostgreSQL adapter로 전환하면 같은 포트 뒤에서 pgvector의 <=> 연산과 LIMIT 검색을 사용하게 됩니다.
추가 기술과 투입 이유
| 기술 | 투입 이유 |
|---|---|
| Node.js ESM | TypeScript MCP 샘플을 현재 Node 런타임 방식으로 단순하게 실행하기 위해 |
| MCP SDK | index_note, search_knowledge, upsert_vector, search_vectors 도구를 표준 MCP 서버로 등록하기 위해 |
| Zod | MCP 입력값과 설정값을 런타임에서 검증하기 위해 |
| Drizzle ORM | PostgreSQL adapter를 연결할 때 타입 안전한 스키마·쿼리 경계를 제공하기 위해 |
| pg | 실제 PostgreSQL 연결 adapter로 전환할 때 사용할 드라이버 |
| Smithery CLI | MCP 서버를 개발·검증하는 실행 경로를 제공하기 위해 |
| Docker | 로컬과 배포 환경의 Node/MCP 실행 조건을 고정하기 위해 |
| chokidar·fast-glob | Markdown vault 변경 감지와 파일 탐색을 담당하기 위해 |
| gray-matter·marked | Markdown frontmatter와 본문을 지식 chunk로 다루기 위해 |
| dotenv | 로컬 환경 설정을 코드와 분리하기 위해 |
모든 의존성이 벡터 검색의 핵심은 아닙니다. 일부는 Obsidian·Notion 연동을 위한 보조 기술이고, 검색 경로의 중심은 MCP SDK → Vector Service → Repository → pgvector입니다.
기술을 선택한 이유
| 기술 | 선택 이유 |
|---|---|
| Obsidian Markdown | 원본이 평문 파일이라 소유권과 이동성이 높고, 특정 SaaS에 지식 원본을 종속시키지 않기 위해 선택 |
| MCP | LLM 클라이언트마다 별도 연동 코드를 만들지 않고, 동일한 지식 도구를 표준 인터페이스로 노출하기 위해 선택 |
| TypeScript | MCP SDK와의 연결이 자연스럽고, 도구 입력·출력 경계를 타입으로 관리하기 위해 선택 |
| PostgreSQL | 문서 메타데이터·상태·검색 결과를 한 저장소에서 일관되게 관리하고, 운영 전환 경로를 확보하기 위해 선택 |
| pgvector | 별도 벡터 DB를 추가하지 않고 PostgreSQL 안에서 원문 메타데이터와 벡터 검색을 함께 다루기 위해 선택 |
| Notion adapter | Notion을 원본 저장소로 삼기보다, 필요한 경우 지식 조각을 외부 워크스페이스로 동기화하는 경계를 보여주기 위해 선택 |
| 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
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.