@sunub/obsidian-mcp-server 0.3.20 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -451
- package/build/cli/App.js +42 -16
- package/build/cli/App.js.map +1 -1
- package/build/cli/config/mcpServersConfig.js +0 -4
- package/build/cli/config/mcpServersConfig.js.map +1 -1
- package/build/cli/config/settingSchema.d.ts +0 -18
- package/build/cli/config/settingSchema.js +0 -18
- package/build/cli/config/settingSchema.js.map +1 -1
- package/build/cli/hooks/useLlmStream/useLlmStream.d.ts +1 -1
- package/build/cli/hooks/useLlmStream/useLlmStream.js +4 -3
- package/build/cli/hooks/useLlmStream/useLlmStream.js.map +1 -1
- package/build/cli/hooks/useMcpClient.js +0 -4
- package/build/cli/hooks/useMcpClient.js.map +1 -1
- package/build/cli/index.js +1 -0
- package/build/cli/index.js.map +1 -1
- package/build/cli/ui/LLMErrorComponent.js +1 -1
- package/build/cli/ui/LLMErrorComponent.js.map +1 -1
- package/build/cli/utils/terminal.d.ts +15 -0
- package/build/cli/utils/terminal.js +51 -0
- package/build/cli/utils/terminal.js.map +1 -0
- package/build/config.d.ts +0 -6
- package/build/config.js +0 -8
- package/build/config.js.map +1 -1
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/build/server.js +1 -2
- package/build/server.js.map +1 -1
- package/build/setup.js +0 -1
- package/build/setup.js.map +1 -1
- package/build/shared/configSchema.d.ts +4 -4
- package/build/tools/create_document_with_properties/index.d.ts +2 -2
- package/build/tools/create_document_with_properties/params.d.ts +4 -4
- package/build/tools/generate_property/params.d.ts +2 -2
- package/build/tools/index.d.ts +4 -4
- package/build/tools/vault/index.d.ts +1 -1
- package/build/tools/vault/index.js +1 -1
- package/build/tools/vault/params.d.ts +2 -2
- package/build/tools/write_property/index.d.ts +2 -2
- package/build/tools/write_property/params.d.ts +4 -4
- package/build/utils/LLMClient.d.ts +0 -4
- package/build/utils/LLMClient.js +0 -43
- package/build/utils/LLMClient.js.map +1 -1
- package/build/utils/RAGIndexer.js +3 -6
- package/build/utils/RAGIndexer.js.map +1 -1
- package/build/utils/RerankerClient.js +1 -1
- package/build/utils/RerankerClient.js.map +1 -1
- package/build/utils/VaultManger/VaultManager.d.ts +0 -3
- package/build/utils/VaultManger/VaultManager.js +10 -13
- package/build/utils/VaultManger/VaultManager.js.map +1 -1
- package/build/utils/VectorDB.js +3 -5
- package/build/utils/VectorDB.js.map +1 -1
- package/package.json +1 -1
- package/build/db.d.ts +0 -1
- package/build/db.js +0 -55
- package/build/db.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,131 +4,67 @@
|
|
|
4
4
|
|
|
5
5
|
`obsidian-mcp-server`는 Obsidian Vault의 Markdown 문서를 AI 에이전트가 조회하고, 검색하고, 요약할 수 있게 해주는 MCP 서버입니다.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
이 프로젝트는 단순히 문서를 읽어오는 것을 넘어, **transformers.js를 활용한 로컬 하이브리드 검색** 기능을 제공하며, 터미널에서 즉시 Vault와 대화할 수 있는 **대화형 CLI AI Agent UI**를 포함하고 있습니다.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## 주요 특징
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- 🔍 **하이브리드 검색:** 키워드 검색과 시맨틱(벡터) 검색을 결합하고 RRF(Reciprocal Rank Fusion)와 Reranking을 통해 최적의 결과 제공.
|
|
12
|
+
- 🚀 **Zero-Dependency 로컬 AI:** `@huggingface/transformers`를 사용하여 임베딩 및 리랭킹 모델을 Node.js 프로세스 내에서 직접 실행 (외부 API 서버 불필요).
|
|
13
|
+
- 💬 **내장 CLI 에이전트:** MCP 도구들을 활용하여 Vault 내용에 대해 질문하고 답변을 받을 수 있는 터미널 기반 UI 제공. [상세 보기](#대화형-cli-ai-agent-ui)
|
|
14
|
+
- 📦 **토큰 최적화:** AI 에이전트의 토큰 사용량을 제어하기 위한 다양한 압축 모드와 출력 제한 기능 제공.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
- 특정 노트 열람 (`vault`, `action="read"`)
|
|
15
|
-
- Vault 전체 문서 목록 조회 (`vault`, `action="list_all"`)
|
|
16
|
-
- Vault 상태 조회 (`vault`, `action="stats"`)
|
|
17
|
-
- RAG 기반 검색 (`vault`, `action="search_vault_by_semantic"`) - LLM 서버(llama.cpp) 연동 필수
|
|
18
|
-
- 전체 색인 트리거 (`vault`, `action="index_vault_to_vectordb"`)
|
|
19
|
-
- frontmatter 자동 생성 제안 (`generate_property`)
|
|
20
|
-
- frontmatter 실제 반영 (`write_property`)
|
|
21
|
-
- 이미지 첨부파일 정리 (`organize_attachments`)
|
|
16
|
+
## 무엇을 할 수 있나 (MCP Tools)
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
- **통합 검색 (`vault`, `action="search"`)**: 키워드와 의미 기반 검색을 동시에 수행하여 관련성 높은 문서 탐색.
|
|
19
|
+
- **문서 열람 (`vault`, `action="read"`)**: 특정 노트의 본문 및 메타데이터 조회.
|
|
20
|
+
- **전체 목록 및 상태 (`vault`, `action="list_all"|"stats"`)**: Vault의 전반적인 상태와 파일 목록 확인.
|
|
21
|
+
- **컨텍스트 수집 (`vault`, `action="collect_context"`)**: 특정 주제와 연관된 고밀도 지식 패킷 생성.
|
|
22
|
+
- **지식 로드 (`vault`, `action="load_memory"`)**: 저장된 메모리 스냅샷 호출.
|
|
23
|
+
- **Frontmatter 관리 (`generate_property`|`write_property`)**: AI 기반 메타데이터 생성 및 반영.
|
|
24
|
+
- **첨부파일 정리 (`organize_attachments`)**: 문서 내 이미지를 전용 폴더로 자동 이동 및 링크 업데이트.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- 신뢰할 수 없는 AI 에이전트와 연결하지 마세요.
|
|
28
|
-
- Vault 경로(`VAULT_DIR_PATH`)는 최소 권한으로 제한하세요.
|
|
29
|
-
- 조회량이 큰 Vault는 `maxOutputChars`, `limit`를 조절해 토큰 비용을 통제하세요.
|
|
30
|
-
- `vault` 액션 기본 압축 모드는 `balanced`입니다.
|
|
31
|
-
|
|
32
|
-
### 설정 확인사항
|
|
33
|
-
|
|
34
|
-
**1. Vault 경로: `VAULT_DIR_PATH`에는 반드시 절대 경로를 입력해야 합니다.**
|
|
26
|
+
---
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
# ✅ 올바른 예시 (macOS / Linux)
|
|
38
|
-
VAULT_DIR_PATH="/Users/username/Documents/MyVault"
|
|
28
|
+
## 설치 및 설정
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
VAULT_DIR_PATH="C:\\Users\\username\\Documents\\MyVault"
|
|
30
|
+
### 1. 사전 요구사항
|
|
42
31
|
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
- **Node.js**: v22.0.0 이상
|
|
33
|
+
- **Obsidian Vault**: 절대 경로를 알고 있어야 합니다.
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
VAULT_DIR_PATH="~/Documents/MyVault"
|
|
48
|
-
VAULT_DIR_PATH="./vault"
|
|
49
|
-
```
|
|
35
|
+
### 2. 로컬 AI 모델 설치 (필수)
|
|
50
36
|
|
|
51
|
-
|
|
37
|
+
시맨틱 검색 및 리랭킹 기능을 활성화하려면 아래 명령어를 통해 필요한 로컬 모델을 다운로드해야 합니다.
|
|
52
38
|
|
|
53
39
|
```bash
|
|
54
|
-
|
|
40
|
+
# 로컬 임베딩 및 리랭킹 모델 설치
|
|
41
|
+
npx @sunub/obsidian-mcp-server setup
|
|
55
42
|
```
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
### 환경변수 전체 설정 예시
|
|
60
|
-
|
|
61
|
-
MCP 클라이언트 설정(`mcpServers.env`) 또는 셸 환경변수로 주입합니다.
|
|
62
|
-
시맨틱 검색·리랭킹 기능을 사용하지 않을 경우 `VAULT_DIR_PATH`만 필수입니다.
|
|
44
|
+
또는 이미 패키지를 설치했다면:
|
|
63
45
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"mcpServers": {
|
|
69
|
-
"obsidian": {
|
|
70
|
-
"command": "npx",
|
|
71
|
-
"args": ["-y", "@sunub/obsidian-mcp-server@latest"],
|
|
72
|
-
"env": {
|
|
73
|
-
"VAULT_DIR_PATH": "/abs/path/to/your/vault"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
46
|
+
```bash
|
|
47
|
+
obsidian-mcp-server setup
|
|
78
48
|
```
|
|
79
49
|
|
|
80
|
-
|
|
50
|
+
이 명령어는 `Xenova/paraphrase-multilingual-MiniLM-L12-v2`(임베딩)와 `Xenova/bge-reranker-base`(리랭킹) 모델을 다운로드하여 로컬 캐시에 저장합니다.
|
|
81
51
|
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
"mcpServers": {
|
|
85
|
-
"obsidian": {
|
|
86
|
-
"command": "npx",
|
|
87
|
-
"args": ["-y", "@sunub/obsidian-mcp-server@latest"],
|
|
88
|
-
"env": {
|
|
89
|
-
"VAULT_DIR_PATH": "/abs/path/to/your/vault",
|
|
90
|
-
"LOGGING_LEVEL": "info",
|
|
91
|
-
"LLM_API_URL": "http://127.0.0.1:8080",
|
|
92
|
-
"LLM_EMBEDDING_API_URL": "http://127.0.0.1:8081",
|
|
93
|
-
"LLM_EMBEDDING_MODEL": "nomic-embed-text",
|
|
94
|
-
"LLM_CHAT_MODEL": "llama3",
|
|
95
|
-
"LLM_RERANKER_API_URL": "http://127.0.0.1:8082",
|
|
96
|
-
"VAULT_METRICS_LOG_PATH": "/abs/path/to/vault-metrics.ndjson"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
각 변수가 연결되는 서버는 아래와 같습니다:
|
|
52
|
+
### 3. 환경 변수 설정
|
|
104
53
|
|
|
105
54
|
| 환경변수 | 기본값 | 역할 | 필수 여부 |
|
|
106
55
|
|---|---|---|---|
|
|
107
56
|
| `VAULT_DIR_PATH` | — | Obsidian Vault 절대 경로 | **필수** |
|
|
57
|
+
| `LLM_API_URL` | `http://127.0.0.1:8080` | CLI UI용 채팅 모델 API 엔드포인트 | CLI 사용 시 필수 |
|
|
58
|
+
| `LLM_CHAT_MODEL` | `llama3` | 채팅에 사용할 모델명 | CLI 사용 시 필수 |
|
|
108
59
|
| `LOGGING_LEVEL` | `info` | 로그 수준 (`debug` / `info` / `warn` / `error`) | 선택 |
|
|
109
|
-
| `LLM_API_URL` | `http://127.0.0.1:8080` | llama-server 채팅 엔드포인트 | 선택 |
|
|
110
|
-
| `LLM_EMBEDDING_API_URL` | `http://127.0.0.1:8081` | llama-server 임베딩 엔드포인트 (벡터 색인 필수) | 선택 |
|
|
111
|
-
| `LLM_EMBEDDING_MODEL` | `nomic-embed-text` | 임베딩 모델명 | 선택 |
|
|
112
|
-
| `LLM_CHAT_MODEL` | `llama3` | 채팅 모델명 | 선택 |
|
|
113
|
-
| `LLM_RERANKER_API_URL` | `http://127.0.0.1:8082` | llama-server 리랭커 엔드포인트 (미설정 시 폴백) | 선택 |
|
|
114
|
-
| `VAULT_METRICS_LOG_PATH` | — | 응답 압축·토큰 메트릭 JSONL 기록 경로 | 선택 |
|
|
115
|
-
|
|
116
|
-
> `LLM_EMBEDDING_API_URL`이 없으면 `search_vault_by_semantic`과 `index_vault_to_vectordb`가 동작하지 않습니다.
|
|
117
|
-
> `LLM_RERANKER_API_URL`에 서버가 없으면 오류 없이 폴백(원래 정렬 유지)됩니다.
|
|
118
60
|
|
|
119
|
-
|
|
61
|
+
---
|
|
120
62
|
|
|
121
|
-
|
|
63
|
+
## MCP 클라이언트 설정 예시
|
|
122
64
|
|
|
123
|
-
|
|
124
|
-
- MCP 실행 자체는 배포 패키지 기준으로 맞춰 설명합니다.
|
|
125
|
-
- 배포 패키지(`npx`) 사용 (권장)
|
|
126
|
-
- 로컬 `build/index.js`는 개발/디버깅 목적
|
|
127
|
-
- 로컬 실행은 마지막 섹션(5)에서 분리 안내합니다.
|
|
128
|
-
- Vault 경로가 없으면 시작이 실패합니다.
|
|
129
|
-
- 아래 예시는 복붙으로 바로 사용할 수 있도록 정리했습니다.
|
|
65
|
+
각 클라이언트 설정에서 `env.VAULT_DIR_PATH`를 본인의 Vault 경로로 수정하여 사용하세요.
|
|
130
66
|
|
|
131
|
-
###
|
|
67
|
+
### Claude Desktop / Cursor / Copilot
|
|
132
68
|
|
|
133
69
|
```json
|
|
134
70
|
{
|
|
@@ -137,376 +73,58 @@ MCP 클라이언트 설정(`mcpServers.env`) 또는 셸 환경변수로 주입
|
|
|
137
73
|
"command": "npx",
|
|
138
74
|
"args": ["-y", "@sunub/obsidian-mcp-server@latest"],
|
|
139
75
|
"env": {
|
|
140
|
-
"VAULT_DIR_PATH": "/
|
|
141
|
-
"LOGGING_LEVEL": "info"
|
|
76
|
+
"VAULT_DIR_PATH": "/Users/username/Documents/MyVault"
|
|
142
77
|
}
|
|
143
78
|
}
|
|
144
79
|
}
|
|
145
80
|
}
|
|
146
81
|
```
|
|
147
82
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
npx -y @sunub/obsidian-mcp-server@latest --vault-path /abs/path/to/your/vault --logging-level info
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### 3) MCP Client configuration
|
|
155
|
-
|
|
156
|
-
클라이언트별 UI가 다르더라도 `command`/`args`/`env`의 기본 형태는 동일합니다.
|
|
157
|
-
|
|
158
|
-
> 배포 패키지 기준 핵심: `command="npx"`, `args=["-y","@sunub/obsidian-mcp-server@latest"]`, `env.VAULT_DIR_PATH`
|
|
159
|
-
|
|
160
|
-
<details>
|
|
161
|
-
<summary>Codex</summary>
|
|
162
|
-
`.codex/config.toml`에 아래처럼 등록합니다.
|
|
163
|
-
|
|
164
|
-
```toml
|
|
165
|
-
[mcp_servers.obsidian]
|
|
166
|
-
command = "npx"
|
|
167
|
-
args = ["-y", "@sunub/obsidian-mcp-server@latest"]
|
|
168
|
-
env = { VAULT_DIR_PATH = "/abs/path/to/your/vault" }
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
</details>
|
|
172
|
-
|
|
173
|
-
<details>
|
|
174
|
-
<summary>Copilot CLI</summary>
|
|
175
|
-
|
|
176
|
-
1) `copilot` 실행
|
|
177
|
-
2) `/mcp add`
|
|
178
|
-
3) 아래 값 입력
|
|
179
|
-
|
|
180
|
-
- **Server name:** `obsidian`
|
|
181
|
-
- **Server Type:** `[1] Local`
|
|
182
|
-
- **Command:** `npx -y @sunub/obsidian-mcp-server@latest`
|
|
183
|
-
- **Environment:** `{ "VAULT_DIR_PATH": "/abs/path/to/your/vault" }`
|
|
184
|
-
|
|
185
|
-
</details>
|
|
186
|
-
|
|
187
|
-
<details>
|
|
188
|
-
<summary>Copilot / VS Code</summary>
|
|
189
|
-
|
|
190
|
-
버전별 JSON 키 이름이 다를 수 있으므로(예: `servers`/`mcpServers`), 프로젝트 문서에 맞춰 적용하세요.
|
|
191
|
-
|
|
192
|
-
```json
|
|
193
|
-
{
|
|
194
|
-
"mcpServers": {
|
|
195
|
-
"obsidian": {
|
|
196
|
-
"command": "npx",
|
|
197
|
-
"args": ["-y", "@sunub/obsidian-mcp-server@latest"],
|
|
198
|
-
"env": { "VAULT_DIR_PATH": "/abs/path/to/your/vault" }
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
</details>
|
|
205
|
-
|
|
206
|
-
<details>
|
|
207
|
-
<summary>Cursor</summary>
|
|
208
|
-
|
|
209
|
-
`Cursor Settings` → `MCP` → `New MCP Server`에서 등록합니다.
|
|
210
|
-
|
|
211
|
-
```json
|
|
212
|
-
{
|
|
213
|
-
"obsidian": {
|
|
214
|
-
"command": "npx",
|
|
215
|
-
"args": ["-y", "@sunub/obsidian-mcp-server@latest"],
|
|
216
|
-
"env": { "VAULT_DIR_PATH": "/abs/path/to/your/vault" }
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
※ 일부 버전은 서버 식별 키명이 다를 수 있으니 설정 화면 안내에 맞춰 붙여넣으세요.
|
|
222
|
-
</details>
|
|
223
|
-
|
|
224
|
-
<details>
|
|
225
|
-
<summary>Gemini CLI</summary>
|
|
226
|
-
|
|
227
|
-
패키지 설치형 예시:
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
gemini mcp add obsidian npx -y @sunub/obsidian-mcp-server@latest --vault-path /abs/path/to/your/vault
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
※ 일부 Gemini 버전은 `--vault-path` 지원이 다를 수 있으므로, `gemini mcp add`의 최신 문서를 확인하세요.
|
|
234
|
-
</details>
|
|
235
|
-
|
|
236
|
-
### 4) 완성 예시 설정
|
|
237
|
-
|
|
238
|
-
```json
|
|
239
|
-
{
|
|
240
|
-
"mcpServers": {
|
|
241
|
-
"obsidian": {
|
|
242
|
-
"command": "npx",
|
|
243
|
-
"args": [
|
|
244
|
-
"-y",
|
|
245
|
-
"@sunub/obsidian-mcp-server@latest"
|
|
246
|
-
],
|
|
247
|
-
"env": {
|
|
248
|
-
"VAULT_DIR_PATH": "/path/to/obsidian-vault",
|
|
249
|
-
"VAULT_METRICS_LOG_PATH": "/path/to/vault-metrics.ndjson",
|
|
250
|
-
"LOGGING_LEVEL": "info",
|
|
251
|
-
"LLM_API_URL": "http://127.0.0.1:8080",
|
|
252
|
-
"LLM_EMBEDDING_API_URL": "http://127.0.0.1:8081",
|
|
253
|
-
"LLM_EMBEDDING_MODEL": "nomic-embed-text",
|
|
254
|
-
"LLM_CHAT_MODEL": "llama3",
|
|
255
|
-
"LLM_RERANKER_API_URL": "http://127.0.0.1:8082"
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
```json
|
|
261
|
-
{
|
|
262
|
-
"mcpServers": {
|
|
263
|
-
"obsidian": {
|
|
264
|
-
"command": "npx",
|
|
265
|
-
"args": [
|
|
266
|
-
"-y",
|
|
267
|
-
"@sunub/obsidian-mcp-server@latest"
|
|
268
|
-
],
|
|
269
|
-
"env": {
|
|
270
|
-
"VAULT_DIR_PATH": "/path/to/obsidian-vault",
|
|
271
|
-
"VAULT_METRICS_LOG_PATH": "/path/to/vault-metrics.ndjson",
|
|
272
|
-
"LOGGING_LEVEL": "info",
|
|
273
|
-
"LLM_API_URL": "http://127.0.0.1:8080",
|
|
274
|
-
"LLM_EMBEDDING_API_URL": "http://127.0.0.1:8081",
|
|
275
|
-
"LLM_EMBEDDING_MODEL": "nomic-embed-text",
|
|
276
|
-
"LLM_CHAT_MODEL": "llama3",
|
|
277
|
-
"LLM_RERANKER_API_URL": "http://127.0.0.1:8082"
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
> 각 변수의 역할과 기본값은 [사전 주의사항 → 환경변수 전체 설정 예시](#환경변수-전체-설정-예시)를 참고하세요.
|
|
285
|
-
|
|
286
|
-
## 시맨틱 검색 (RAG) 설정
|
|
287
|
-
|
|
288
|
-
키워드 검색 외에 **의미 기반 벡터 검색**을 사용하려면 임베딩 서버가 필요합니다.
|
|
289
|
-
|
|
290
|
-
### 활성화 조건
|
|
291
|
-
|
|
292
|
-
`LLM_EMBEDDING_API_URL`이 가리키는 서버가 실행 중이어야 합니다.
|
|
293
|
-
없으면 `search_vault_by_semantic`과 `index_vault_to_vectordb`가 동작하지 않고,
|
|
294
|
-
`useRagContext`는 자동으로 키워드 검색으로 폴백됩니다.
|
|
295
|
-
|
|
296
|
-
```bash
|
|
297
|
-
# ecosystem.config.cjs 기준 — 임베딩 서버 단독 시작
|
|
298
|
-
pm2 start ecosystem.config.cjs --only llama-embedding-server
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### 초기 색인 실행
|
|
302
|
-
|
|
303
|
-
서버 연결 후 처음 한 번은 전체 색인을 직접 트리거해야 합니다.
|
|
304
|
-
|
|
305
|
-
```text
|
|
306
|
-
"Vault 전체를 재색인해줘."
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
내부 동작:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
```json
|
|
313
|
-
{
|
|
314
|
-
"mcpServers": {
|
|
315
|
-
"obsidian": {
|
|
316
|
-
"command": "npx",
|
|
317
|
-
"args": [
|
|
318
|
-
"-y",
|
|
319
|
-
"@sunub/obsidian-mcp-server@latest"
|
|
320
|
-
],
|
|
321
|
-
"env": {
|
|
322
|
-
"VAULT_DIR_PATH": "/path/to/obsidian-vault",
|
|
323
|
-
"VAULT_METRICS_LOG_PATH": "/path/to/vault-metrics.ndjson",
|
|
324
|
-
"LOGGING_LEVEL": "info",
|
|
325
|
-
"LLM_API_URL": "http://127.0.0.1:8080",
|
|
326
|
-
"LLM_EMBEDDING_API_URL": "http://127.0.0.1:8081",
|
|
327
|
-
"LLM_EMBEDDING_MODEL": "nomic-embed-text",
|
|
328
|
-
"LLM_CHAT_MODEL": "llama3",
|
|
329
|
-
"LLM_RERANKER_API_URL": "http://127.0.0.1:8082"
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
이후에는 파일 변경 시 **VaultWatcher가 자동으로 증분 색인**합니다 (mtime 기반).
|
|
337
|
-
|
|
338
|
-
### 색인 저장 위치
|
|
339
|
-
|
|
340
|
-
```
|
|
341
|
-
{VAULT_DIR_PATH}/.obsidian/vector_cache/ ← LanceDB 데이터 디렉터리
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
- 직접 삭제해도 무방합니다. 다음 `index_vault_to_vectordb` 호출 시 재생성됩니다.
|
|
345
|
-
- `LLM_EMBEDDING_MODEL`을 변경하면 서버 시작 시 **자동으로 전체 재색인**이 발생합니다 (INDEX_VERSION 또는 모델명 불일치 감지).
|
|
346
|
-
|
|
347
|
-
### 검색 전략 흐름
|
|
348
|
-
|
|
349
|
-
```
|
|
350
|
-
사용자 질문
|
|
351
|
-
│
|
|
352
|
-
▼
|
|
353
|
-
search_vault_by_semantic (벡터 유사도 검색 → 리랭킹)
|
|
354
|
-
│ 결과 없음
|
|
355
|
-
▼
|
|
356
|
-
vault.search (키워드 폴백)
|
|
357
|
-
│ 결과 없음
|
|
358
|
-
▼
|
|
359
|
-
컨텍스트 없이 LLM 응답
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
<details>
|
|
363
|
-
<summary>청크·토큰 파라미터 (파워유저용)</summary>
|
|
364
|
-
|
|
365
|
-
| 항목 | 값 | 비고 |
|
|
366
|
-
|---|---|---|
|
|
367
|
-
| 청크 크기 | 500 tokens | tiktoken cl100k 기준 |
|
|
368
|
-
| 청크 오버랩 | 50 tokens | |
|
|
369
|
-
| 임베딩 최대 토큰 | 500 tokens | 초과 시 비례 잘라냄 |
|
|
370
|
-
| 임베딩 접두어 | `search_document: {text}` | nomic-embed-text 형식 |
|
|
371
|
-
| 벡터 차원 | 768 | nomic-embed-text 출력 크기 |
|
|
372
|
-
| IVF-PQ 인덱스 생성 | 256개 문서 이상 | 미만이면 브루트포스로 동작 |
|
|
373
|
-
| 동시 임베딩 요청 | 최대 3 | Semaphore 제한 |
|
|
374
|
-
|
|
375
|
-
</details>
|
|
376
|
-
|
|
377
|
-
## 시작 후 빠른 검증
|
|
378
|
-
|
|
379
|
-
연결이 끝나면 아래 3가지만 먼저 확인하세요.
|
|
380
|
-
문서가 안 열리더라도 어디서 실패했는지 바로 좁힐 수 있습니다.
|
|
381
|
-
|
|
382
|
-
1) Vault 상태 확인
|
|
383
|
-
|
|
384
|
-
```text
|
|
385
|
-
"Vault 상태를 요약해줘."
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
예상 내부 동작:
|
|
389
|
-
|
|
390
|
-
```json
|
|
391
|
-
{
|
|
392
|
-
"method": "tools/call",
|
|
393
|
-
"params": {
|
|
394
|
-
"name": "vault",
|
|
395
|
-
"arguments": { "action": "stats" }
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
정상 동작 시 응답에 `totalFiles`, `isInitialized`, `vaultPath`가 들어갑니다.
|
|
401
|
-
|
|
402
|
-
1) 검색 인덱스 확인
|
|
403
|
-
|
|
404
|
-
```text
|
|
405
|
-
"노트 제목에 'MCP'가 들어간 문서만 5개 찾아줘."
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
예상 내부 동작:
|
|
409
|
-
|
|
410
|
-
```json
|
|
411
|
-
{
|
|
412
|
-
"method": "tools/call",
|
|
413
|
-
"params": {
|
|
414
|
-
"name": "vault",
|
|
415
|
-
"arguments": {
|
|
416
|
-
"action": "search",
|
|
417
|
-
"keyword": "MCP",
|
|
418
|
-
"limit": 5
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
`search`에서 결과가 비어 있으면 인덱싱/경로 또는 키워드 범위를 의심합니다.
|
|
425
|
-
|
|
426
|
-
1) 문서 읽기 확인
|
|
427
|
-
|
|
428
|
-
```text
|
|
429
|
-
"특정 노트 하나를 읽어줘."
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
예상 내부 동작:
|
|
433
|
-
|
|
434
|
-
```json
|
|
435
|
-
{
|
|
436
|
-
"method": "tools/call",
|
|
437
|
-
"params": {
|
|
438
|
-
"name": "vault",
|
|
439
|
-
"arguments": {
|
|
440
|
-
"action": "read",
|
|
441
|
-
"filename": "예: 어떤 문서 이름"
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
`filename`이 틀리면 `{ "error": "Document not found: ..." }`가 오고, `read` 대신 `list_all`으로 후보를 먼저 확인하면 해결이 빠릅니다.
|
|
448
|
-
|
|
449
|
-
`search`, `list_all`, `load_memory`는 `quiet` 기본값이 `true`라서 기본 응답이 간략해질 수 있습니다.
|
|
450
|
-
필요하면 `quiet: false`, `includeContent: true`, `excerptLength`(또는 `maxOutputChars`)를 함께 써서 상세를 확인하세요.
|
|
451
|
-
|
|
452
|
-
## 사용 예시
|
|
453
|
-
|
|
454
|
-
MCP를 어떻게 호출하는지보다 “무슨 동작을 하려고 하는지”가 더 중요합니다.
|
|
455
|
-
|
|
456
|
-
마크다운 문서에 작성되어 있는 첨부파일을 정리하는 동작 예시 :
|
|
457
|
-
|
|
458
|
-
<https://github.com/user-attachments/assets/eb74ec05-09f7-4632-a22c-666b7e844147>
|
|
83
|
+
---
|
|
459
84
|
|
|
460
|
-
|
|
461
|
-
- 정리된 문서는 기존의 작성되어 있던 문서의 연결이 깨지지 않도록 `images` 폴더를 기준으로 연결을 업데이트 합니다.
|
|
85
|
+
## 하이브리드 검색 동작 방식
|
|
462
86
|
|
|
463
|
-
|
|
87
|
+
기존의 키워드 검색만으로는 찾기 힘든 의미적 연관성을 잡기 위해 다음과 같은 파이프라인을 거칩니다:
|
|
464
88
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
89
|
+
1. **Keyword Search**: 내부 `Indexer`를 통해 정확한 단어 매칭 결과 추출.
|
|
90
|
+
2. **Vector Search**: LanceDB와 `transformers.js` 임베딩을 사용하여 의미적으로 유사한 청크 탐색.
|
|
91
|
+
3. **RRF Fusion**: 두 검색 결과의 순위를 Reciprocal Rank Fusion 알고리즘으로 병합.
|
|
92
|
+
4. **Local Reranking**: 병합된 상위 결과들을 `BGE Reranker` 모델로 다시 평가하여 최종 순위 결정.
|
|
469
93
|
|
|
470
|
-
|
|
94
|
+
모델이 설치되지 않은 경우 자동으로 키워드 전용 모드로 동작하며, 터미널에 `npx @sunub/obsidian-mcp-server setup` 실행 권장 메시지를 표시합니다.
|
|
471
95
|
|
|
472
|
-
|
|
473
|
-
- `"docs/tools-usage-guide.md에서 vault 관련 사용 예시만 찾아서 비교해줘"`
|
|
474
|
-
- `"docs/tool-reference.md의 vault.read 파라미터 설명만 읽어줘"`
|
|
96
|
+
---
|
|
475
97
|
|
|
476
|
-
|
|
98
|
+
## 대화형 CLI AI Agent UI
|
|
477
99
|
|
|
478
|
-
|
|
479
|
-
→ 핵심 키워드만 추출해 `vault.search`가 먼저 호출됩니다.
|
|
480
|
-
- `docs/tool-reference.md의 collect_context 파라미터만 읽어줘`
|
|
481
|
-
→ 먼저 `vault.read`로 문서의 해당 부분을 읽고, 필요 시 `vault.collect_context`로 정리합니다.
|
|
482
|
-
- `docs/tools-usage-guide.md에서 frontmatter 처리 과정을 읽어줘`
|
|
483
|
-
→ 문서 위치를 `vault.read`로 찾은 뒤 `generate_property`/`write_property`/`create_document_with_properties`를 순차 호출할 수 있습니다.
|
|
100
|
+
이 프로젝트에는 Obsidian Vault에 최적화된 **터미널 기반 AI 채팅 인터페이스**가 내장되어 있습니다.
|
|
484
101
|
|
|
485
|
-
|
|
102
|
+
### 특징
|
|
103
|
+
- **RAG 통합**: 질문 시 자동으로 Vault에서 관련 컨텍스트를 수집하여 LLM에 전달합니다.
|
|
104
|
+
- **실시간 스트리밍**: LLM의 답변과 "생각하는 과정(<think>)"을 실시간으로 렌더링합니다.
|
|
105
|
+
- **슬래시 커맨드**: `/search`, `/read`, `/index` 등 MCP 도구를 CLI에서 직접 명령어로 호출 가능합니다.
|
|
106
|
+
- **멀티 MCP 관리**: 연결된 모든 MCP 서버의 상태와 도구 목록을 모니터링합니다.
|
|
486
107
|
|
|
487
|
-
|
|
108
|
+
### 실행 방법
|
|
488
109
|
|
|
489
|
-
|
|
110
|
+
1. **채팅 모델 서버 구동**: `llama.cpp` 또는 `Ollama`와 같은 서버를 OpenAI 호환 모드로 띄웁니다.
|
|
111
|
+
- 예: `llama-server -m models/gemma-2-9b-it.Q4_K_M.gguf --port 8080`
|
|
112
|
+
2. **CLI 실행**:
|
|
113
|
+
```bash
|
|
114
|
+
# 환경변수와 함께 실행
|
|
115
|
+
VAULT_DIR_PATH="/your/vault" LLM_API_URL="http://localhost:8080" npx @sunub/obsidian-mcp-server
|
|
116
|
+
```
|
|
490
117
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
- `load_memory`
|
|
499
|
-
- `search_vault_by_semantic`
|
|
500
|
-
- `index_vault_to_vectordb`
|
|
501
|
-
- [`generate_property`](docs/tool-reference.md#generate_property)
|
|
502
|
-
- [`write_property`](docs/tool-reference.md#write_property)
|
|
503
|
-
- [`create_document_with_properties`](docs/tool-reference.md#create_document_with_properties)
|
|
504
|
-
- [`organize_attachments`](docs/tool-reference.md#organize_attachments)
|
|
118
|
+
### 슬래시 커맨드 도움말
|
|
119
|
+
- `/search <keyword>`: 하이브리드 검색 실행
|
|
120
|
+
- `/read "filename"`: 특정 문서 읽기
|
|
121
|
+
- `/stats`: Vault 상태 확인
|
|
122
|
+
- `/index`: 벡터 DB 재색인 강제 실행
|
|
123
|
+
- `/tools`: 사용 가능한 모든 MCP 도구 목록 확인
|
|
124
|
+
- `/help`: 도움말 보기
|
|
505
125
|
|
|
506
|
-
|
|
126
|
+
---
|
|
507
127
|
|
|
508
|
-
##
|
|
128
|
+
## 라이선스
|
|
509
129
|
|
|
510
|
-
-
|
|
511
|
-
- `vault`는 하나의 MCP tool이고 `action` 값으로 실제 동작이 분기됩니다. 오타가 가장 흔한 실패 원인입니다.
|
|
512
|
-
- 대규모 Vault에서는 `collect_context`를 `scope="all"`, `maxDocs`를 작게 시작해 단계적으로 확장하세요.
|
|
130
|
+
Apache-2.0
|
package/build/cli/App.js
CHANGED
|
@@ -20,6 +20,7 @@ import { historyStorage } from "./utils/historyStorage.js";
|
|
|
20
20
|
import { Box, Text } from "ink";
|
|
21
21
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
22
22
|
import { debugLogger } from "../shared/index.js";
|
|
23
|
+
import { disableMouseEvents } from "./utils/terminal.js";
|
|
23
24
|
export const App = () => {
|
|
24
25
|
const [shellModeActive] = useState(false);
|
|
25
26
|
const [copyModeEnabled, setCopyModeEnabled] = useState(false);
|
|
@@ -28,16 +29,19 @@ export const App = () => {
|
|
|
28
29
|
const keyMatchers = useKeyMatchers();
|
|
29
30
|
const { columns: terminalWidth, rows: terminalHeight } = useTerminalSize();
|
|
30
31
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
disableMouseEvents();
|
|
33
|
+
}, []);
|
|
34
|
+
//
|
|
35
|
+
// useEffect(() => {
|
|
36
|
+
// if (copyModeEnabled) {
|
|
37
|
+
// process.stdout.write("\x1b[?1000l\x1b[?1003l\x1b[?1015l\x1b[?1006l");
|
|
38
|
+
// } else {
|
|
39
|
+
// process.stdout.write("\x1b[?1000h\x1b[?1003h\x1b[?1015h\x1b[?1006h");
|
|
40
|
+
// }
|
|
41
|
+
// return () => {
|
|
42
|
+
// process.stdout.write("\x1b[?1000l\x1b[?1003l\x1b[?1015l\x1b[?1006l");
|
|
43
|
+
// };
|
|
44
|
+
// }, [copyModeEnabled]);
|
|
41
45
|
const mainAreaWidth = terminalWidth;
|
|
42
46
|
const { inputWidth, suggestionsWidth } = useMemo(() => {
|
|
43
47
|
const { inputWidth, suggestionsWidth } = calculatePromptWidths(mainAreaWidth);
|
|
@@ -222,20 +226,42 @@ export const App = () => {
|
|
|
222
226
|
timestamp: Date.now(),
|
|
223
227
|
});
|
|
224
228
|
buffer.setText("");
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
// 1. 도구 트리거 감지 (도구 이름이나 서버 이름이 포함된 경우)
|
|
230
|
+
const triggeredTools = [];
|
|
231
|
+
const lowerValue = value.toLowerCase();
|
|
232
|
+
for (const [serverName, serverTools] of mcpToolsByServer.entries()) {
|
|
233
|
+
// 서버 이름이 언급된 경우 해당 서버의 모든 도구 활성화
|
|
234
|
+
if (lowerValue.includes(serverName.toLowerCase())) {
|
|
235
|
+
triggeredTools.push(...serverTools);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// 특정 도구 이름이 언급된 경우 해당 도구만 활성화
|
|
239
|
+
for (const tool of serverTools) {
|
|
240
|
+
if (lowerValue.includes(tool.name.toLowerCase())) {
|
|
241
|
+
triggeredTools.push(tool);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// 중복 제거
|
|
246
|
+
const uniqueTriggered = Array.from(new Set(triggeredTools));
|
|
247
|
+
// 2. Vault 도구가 트리거된 경우에만 RAG 컨텍스트 수집
|
|
248
|
+
const isVaultTriggered = uniqueTriggered.some((t) => t.name === "vault");
|
|
249
|
+
const ragContext = isVaultTriggered ? await fetchContext(value) : null;
|
|
250
|
+
// 3. 트리거된 도구만 LLM에 전달
|
|
251
|
+
void sendMessage(value, ragContext, uniqueTriggered);
|
|
227
252
|
}
|
|
228
253
|
}, [
|
|
229
254
|
addInput,
|
|
230
255
|
buffer,
|
|
231
|
-
callTool,
|
|
232
|
-
fetchContext,
|
|
233
|
-
genMcpToolsText,
|
|
234
256
|
handleDispatch,
|
|
257
|
+
callTool,
|
|
235
258
|
historyManager,
|
|
259
|
+
clearStreamingHistory,
|
|
260
|
+
genMcpToolsText,
|
|
261
|
+
mcpToolsByServer,
|
|
262
|
+
fetchContext,
|
|
236
263
|
sendMessage,
|
|
237
264
|
isLoading,
|
|
238
|
-
clearStreamingHistory,
|
|
239
265
|
addInfoMessage,
|
|
240
266
|
]);
|
|
241
267
|
return (_jsxs(InputContext.Provider, { value: inputState, children: [_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(MainContent, { history: historyManager.history, pendingItem: pendingItem, streamingState: streamingState, isRagFetching: isRagFetching, isCommandProcessing: isCommandProcessing, width: mainAreaWidth }), error && (_jsx(Box, { paddingX: 1, marginBottom: 1, children: _jsxs(Text, { color: "red", bold: true, children: ["\u2716 ", error.message] }) }))] }), _jsxs(Box, { paddingX: 1, marginTop: 1, flexDirection: "column", gap: 0, width: "100%", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderTopColor: theme.border.default, borderStyle: "bold", children: [_jsx(SystemInfoSummaryBox, { children: _jsx(MCPServers, { isConnected: mcpConnected, connections: mcpConnections, serverCount: mcpServerCount, connectedCount: mcpConnectedCount, errors: mcpErrors }) }), _jsx(InputPrompt, { onSubmit: handleFinalSubmit, focus: streamingState === "idle" && mcpConnected, placeholder: isAnyConnecting
|