@fromsko/obsidian-mcp-server 1.1.7

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 ADDED
@@ -0,0 +1,139 @@
1
+ # Obsidian MCP Server
2
+
3
+ 将你的 Obsidian 笔记库暴露为 MCP 服务,让 AI 助手可以搜索和读取你的笔记。
4
+
5
+ ## ✨ 功能
6
+
7
+ | 工具 | 描述 |
8
+ |------|------|
9
+ | `search_notes` | 按关键词、标签、分类搜索笔记 |
10
+ | `read_note` | 读取指定笔记的完整内容 |
11
+ | `list_folder` | 列出文件夹下的笔记和子文件夹 |
12
+ | `get_note_structure` | 获取笔记库目录结构 |
13
+ | `full_text_search` | 在所有笔记中全文搜索 |
14
+ | `create_note` | 创建新笔记 |
15
+ | `update_note` | 更新已存在的笔记 |
16
+ | `delete_note` | 删除指定笔记 |
17
+ | `create_folder` | 创建新文件夹 |
18
+ | `get_prompt_guide` | 获取知识库整理助手提示词的使用指南 |
19
+
20
+ ## 🚀 安装
21
+
22
+ ### 方式一:直接使用(推荐)
23
+
24
+ 无需安装,直接在 MCP 配置中使用 `npx`:
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "obsidian-notes": {
30
+ "command": "npx",
31
+ "args": [
32
+ "-y",
33
+ "@andysama/obsidian-mcp-server",
34
+ "--vault",
35
+ "/path/to/your/obsidian/vault"
36
+ ]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### 方式二:全局安装
43
+
44
+ ```bash
45
+ npm install -g @andysama/obsidian-mcp-server
46
+ ```
47
+
48
+ ### 方式三:从源码构建
49
+
50
+ ```bash
51
+ git clone https://github.com/andysama-work/obsidian-mcp-server.git
52
+ cd obsidian-mcp-server
53
+ npm install
54
+ npm run build
55
+ ```
56
+
57
+ ## ⚙️ 配置
58
+
59
+ ### Claude Desktop
60
+
61
+ 编辑 `%APPDATA%\Claude\claude_desktop_config.json`(Windows)或 `~/Library/Application Support/Claude/claude_desktop_config.json`(macOS):
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "obsidian-notes": {
67
+ "command": "node",
68
+ "args": [
69
+ "/path/to/obsidian-mcp-server/dist/index.js",
70
+ "--vault",
71
+ "/path/to/your/obsidian/vault"
72
+ ]
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ### Windsurf / Cursor
79
+
80
+ 在 MCP 配置文件中添加:
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "obsidian-notes": {
86
+ "command": "node",
87
+ "args": [
88
+ "/path/to/obsidian-mcp-server/dist/index.js",
89
+ "--vault",
90
+ "/path/to/your/obsidian/vault"
91
+ ]
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ > ⚠️ 请将 `/path/to/your/obsidian/vault` 替换为你的 Obsidian 笔记库实际路径
98
+
99
+ ## 📖 使用示例
100
+
101
+ 配置完成后,AI 助手可以:
102
+
103
+ - 搜索笔记:`搜索关于 STM32 的笔记`
104
+ - 读取内容:`读取 STM32系列选型速查.md`
105
+ - 浏览结构:`列出硬件学习文件夹的内容`
106
+ - 全文搜索:`在笔记中搜索 "定时器"`
107
+ - 创建文件夹:`在知识点目录下创建一个新文件夹`
108
+ - 获取提示词指南:`告诉我如何使用知识库整理助手提示词`
109
+
110
+ ## 🛠️ 开发
111
+
112
+ ```bash
113
+ # 安装依赖
114
+ npm install
115
+
116
+ # 构建
117
+ npm run build
118
+
119
+ # 测试运行(需指定 vault 路径)
120
+ node dist/index.js --vault "/path/to/vault"
121
+ ```
122
+
123
+ ## 📝 Frontmatter 支持
124
+
125
+ 本工具会解析笔记的 YAML Frontmatter,支持以下字段:
126
+
127
+ ```yaml
128
+ ---
129
+ category: hardware
130
+ tags: [STM32, 嵌入式]
131
+ summary: 笔记摘要
132
+ folder: 知识点/03-硬件学习/
133
+ created: 2024-12-18
134
+ ---
135
+ ```
136
+
137
+ ## 📄 License
138
+
139
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,587 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import * as fs from "fs/promises";
6
+ import { glob } from "glob";
7
+ import matter from "gray-matter";
8
+ import * as path from "path";
9
+ // 解析命令行参数
10
+ function parseArgs() {
11
+ const args = process.argv.slice(2);
12
+ let vaultPath = "";
13
+ for (let i = 0; i < args.length; i++) {
14
+ if (args[i] === "--vault" && args[i + 1]) {
15
+ vaultPath = args[i + 1];
16
+ break;
17
+ }
18
+ }
19
+ if (!vaultPath) {
20
+ console.error("错误: 请使用 --vault 参数指定 Obsidian 笔记库路径");
21
+ console.error("用法: node dist/index.js --vault \"/path/to/your/vault\"");
22
+ process.exit(1);
23
+ }
24
+ return { vaultPath };
25
+ }
26
+ const { vaultPath: VAULT_PATH } = parseArgs();
27
+ // 解析 Markdown 文件的 Frontmatter
28
+ async function parseNote(filePath) {
29
+ try {
30
+ const content = await fs.readFile(filePath, "utf-8");
31
+ const { data } = matter(content);
32
+ const relativePath = path.relative(VAULT_PATH, filePath);
33
+ return {
34
+ path: relativePath,
35
+ name: path.basename(filePath, ".md"),
36
+ category: data.category,
37
+ tags: data.tags,
38
+ summary: data.summary,
39
+ folder: data.folder,
40
+ created: data.created,
41
+ };
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ // 获取所有笔记
48
+ async function getAllNotes() {
49
+ const files = await glob("**/*.md", {
50
+ cwd: VAULT_PATH,
51
+ ignore: [".obsidian/**", ".smart-env/**", ".windsurf/**"],
52
+ });
53
+ const notes = [];
54
+ for (const file of files) {
55
+ const meta = await parseNote(path.join(VAULT_PATH, file));
56
+ if (meta)
57
+ notes.push(meta);
58
+ }
59
+ return notes;
60
+ }
61
+ // 搜索笔记
62
+ async function searchNotes(query, tag, category) {
63
+ const allNotes = await getAllNotes();
64
+ return allNotes.filter((note) => {
65
+ const matchesQuery = !query ||
66
+ note.name.toLowerCase().includes(query.toLowerCase()) ||
67
+ note.summary?.toLowerCase().includes(query.toLowerCase());
68
+ const matchesTag = !tag || note.tags?.includes(tag);
69
+ const matchesCategory = !category || note.category === category;
70
+ return matchesQuery && matchesTag && matchesCategory;
71
+ });
72
+ }
73
+ // 读取笔记内容
74
+ async function readNote(notePath) {
75
+ const fullPath = path.join(VAULT_PATH, notePath);
76
+ try {
77
+ return await fs.readFile(fullPath, "utf-8");
78
+ }
79
+ catch {
80
+ throw new Error(`笔记不存在: ${notePath}`);
81
+ }
82
+ }
83
+ // 列出文件夹内容
84
+ async function listFolder(folderPath = "") {
85
+ const targetPath = path.join(VAULT_PATH, folderPath);
86
+ const entries = await fs.readdir(targetPath, { withFileTypes: true });
87
+ const folders = [];
88
+ const notes = [];
89
+ for (const entry of entries) {
90
+ if (entry.name.startsWith("."))
91
+ continue;
92
+ if (entry.isDirectory()) {
93
+ folders.push(entry.name);
94
+ }
95
+ else if (entry.name.endsWith(".md")) {
96
+ const meta = await parseNote(path.join(targetPath, entry.name));
97
+ if (meta)
98
+ notes.push(meta);
99
+ }
100
+ }
101
+ return { folders, notes };
102
+ }
103
+ // 获取笔记库结构
104
+ async function getNoteStructure() {
105
+ const rootContent = await listFolder("");
106
+ const structure = { _notes: rootContent.notes.map(n => n.name) };
107
+ for (const folder of rootContent.folders) {
108
+ try {
109
+ const subContent = await listFolder(folder);
110
+ structure[folder] = {
111
+ folders: subContent.folders,
112
+ notes: subContent.notes.map(n => n.name),
113
+ };
114
+ }
115
+ catch {
116
+ structure[folder] = { error: "无法读取" };
117
+ }
118
+ }
119
+ return structure;
120
+ }
121
+ // 创建笔记
122
+ async function createNote(notePath, content) {
123
+ const fullPath = path.join(VAULT_PATH, notePath);
124
+ // 确保目录存在
125
+ const dir = path.dirname(fullPath);
126
+ await fs.mkdir(dir, { recursive: true });
127
+ // 检查文件是否已存在
128
+ try {
129
+ await fs.access(fullPath);
130
+ throw new Error(`笔记已存在: ${notePath}`);
131
+ }
132
+ catch (err) {
133
+ if (err.code !== 'ENOENT')
134
+ throw err;
135
+ }
136
+ // 写入文件
137
+ await fs.writeFile(fullPath, content, 'utf-8');
138
+ return `笔记创建成功: ${notePath}`;
139
+ }
140
+ // 更新笔记
141
+ async function updateNote(notePath, content) {
142
+ const fullPath = path.join(VAULT_PATH, notePath);
143
+ // 检查文件是否存在
144
+ try {
145
+ await fs.access(fullPath);
146
+ }
147
+ catch {
148
+ throw new Error(`笔记不存在: ${notePath}`);
149
+ }
150
+ // 写入文件
151
+ await fs.writeFile(fullPath, content, 'utf-8');
152
+ return `笔记更新成功: ${notePath}`;
153
+ }
154
+ // 创建文件夹
155
+ async function createFolder(folderPath) {
156
+ const fullPath = path.join(VAULT_PATH, folderPath);
157
+ // 检查文件夹是否已存在
158
+ try {
159
+ const stat = await fs.stat(fullPath);
160
+ if (stat.isDirectory()) {
161
+ throw new Error(`文件夹已存在: ${folderPath}`);
162
+ }
163
+ else {
164
+ throw new Error(`路径已存在但不是文件夹: ${folderPath}`);
165
+ }
166
+ }
167
+ catch (err) {
168
+ if (err.code !== 'ENOENT')
169
+ throw err;
170
+ }
171
+ // 创建文件夹
172
+ await fs.mkdir(fullPath, { recursive: true });
173
+ return `文件夹创建成功: ${folderPath}`;
174
+ }
175
+ // 删除笔记
176
+ async function deleteNote(notePath) {
177
+ const fullPath = path.join(VAULT_PATH, notePath);
178
+ // 检查文件是否存在
179
+ try {
180
+ await fs.access(fullPath);
181
+ }
182
+ catch {
183
+ throw new Error(`笔记不存在: ${notePath}`);
184
+ }
185
+ // 删除文件
186
+ await fs.unlink(fullPath);
187
+ return `笔记删除成功: ${notePath}`;
188
+ }
189
+ // 获取提示词使用指南
190
+ async function getPromptGuide() {
191
+ const guide = `# 🗂️ Obsidian 知识库整理助手 - 使用指南
192
+
193
+ ## 一、提示词简介
194
+
195
+ 这是一个用于将杂乱笔记整理为标准 Obsidian Markdown 文档的提示词。它可以:
196
+ - 自动生成标准的 Frontmatter(YAML)
197
+ - 根据内容智能分类并推荐存放路径
198
+ - 规范化 Markdown 结构
199
+ - 自动推荐相关笔记链接
200
+
201
+ ## 二、如何添加提示词
202
+
203
+ ### 方法 1:在 AI 对话中直接使用
204
+
205
+ 1. 复制下方提示词内容
206
+ 2. 在与 AI 的对话中粘贴提示词
207
+ 3. 在提示词末尾粘贴你要整理的笔记内容
208
+ 4. AI 会返回整理好的 Markdown 文档
209
+
210
+ ### 方法 2:保存为 Obsidian 模板
211
+
212
+ 1. 在 Obsidian 中创建一个模板文件夹(如 Templates/)
213
+ 2. 创建新笔记,命名为「知识库整理助手提示词.md」
214
+ 3. 将提示词内容粘贴进去
215
+ 4. 需要时复制使用
216
+
217
+ ### 方法 3:配置为 AI 工具的系统提示词
218
+
219
+ 如果你使用的 AI 工具支持自定义系统提示词(如 ChatGPT、Claude 等):
220
+ 1. 进入设置/偏好设置
221
+ 2. 找到「自定义指令」或「系统提示词」选项
222
+ 3. 将提示词粘贴到相应位置
223
+ 4. 保存后,AI 会自动按照提示词格式整理笔记
224
+
225
+ ## 三、提示词内容
226
+
227
+ \`\`\`
228
+ 你是一个 Obsidian 知识库整理助手。
229
+
230
+ 请将下面杂乱的笔记内容,整理为「Obsidian 可直接使用的 Markdown 文档」,要求:
231
+
232
+ ## 一、生成标准 Obsidian Frontmatter(YAML)
233
+
234
+ ---
235
+ category: <主分类>
236
+ tags: [tag1, tag2, tag3]
237
+ summary: <一句话总结>
238
+ icon: <lucide图标名或emoji>
239
+ status: <draft | active | archived>
240
+ folder: <推荐存放路径>
241
+ created: <YYYY-MM-DD>
242
+ ---
243
+
244
+ ### 分类规则(category → folder 映射)
245
+
246
+ | category | 说明 | 对应文件夹路径 |
247
+ |----------|------|----------------|
248
+ | frontend | 前端开发(JS/TS/React/Vue/CSS等) | 知识点/02-前端知识/ |
249
+ | backend | 后端开发(Rust/Java/Scala等) | 知识点/05-后端知识/ |
250
+ | hardware | 硬件/嵌入式(STM32/SystemRDL/FPGA) | 知识点/03-硬件学习/ |
251
+ | ai | 人工智能/LLM/MCP/提示词 | 知识点/04-人工智能/ |
252
+ | docker | Docker/容器化 | 知识点/01-Docker/ |
253
+ | devops | CI/CD/Git/部署 | 知识点/00-闲置笔记/git/ |
254
+ | linux | Linux 系统/命令 | 知识点/00-闲置笔记/Liunx/ |
255
+ | database | 数据库相关 | 知识点/00-闲置笔记/数据库/ |
256
+ | server | 服务器环境配置 | 知识点/00-闲置笔记/服务器安装环境/ |
257
+ | security | 密钥/认证/安全 | 知识点/密钥/ |
258
+ | music | 乐理/吉他/音乐学习 | 乐理/ |
259
+ | misc | 杂项/临时笔记 | 知识点/00-闲置笔记/ |
260
+
261
+ ## 二、正文结构要求
262
+
263
+ - 使用清晰的 Markdown 标题(# / ## / ###)
264
+ - 合并重复或相近内容
265
+ - 技术笔记优先结构化(列表 / 步骤 / 代码块)
266
+ - 保留原始想法,不要过度改写语义
267
+ - 代码块必须标注语言类型
268
+
269
+ ## 三、文件命名规范
270
+
271
+ - 格式:主题-子主题.md 或 序号-主题.md
272
+ - 示例:Gitea-Actions-部署方案.md、01-吉他谱寻找.md
273
+ - 避免特殊字符,使用中划线连接
274
+
275
+ ## 四、在文末新增「🔗 Related Notes」章节
276
+
277
+ - 推测 3~5 个可能相关的笔记标题
278
+ - 使用 Obsidian 双链格式:[[笔记名称]]
279
+
280
+ ## 五、输出要求
281
+
282
+ - 只输出整理后的 Markdown
283
+ - 不要解释整理过程
284
+ - 不要输出多余说明
285
+ - **必须在 Frontmatter 中包含 folder 字段,精确到子目录**
286
+
287
+ ---
288
+
289
+ 下面是需要整理的原始笔记内容:
290
+ \`\`\`
291
+
292
+ ## 四、使用示例
293
+
294
+ ### 输入示例
295
+
296
+ \`\`\`
297
+ react hooks 学习笔记
298
+ useState 用来管理状态
299
+ useEffect 处理副作用,比如请求数据
300
+ 自定义hook要用use开头
301
+ \`\`\`
302
+
303
+ ### 输出示例
304
+
305
+ \`\`\`markdown
306
+ ---
307
+ category: frontend
308
+ tags: [react, hooks, useState, useEffect]
309
+ summary: React Hooks 核心概念学习笔记
310
+ icon: ⚛️
311
+ status: active
312
+ folder: 知识点/02-前端知识/React/
313
+ created: 2024-12-26
314
+ ---
315
+
316
+ # React Hooks 学习笔记
317
+
318
+ ## useState
319
+
320
+ 用于管理组件状态。
321
+
322
+ ## useEffect
323
+
324
+ 处理副作用,常见用途:
325
+ - 请求数据
326
+ - 订阅事件
327
+ - 操作 DOM
328
+
329
+ ## 自定义 Hook
330
+
331
+ - 命名必须以 \`use\` 开头
332
+ - 可以复用状态逻辑
333
+
334
+ ---
335
+
336
+ ## 🔗 Related Notes
337
+
338
+ - [[React 基础入门]]
339
+ - [[useState 详解]]
340
+ - [[useEffect 最佳实践]]
341
+ - [[自定义 Hook 封装技巧]]
342
+ \`\`\`
343
+
344
+ ## 五、注意事项
345
+
346
+ 1. **保持原意**:提示词会保留你的原始想法,只做结构化整理
347
+ 2. **智能分类**:根据内容自动判断分类和存放路径
348
+ 3. **灵活调整**:生成的 folder 路径可以根据实际情况手动调整
349
+ 4. **批量处理**:可以一次性粘贴多段笔记内容进行整理
350
+ `;
351
+ return guide;
352
+ }
353
+ // 全文搜索
354
+ async function fullTextSearch(keyword) {
355
+ const files = await glob("**/*.md", {
356
+ cwd: VAULT_PATH,
357
+ ignore: [".obsidian/**", ".smart-env/**", ".windsurf/**"],
358
+ });
359
+ const results = [];
360
+ for (const file of files) {
361
+ try {
362
+ const content = await fs.readFile(path.join(VAULT_PATH, file), "utf-8");
363
+ const lines = content.split("\n");
364
+ const matches = [];
365
+ lines.forEach((line, index) => {
366
+ if (line.toLowerCase().includes(keyword.toLowerCase())) {
367
+ matches.push(`L${index + 1}: ${line.trim().substring(0, 100)}`);
368
+ }
369
+ });
370
+ if (matches.length > 0) {
371
+ results.push({ path: file, matches: matches.slice(0, 5) });
372
+ }
373
+ }
374
+ catch {
375
+ // 忽略读取错误
376
+ }
377
+ }
378
+ return results.slice(0, 20);
379
+ }
380
+ // 创建 MCP 服务器
381
+ const server = new Server({
382
+ name: "obsidian-notes-server",
383
+ version: "1.0.0",
384
+ }, {
385
+ capabilities: {
386
+ tools: {},
387
+ },
388
+ });
389
+ // 注册工具列表
390
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
391
+ tools: [
392
+ {
393
+ name: "search_notes",
394
+ description: "搜索 Obsidian 笔记库中的笔记,支持关键词、标签、分类过滤",
395
+ inputSchema: {
396
+ type: "object",
397
+ properties: {
398
+ query: { type: "string", description: "搜索关键词(匹配笔记名称和摘要)" },
399
+ tag: { type: "string", description: "按标签过滤" },
400
+ category: { type: "string", description: "按分类过滤(如 hardware, ai, backend 等)" },
401
+ },
402
+ },
403
+ },
404
+ {
405
+ name: "read_note",
406
+ description: "读取指定笔记的完整内容",
407
+ inputSchema: {
408
+ type: "object",
409
+ properties: {
410
+ path: { type: "string", description: "笔记的相对路径(如 知识点/03-硬件学习/STM32系列选型速查.md)" },
411
+ },
412
+ required: ["path"],
413
+ },
414
+ },
415
+ {
416
+ name: "list_folder",
417
+ description: "列出指定文件夹下的子文件夹和笔记",
418
+ inputSchema: {
419
+ type: "object",
420
+ properties: {
421
+ folder: { type: "string", description: "文件夹路径(留空则列出根目录)" },
422
+ },
423
+ },
424
+ },
425
+ {
426
+ name: "get_note_structure",
427
+ description: "获取整个笔记库的目录结构概览",
428
+ inputSchema: { type: "object", properties: {} },
429
+ },
430
+ {
431
+ name: "full_text_search",
432
+ description: "在所有笔记中进行全文搜索",
433
+ inputSchema: {
434
+ type: "object",
435
+ properties: {
436
+ keyword: { type: "string", description: "要搜索的关键词" },
437
+ },
438
+ required: ["keyword"],
439
+ },
440
+ },
441
+ {
442
+ name: "create_note",
443
+ description: "创建新笔记",
444
+ inputSchema: {
445
+ type: "object",
446
+ properties: {
447
+ path: { type: "string", description: "笔记的相对路径(如 知识点/04-人工智能/MCP/自己制作的MCP/新笔记.md)" },
448
+ content: { type: "string", description: "笔记内容(支持 Markdown 和 Frontmatter)" },
449
+ },
450
+ required: ["path", "content"],
451
+ },
452
+ },
453
+ {
454
+ name: "update_note",
455
+ description: "更新已存在的笔记内容",
456
+ inputSchema: {
457
+ type: "object",
458
+ properties: {
459
+ path: { type: "string", description: "笔记的相对路径" },
460
+ content: { type: "string", description: "新的笔记内容" },
461
+ },
462
+ required: ["path", "content"],
463
+ },
464
+ },
465
+ {
466
+ name: "delete_note",
467
+ description: "删除指定笔记",
468
+ inputSchema: {
469
+ type: "object",
470
+ properties: {
471
+ path: { type: "string", description: "笔记的相对路径" },
472
+ },
473
+ required: ["path"],
474
+ },
475
+ },
476
+ {
477
+ name: "create_folder",
478
+ description: "创建新文件夹",
479
+ inputSchema: {
480
+ type: "object",
481
+ properties: {
482
+ path: { type: "string", description: "文件夹的相对路径(如 知识点/04-人工智能/MCP/新文件夹)" },
483
+ },
484
+ required: ["path"],
485
+ },
486
+ },
487
+ {
488
+ name: "get_prompt_guide",
489
+ description: "获取 Obsidian 知识库整理助手提示词的使用指南,展示如何添加和使用这个提示词",
490
+ inputSchema: {
491
+ type: "object",
492
+ properties: {},
493
+ },
494
+ },
495
+ ],
496
+ }));
497
+ // 处理工具调用
498
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
499
+ const { name, arguments: args } = request.params;
500
+ try {
501
+ switch (name) {
502
+ case "search_notes": {
503
+ const results = await searchNotes(args?.query, args?.tag, args?.category);
504
+ return {
505
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
506
+ };
507
+ }
508
+ case "read_note": {
509
+ const content = await readNote(args?.path);
510
+ return {
511
+ content: [{ type: "text", text: content }],
512
+ };
513
+ }
514
+ case "list_folder": {
515
+ const result = await listFolder(args?.folder || "");
516
+ return {
517
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
518
+ };
519
+ }
520
+ case "get_note_structure": {
521
+ const structure = await getNoteStructure();
522
+ return {
523
+ content: [{ type: "text", text: JSON.stringify(structure, null, 2) }],
524
+ };
525
+ }
526
+ case "full_text_search": {
527
+ const results = await fullTextSearch(args?.keyword);
528
+ return {
529
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
530
+ };
531
+ }
532
+ case "create_note": {
533
+ const result = await createNote(args?.path, args?.content);
534
+ return {
535
+ content: [{ type: "text", text: result }],
536
+ };
537
+ }
538
+ case "update_note": {
539
+ const result = await updateNote(args?.path, args?.content);
540
+ return {
541
+ content: [{ type: "text", text: result }],
542
+ };
543
+ }
544
+ case "delete_note": {
545
+ const result = await deleteNote(args?.path);
546
+ return {
547
+ content: [{ type: "text", text: result }],
548
+ };
549
+ }
550
+ case "create_folder": {
551
+ const result = await createFolder(args?.path);
552
+ return {
553
+ content: [{ type: "text", text: result }],
554
+ };
555
+ }
556
+ case "get_prompt_guide": {
557
+ const guide = await getPromptGuide();
558
+ return {
559
+ content: [{ type: "text", text: guide }],
560
+ };
561
+ }
562
+ default:
563
+ throw new Error(`未知工具: ${name}`);
564
+ }
565
+ }
566
+ catch (error) {
567
+ return {
568
+ content: [{ type: "text", text: `错误: ${error}` }],
569
+ isError: true,
570
+ };
571
+ }
572
+ });
573
+ // 启动服务器
574
+ async function main() {
575
+ // 验证笔记库路径是否存在
576
+ try {
577
+ await fs.access(VAULT_PATH);
578
+ }
579
+ catch {
580
+ console.error(`错误: 笔记库路径不存在: ${VAULT_PATH}`);
581
+ process.exit(1);
582
+ }
583
+ const transport = new StdioServerTransport();
584
+ await server.connect(transport);
585
+ console.error(`Obsidian MCP Server 已启动,笔记库: ${VAULT_PATH}`);
586
+ }
587
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@fromsko/obsidian-mcp-server",
3
+ "version": "1.1.7",
4
+ "description": "MCP server for Obsidian notes - search and read your Obsidian vault",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "obsidian-mcp-server": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "tsx src/index.ts",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "obsidian",
22
+ "markdown",
23
+ "notes",
24
+ "ai",
25
+ "claude",
26
+ "windsurf"
27
+ ],
28
+ "author": "fromsko",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/fromsko/obsidian-mcp-server.git"
33
+ },
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.0.0",
36
+ "gray-matter": "^4.0.3",
37
+ "glob": "^10.3.10"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.10.0",
41
+ "tsx": "^4.7.0",
42
+ "typescript": "^5.3.0"
43
+ }
44
+ }