Obsidian 검색 MCP 만들기 — Claude Code 입문자가 옵시디언 vault를 한 줄로 검색하기 (2026 5월)
- 만들 도구:
obsidian_search(query, days)— 본인 옵시디언 vault에서 키워드로 검색하는 MCP 서버 한 개 - 왜 만드나: “어제 vault에 그 API 키 어디 적었지?”를 매번 폴더 경로 알려주고·권한 승인하고·grep 안내까지 반복하는 흐름을 한 줄 호출로 줄이려고
- 옵시디언 특화 처리:
.obsidian/설정 폴더 자동 제외·frontmatter title 우선·iCloud 동기화 경로·Daily Notes 한정 옵션 - 시간: 약 90분 (mcp-builder로 코드 위임 30분 + stdout 점검·Inspector 검증·등록 30분 + 다음날 실사용 회고 30분)
- 의존성: Claude Code + Node.js 18+ + mcp-builder 스킬. API 토큰·Obsidian Sync 유료 플랜 0
이 가이드는 macOS + Obsidian 기준(Linux 동일). Windows는 WSL2 권장(또는 경로 표기를 PowerShell 형식으로 변환).
적용 ✅: Obsidian(메인), Logseq, Foam, Dendron, VS Code로 직접 관리하는 마크다운 폴더
적용 ❌: macOS 기본 메모.app·Bear·Apple Notes(sqlite/CoreData), Notion·Evernote·OneNote(자체 API), Windows 기본 메모장(.txt)
옵시디언 vault에 매일 일기·작업 로그·아이디어 메모를 남기는데, 며칠 전 노트를 다시 찾을 때마다 클로드에게 vault 경로를 알려주고 → 권한 승인하고 → grep 명령을 안내하는 일을 하루 1~2번씩 반복하고 있었습니다. 그 한 사이클을 MCP 서버 도구 1개로 줄이는 게 이 글의 목표입니다.
이 글은 Claude Code를 쓰기 시작한 지 1~4주차 입문자를 대상으로, obsidian_search라는 도구 한 개를 처음부터 끝까지 만들어 등록하는 90분 가이드입니다. 4월에 발행한 mcp-builder 스킬 정리 글이 공식 가이드 정리 각도였다면, 이 글은 옵시디언 사용자가 그 스킬로 첫 도구를 어떻게 살리나에 가깝습니다.

1. 왜 만드나 — “어제 vault에 그거 어디 적었지?”
옵시디언으로 매일 마크다운 노트를 남기는 분이라면 다음 흐름이 익숙할 겁니다.
나: 옵시디언 vault에서 'API 키' 검색해 줘.
클로드: vault 폴더 경로가 어디인가요?
나: ~/Documents/Obsidian/MyVault
클로드: 폴더 읽기 권한 승인해 주세요.
나: [승인]
클로드: [Glob/Grep] ...
이걸 매일 1~2번씩 반복하는데, 매번 vault 경로 알려주기와 권한 승인에 1분쯤 들어갑니다. 한 달이면 30~60번.
MCP 서버 도구 1개를 만들면 구조가 다음처럼 단순해집니다. 한 번 가르쳐 두면 다음부터 클로드가 자동으로 도구를 호출합니다.
flowchart LR
U["👤 나<br/>(한 줄 질문)"]
C["🤖 Claude Code"]
M["📦 obsidian_search<br/>MCP 서버"]
V[("📁 Obsidian vault<br/>~/Obsidian/MyVault")]
U -- "질문" --> C
C -- "도구 호출" --> M
M -- "읽기" --> V
V -- ".md 파일" --> M
M -- "JSON 결과" --> C
C -- "답변" --> U
classDef purple fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#ffffff
classDef indigo fill:#4f46e5,stroke:#3730a3,stroke-width:2px,color:#ffffff
classDef gray fill:#e5e7eb,stroke:#6b7280,stroke-width:2px,color:#111827
class M purple
class C indigo
class V,U gray
핵심은 **“한 번 가르쳐 두는 도구가 매번 묻는 5턴을 1턴으로 줄인다”**는 점입니다. 클로드는 도구 이름과 설명만 보고 적절한 상황에 자동으로 호출합니다.
옵시디언 커뮤니티 플러그인이 있는데 왜 직접 만드나?
옵시디언 커뮤니티 플러그인 중 Obsidian Local REST API 같은 옵션도 있습니다. 다만 ① Obsidian 앱이 항상 켜져 있어야 동작하고, ② Claude Code가 별도 HTTP 클라이언트로 호출하는 구조라 Claude Code의 표준 도구 인터페이스에 자연스럽게 연결되지 않습니다.
직접 만든 stdio MCP 서버는 ① Obsidian 앱이 꺼져 있어도 vault 폴더만 있으면 검색 가능하고, ② Claude Code가 도구 이름만 보고 자동으로 호출합니다. 매일 호출되는 도구일수록 이 두 가지가 중요해집니다.
또 직접 만들면 다음을 옵시디언 사용 패턴에 맞게 처리할 수 있습니다.
- frontmatter
title인식: vault에 frontmatter를 주로 쓰는 사용자는 파일명보다 title이 중요 .obsidian/자동 제외: 설정 JSON·플러그인 코드까지 검색되면 노이즈가 큼- 매치 컨텍스트 ±2줄: wiki-link
[[...]]로 연결된 노트의 맥락 한눈에 파악 - mtime 기반 날짜 필터: 최근 N일 내 수정한 노트만 — 어제·지난주 회고에 가장 자주 사용
2. 만들 도구 — obsidian_search 한 개의 스펙
| 항목 | 값 |
|---|---|
| 도구 이름 | obsidian_search |
| 입력 | query: string (필수), days: number (선택, 기본 30, 최대 365) |
| 출력 | 매치된 노트 목록 — 경로·title·매치 라인(±2줄)·수정시각 |
| 검색 대상 | 옵시디언 vault 폴더 안의 *.md (하위 폴더 재귀, .obsidian/ 자동 제외) |
옵시디언 vault 경로는 사용자 환경에 따라 위치가 다릅니다.
| 환경 | 일반적인 vault 경로 |
|---|---|
| macOS / 로컬 폴더 | ~/Documents/Obsidian/MyVault/ |
| macOS / iCloud Drive 동기화 | ~/Library/Mobile Documents/iCloud~md~obsidian/Documents/MyVault/ |
| macOS / Obsidian Sync | 위 둘 중 하나(사용자가 선택한 위치) |
| Linux | ~/Obsidian/MyVault/ 또는 ~/Documents/Obsidian/MyVault/ |
| Windows (WSL2) | /mnt/c/Users/<이름>/Documents/Obsidian/MyVault/ |
본인 vault 경로를 모르겠으면 옵시디언 앱 → 설정 → “About”에서 Vault location 또는 macOS Finder에서 vault 폴더를 우클릭 → “정보 가져오기”의 위치 항목을 확인하면 됩니다.
vault 구조는 다음을 가정합니다(파일·폴더 이름 규칙은 자유).
~/Documents/Obsidian/MyVault/
Daily/
2026-05-10.md
2026-05-09.md
Inbox/
api-키-발급.md
Projects/
mcp-server.md
.obsidian/ # ← 자동 제외 (설정·플러그인)
workspace.json
plugins/
attachments/ # ← .md 필터로 자연스레 제외 (이미지·PDF)
screenshot.png
각 노트의 frontmatter는 있어도 없어도 동작합니다.
---
title: API 키 발급 절차
tags: [api, infra]
date: 2026-05-08
---
# API 키 발급 절차
1. AWS 콘솔 접속
2. ...
호출 한 줄이 끝나면 클로드가 다음과 같은 JSON 응답을 받습니다.
{
"matches": [
{
"path": "Daily/2026-05-08.md",
"title": "API 키 새로 받음",
"context": ["오늘 인프라 미팅에서", "API 키 새로 받음 — sk-...", "1주일 안에 키 회전 예정"],
"mtime": "2026-05-08T14:23:11+09:00"
}
]
}
3. 90분 빌드 흐름 — 5단계
도구 스펙이 정해졌으면 다섯 단계로 만듭니다. LLM이 할 일(보라)과 사람이 할 일(주황)이 명확히 분리됩니다.
flowchart TD
S1["1단계 · mcp-builder 스킬 호출<br/>🤖 LLM · 30분"]
S2["2단계 · stdout 오염 점검<br/>👤 사람 · 5분"]
S3["3단계 · Inspector로 검증<br/>👤 사람 · 15분"]
S4["4단계 · Claude Code 등록<br/>👤 사람 · 10분"]
S5["5단계 · 다음날 실사용 회고<br/>👤 사람 · 30분"]
S1 --> S2 --> S3 --> S4 --> S5
classDef llm fill:#7c3aed,stroke:#5b21b6,stroke-width:2px,color:#ffffff
classDef human fill:#ea580c,stroke:#c2410c,stroke-width:2px,color:#ffffff
class S1 llm
class S2,S3,S4,S5 human
3-1. mcp-builder 스킬에 코드 위임 — 30분 (LLM)
빈 폴더 + Claude Code 시작.
mkdir -p ~/projects/obsidian-mcp && cd ~/projects/obsidian-mcp
npm init -y
claude
mcp-builder 스킬을 미리 설치(없으면 한 줄).
npx skills add https://github.com/anthropics/skills --skill mcp-builder -a claude-code
Claude Code 안에서 다음을 입력합니다. 옵시디언 vault임을 명시하고 .obsidian/ 제외를 못 박는 게 핵심.
/mcp-builder
옵시디언 vault에서 키워드로 마크다운 노트를 검색하는 MCP 서버를 TypeScript stdio로 만들어 줘.
도구는 obsidian_search 하나. 입력: query (string, required), days (number, default 30, max 365).
출력: 매치 노트의 path (vault 기준 상대경로), title (frontmatter title 또는 첫 # 헤딩),
context (매치 라인 ±2줄), mtime.
- vault 경로는 OBSIDIAN_VAULT_DIR 환경변수에서 읽기.
- .obsidian/ 폴더는 검색 대상에서 자동 제외.
- 서버 시작 로그는 반드시 console.error로 (stdio 통신 보호).
스킬이 4단계(리서치 → 구현 → 테스트 → 평가)로 정리해 코드를 만들어 줍니다. 받게 되는 코드는 다음과 같은 형태입니다.
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readdir, readFile, stat } from "node:fs/promises";
import { join, relative } from "node:path";
import { homedir } from "node:os";
const VAULT_DIR = process.env.OBSIDIAN_VAULT_DIR
?? join(homedir(), "Documents", "Obsidian", "MyVault");
const server = new McpServer({ name: "obsidian-search", version: "0.1.0" });
server.registerTool(
"obsidian_search",
{
title: "Obsidian Search",
description: "본인 옵시디언 vault에서 키워드로 마크다운 노트를 검색합니다. frontmatter title·매치 컨텍스트(±2줄)·수정시각을 함께 반환하며 .obsidian/ 설정 폴더는 제외됩니다.",
inputSchema: {
query: z.string().min(1).describe("검색 키워드"),
days: z.number().int().min(1).max(365).default(30).describe("최근 N일 내 수정된 노트만"),
},
},
async ({ query, days }) => {
const since = Date.now() - days * 86_400_000;
const files = await collectMd(VAULT_DIR);
const matches = [];
for (const path of files) {
const st = await stat(path);
if (st.mtimeMs < since) continue;
const text = await readFile(path, "utf8");
const lines = text.split("\n");
const hitIdx = lines.findIndex((l) => l.toLowerCase().includes(query.toLowerCase()));
if (hitIdx === -1) continue;
matches.push({
path: relative(VAULT_DIR, path),
title: extractTitle(text, path),
context: lines.slice(Math.max(0, hitIdx - 2), hitIdx + 3),
mtime: st.mtime.toISOString(),
});
}
return { content: [{ type: "text", text: JSON.stringify({ matches }, null, 2) }] };
}
);
// .obsidian/ 자동 제외
async function collectMd(dir: string): Promise<string[]> {
const out: string[] = [];
for (const entry of await readdir(dir, { withFileTypes: true })) {
if (entry.name === ".obsidian" || entry.name.startsWith(".")) continue;
const full = join(dir, entry.name);
if (entry.isDirectory()) out.push(...(await collectMd(full)));
else if (entry.name.endsWith(".md")) out.push(full);
}
return out;
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.error(`obsidian-search MCP started (vault=${VAULT_DIR})`);
extractTitle(frontmatter 우선, 없으면 첫 # 헤딩 폴백) 헬퍼는 스킬이 자동으로 같이 작성합니다. 설치는 한 줄.
npm i @modelcontextprotocol/sdk zod && npm i -D typescript @types/node
[!IMPORTANT] import 경로가 고레벨
McpServer(server/mcp.js)인지 받자마자 확인하세요. LLM이 가끔 저레벨Server(server/index.js)로 만들기도 하는데, 그쪽은registerTool메서드 자체가 없어서 동작하지 않습니다.
3-2. stdout 오염 점검 — 5분 (사람)
LLM이 만든 코드에 console.log가 한 줄이라도 남아 있으면 stdio 통신이 깨집니다.
# macOS · Linux · WSL 공통
grep -rn "console.log" src/
발견되면 IDE에서 일괄 치환합니다(에디터 단축키가 OS 종속 sed보다 안전·범용). VS Code·Cursor 기준 Cmd/Ctrl + Shift + H로 전체 폴더 검색·치환을 열고 console.log → console.error로 바꾸면 끝입니다.
stdout이 클로드와의 JSON-RPC 통신 채널이라, 로그 한 줄이 끼어드는 순간 Server transport closed 에러가 납니다. 이 5분이 등록 후 1시간 디버깅을 막아줍니다.
3-3. Inspector로 검증 — 15분 (사람)
도구를 만들었으면 클로드에게 등록하기 전에 단독으로 동작하는지 검증합니다.
# 빌드
npx tsc
# Inspector 띄우기 (vault 경로를 환경변수로 주입)
OBSIDIAN_VAULT_DIR="$HOME/Documents/Obsidian/MyVault" \
npx @modelcontextprotocol/inspector node build/index.js
브라우저가 자동으로 열립니다. Tools 탭 → obsidian_search 클릭 → 입력란에 다음을 채웁니다.
| 필드 | 값 |
|---|---|
| query | API 키 (또는 본인 vault에 있는 키워드) |
| days | 7 |
Run 버튼 → 응답 패널에 매치된 노트 목록이 JSON으로 떨어지면 통과입니다. 결과가 비어 있으면 ① vault 경로가 맞는지(OBSIDIAN_VAULT_DIR) ② 최근 7일 내 수정된 노트가 있는지 ③ 키워드가 실제 본문에 있는지 순서로 점검하세요.
3-4. Claude Code 등록 — 10분 (사람)
수동 JSON 편집 대신 공식 CLI를 씁니다. 절대 경로 필수.
# macOS · Linux 예시 (vault가 ~/Documents/Obsidian/MyVault)
claude mcp add obsidian \
-e OBSIDIAN_VAULT_DIR=/Users/<your-name>/Documents/Obsidian/MyVault \
-- node /Users/<your-name>/projects/obsidian-mcp/build/index.js
# macOS / iCloud 동기화 vault인 경우
claude mcp add obsidian \
-e OBSIDIAN_VAULT_DIR="/Users/<your-name>/Library/Mobile Documents/iCloud~md~obsidian/Documents/MyVault" \
-- node /Users/<your-name>/projects/obsidian-mcp/build/index.js
# Windows (WSL2) — /mnt/c/... 형식
claude mcp add obsidian \
-e OBSIDIAN_VAULT_DIR=/mnt/c/Users/<YourName>/Documents/Obsidian/MyVault \
-- node /mnt/c/Users/<YourName>/projects/obsidian-mcp/build/index.js
# 등록 확인
claude mcp list
등록 후 Claude Code 세션을 한 번 재시작하고 /mcp 명령으로 상태를 확인합니다. 절대 경로는 macOS/Linux/WSL pwd, Windows PowerShell Get-Location로 확인할 수 있습니다.
- ① 절대 경로 — 상대 경로(
./build/index.js)는 실패합니다.pwd로 확인한 절대 경로 사용. - ② Claude Code 재시작 — 등록 후 한 번 재시작이 거의 항상 필요.
/mcp로 상태 확인. - ③ 빌드 산출물 확인 —
npx tsc후build/index.js가 실제로 존재하는지. - ④ 권한 승인 — 첫 호출 때 권한 요청. 거절하면 도구가 회색.
3-5. 다음날 실사용 회고 — 30분 (사람)
하루 동안 진짜로 호출되는지 봅니다. 첫날에는 의식적으로 다음과 같이 호출해 봅니다.
- “지난주 vault에서 ‘API 키’ 들어간 노트 찾아줘”
- “어제 Daily Note에 회의 일정 적힌 거 있어?”
- “이번 달 노트에서 ‘TODO’ 카운트해 줘”
저는 첫날 다섯 번 호출했고, 둘째 날부터는 의식하지 않아도 클로드가 자동으로 obsidian_search를 호출하기 시작했습니다. 매일 1번 이상 호출되는지가 살아남는 도구의 기준입니다.
4. Before / After — 시퀀스로 보는 차이
빌드가 끝나면 매일의 vault 검색 흐름이 다음처럼 바뀝니다. 같은 요청이 5턴 → 1턴으로 줄어드는 게 핵심.
❌ Before — 매번 5턴 (사람이 경로·권한·도구를 매번 안내)
sequenceDiagram
autonumber
actor U as 나
participant C as Claude Code
U->>C: vault에서 'API 키' 검색해 줘
C->>U: vault 경로가 어디인가요?
U->>C: ~/Documents/Obsidian/MyVault
C->>U: 읽기 권한 승인해 주세요
U->>C: 승인
C-->>U: [Glob/Grep 호출] 결과
✅ After — 1턴 (도구 한 개가 경로·필터·결과 포맷을 다 처리)
sequenceDiagram
autonumber
actor U as 나
participant C as Claude Code
participant M as obsidian_search MCP
participant V as Obsidian vault
U->>C: 지난주 vault에 'API 키' 노트 찾아줘
C->>M: obsidian_search(query="API 키", days=7)
M->>V: 최근 7일 .md 파일 검색
V-->>M: 매치 노트 2개
M-->>C: JSON 결과
C-->>U: Daily/2026-05-08.md — API 키 새로 받음<br/>Inbox/api-키-발급.md — 발급 절차
매일 1~2번 호출된다고 가정하면, 한 달이면 60턴 → 30턴으로 줄어듭니다. 90분 투자로 매달 30분이 회수되는 셈이고, 두 번째 달부터는 순이익입니다.
5. 다음 도구 1개 — 처음부터 5개 만들지 마세요
obsidian_search가 살아남으면(매일 1번 이상 호출되면) 짝이 되는 도구 1개만 추가합니다. 처음부터 5개 만들면 살아남지 않은 도구가 컨텍스트를 차지해서 오히려 클로드의 도구 선택이 흐려집니다.
옵시디언 사용자에게 추천하는 다음 도구는 다음 둘 중 하나입니다.
obsidian_get(path)— 검색 결과 후 전체 노트 내용을 가져올 때. 검색 → 조회 흐름이 자연스러워집니다.obsidian_today()— 오늘 날짜의 Daily Note를 자동으로 열거나 생성. Daily Notes 코어 플러그인 사용자에게 활용도가 큽니다.
mcp-builder 스킬을 두 번째에 쓰면 코드 위임 30분 → stdout 점검 5분 → Inspector 검증 5분 → 등록 5분까지 줄어들어 총 45분이면 추가됩니다. 첫 빌드보다 절반.