pkm-mcp-server 1.5.3 → 1.7.0
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/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +15 -0
- package/.mcp.json +10 -0
- package/CHANGELOG.md +72 -5
- package/README.md +104 -73
- package/cli.js +10 -2
- package/commands/setup.md +50 -0
- package/handlers.js +45 -47
- package/hooks/README.md +4 -4
- package/hooks/capture-handler.sh +18 -14
- package/hooks/hooks.json +35 -0
- package/hooks/stop-sweep.js +6 -2
- package/index.js +2 -2
- package/init.js +5 -5
- package/package.json +15 -9
- package/sample-project/CLAUDE.md +1 -0
- package/skills/.gitkeep +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "obsidian-pkm-marketplace",
|
|
3
|
+
"description": "Obsidian PKM Plugin for Claude Code",
|
|
4
|
+
"plugins": [
|
|
5
|
+
{
|
|
6
|
+
"name": "obsidian-pkm",
|
|
7
|
+
"description": "Bidirectional knowledge flow between Claude Code and Obsidian — tools, skills, and hooks for PKM",
|
|
8
|
+
"source": "."
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "obsidian-pkm",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Bidirectional knowledge flow between Claude Code and Obsidian — tools, skills, and hooks for PKM",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Adrian Verhoosel"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/AdrianV101/obsidian-pkm-plugin",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["obsidian", "pkm", "knowledge-management", "mcp", "claude-code"],
|
|
11
|
+
"mcpServers": "./.mcp.json",
|
|
12
|
+
"hooks": "./hooks/hooks.json",
|
|
13
|
+
"commands": ["./commands"],
|
|
14
|
+
"skills": ["./skills"]
|
|
15
|
+
}
|
package/.mcp.json
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,54 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.0.0] - 2026-03-24
|
|
10
|
+
|
|
11
|
+
### Breaking Changes
|
|
12
|
+
- **Package renamed**: `pkm-mcp-server` → `obsidian-pkm`
|
|
13
|
+
- **CLI binary renamed**: `pkm-mcp-server` → `obsidian-pkm`
|
|
14
|
+
- **GitHub repo renamed**: `Obsidian-MCP` → `obsidian-pkm-plugin`
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Claude Code plugin structure (`.claude-plugin/plugin.json`)
|
|
18
|
+
- Declarative hook registration (`hooks/hooks.json`) — no more manual hook copying
|
|
19
|
+
- `/obsidian-pkm:setup` slash command for guided configuration
|
|
20
|
+
- Self-hosted marketplace for `claude plugin install` support
|
|
21
|
+
- `.mcp.json` for plugin-managed MCP server
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Version jump from 1.6.x to 2.0.0 reflects the structural migration to a full plugin
|
|
25
|
+
- Init wizard simplified for npm fallback path
|
|
26
|
+
|
|
27
|
+
## [1.6.0] - 2026-03-23
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
- **Fix command injection in capture-handler.sh** — replaced `eval` of Node.js output with safe per-variable stdout capture. Previously, crafted `title` or `content` values containing shell metacharacters (e.g., `$(...)`) could execute arbitrary commands.
|
|
31
|
+
- `handleTrash` and `handleMove` now catch ENOENT errors and return relative paths instead of leaking absolute vault paths in error messages
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Wikilink extraction in `handleLinks` and `handleSuggestLinks` now uses shared `extractWikilinks` from `graph.js` instead of duplicating the regex
|
|
35
|
+
- Extracted `addToBasenameMap`/`removeFromBasenameMap` helpers in `handlers.js` (replaces 3 inline copies)
|
|
36
|
+
- Named constant `SESSION_ID_DISPLAY_LEN` replaces magic number `8` for session ID truncation
|
|
37
|
+
- `@modelcontextprotocol/sdk` version range tightened from `^1.0.0` to `^1.27.0` to reflect actual minimum tested version
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- README hook configuration referenced deleted `stop-sweep.sh` (renamed to `stop-sweep.js` in v1.5.0)
|
|
41
|
+
- `vault_peek` tool description no longer claims "line numbers" in heading outline (removed in v1.1.0)
|
|
42
|
+
- CHANGELOG: added missing entries for v1.5.3, v1.5.2, v1.1.1; fixed all comparison links
|
|
43
|
+
- CLAUDE.md and sample-project/CLAUDE.md: added missing `note.md` template to template lists
|
|
44
|
+
- CONTRIBUTING.md: clarified that double quotes and semicolons are conventions, not lint-enforced
|
|
45
|
+
- CI workflow: added `permissions: {}` for least privilege; added `npm audit --omit=dev` step
|
|
46
|
+
|
|
47
|
+
## [1.5.3] - 2026-03-21
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- Init wizard `patchMcpConfig()` now handles multi-line `MCP_CONFIG=` blocks correctly — previously only replaced the first line, leaving orphaned continuation lines
|
|
51
|
+
|
|
52
|
+
## [1.5.2] - 2026-03-21
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- Hook scripts now auto-detect repo vs installed location — `capture-handler.sh` no longer hardcodes `node $SCRIPT_DIR/../index.js`, falling back to `npx pkm-mcp-server@latest` when `index.js` is not present
|
|
56
|
+
|
|
9
57
|
## [1.5.1] - 2026-03-21
|
|
10
58
|
|
|
11
59
|
### Fixed
|
|
@@ -109,6 +157,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
109
157
|
### Security
|
|
110
158
|
- Resolved 7 transitive dependency vulnerabilities (hono, @hono/node-server, ajv, express-rate-limit, flatted, minimatch, qs)
|
|
111
159
|
|
|
160
|
+
## [1.1.1] - 2026-02-24
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
- Enum validation for task `status` and `priority` fields in `vault_write` and `vault_update_frontmatter`
|
|
164
|
+
|
|
112
165
|
## [1.1.0] - 2026-02-23
|
|
113
166
|
|
|
114
167
|
### Changed
|
|
@@ -158,8 +211,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
158
211
|
- Atomic file creation in `vault_write` (`wx` flag) prevents race conditions
|
|
159
212
|
- Error messages sanitized to prevent leaking absolute vault paths
|
|
160
213
|
|
|
161
|
-
[Unreleased]: https://github.com/AdrianV101/
|
|
162
|
-
[
|
|
163
|
-
[1.
|
|
164
|
-
[1.
|
|
165
|
-
[1.
|
|
214
|
+
[Unreleased]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v2.0.0...HEAD
|
|
215
|
+
[2.0.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.6.0...v2.0.0
|
|
216
|
+
[1.6.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.5.3...v1.6.0
|
|
217
|
+
[1.5.3]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.5.2...v1.5.3
|
|
218
|
+
[1.5.2]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.5.1...v1.5.2
|
|
219
|
+
[1.5.1]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.5.0...v1.5.1
|
|
220
|
+
[1.5.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.4.2...v1.5.0
|
|
221
|
+
[1.4.2]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.4.1...v1.4.2
|
|
222
|
+
[1.4.1]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.4.0...v1.4.1
|
|
223
|
+
[1.4.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.3.3...v1.4.0
|
|
224
|
+
[1.3.3]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.3.2...v1.3.3
|
|
225
|
+
[1.3.2]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.3.1...v1.3.2
|
|
226
|
+
[1.3.1]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.3.0...v1.3.1
|
|
227
|
+
[1.3.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.2.1...v1.3.0
|
|
228
|
+
[1.2.1]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.2.0...v1.2.1
|
|
229
|
+
[1.2.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.1.1...v1.2.0
|
|
230
|
+
[1.1.1]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.1.0...v1.1.1
|
|
231
|
+
[1.1.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/compare/v1.0.0...v1.1.0
|
|
232
|
+
[1.0.0]: https://github.com/AdrianV101/obsidian-pkm-plugin/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -1,75 +1,76 @@
|
|
|
1
|
-
# Obsidian PKM
|
|
1
|
+
# Obsidian PKM Plugin
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/obsidian-pkm)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://github.com/AdrianV101/obsidian-pkm-plugin/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
A Claude Code plugin that turns your Obsidian vault into persistent, structured memory for AI coding assistants. Provides 19 MCP tools for note creation, semantic search, graph traversal, metadata queries, session memory, and passive knowledge capture — plus hooks and skills for seamless workflow integration. Published on npm as [`obsidian-pkm`](https://www.npmjs.com/package/obsidian-pkm).
|
|
9
9
|
|
|
10
10
|
## Why
|
|
11
11
|
|
|
12
|
-
Claude Code
|
|
12
|
+
Claude Code has built-in memory, but it's flat text files scoped to individual projects — no structure, no search beyond exact matches, no connections between notes, and no way to query across projects. As knowledge grows, it doesn't scale. This server replaces that with a proper PKM layer: structured notes with enforced metadata, semantic search, a navigable knowledge graph, and cross-project access through a single Obsidian vault.
|
|
13
13
|
|
|
14
|
-
- **
|
|
15
|
-
- **Structured knowledge**
|
|
16
|
-
- **Semantic
|
|
17
|
-
- **Graph
|
|
14
|
+
- **Structured session memory** — Every tool call is logged with timestamps and session IDs, so Claude can recall exactly what was read, written, and searched in previous conversations — not just what was saved to a text file.
|
|
15
|
+
- **Structured knowledge creation** — ADRs, research notes, devlogs, and tasks are created from enforced templates with validated frontmatter — not freeform text dumps. Your vault stays consistent and queryable.
|
|
16
|
+
- **Semantic discovery** — "Find my notes about caching strategies" works even if you never used the word "caching." Conceptual search surfaces relevant knowledge that keyword search misses.
|
|
17
|
+
- **Graph-aware connections** — Claude explores your knowledge graph by following wikilinks, discovering related notes by proximity rather than just content. Link suggestions help weave new notes into your existing web of knowledge.
|
|
18
|
+
- **Passive capture** — Decisions, tasks, and research findings are captured in the background without interrupting your coding flow. A session-end sweep catches anything that slipped through.
|
|
18
19
|
|
|
19
|
-
Without this,
|
|
20
|
+
Without this, knowledge stays fragmented across per-project memory files and chat logs. With it, your AI assistant maintains a unified knowledge base that compounds over time.
|
|
20
21
|
|
|
21
22
|
https://github.com/user-attachments/assets/58ad9c9b-d987-4728-89e7-33de20b73a38
|
|
22
23
|
|
|
23
24
|
## Features
|
|
24
25
|
|
|
26
|
+
### Knowledge Creation & Editing
|
|
27
|
+
|
|
25
28
|
| Tool | Description |
|
|
26
29
|
|------|-------------|
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `vault_search` | Full-text search across markdown files |
|
|
33
|
-
| `vault_semantic_search` | Semantic similarity search via OpenAI embeddings |
|
|
34
|
-
| `vault_suggest_links` | Suggest relevant notes to link based on content similarity |
|
|
35
|
-
| `vault_list` | List files and folders |
|
|
36
|
-
| `vault_recent` | Recently modified files |
|
|
37
|
-
| `vault_links` | Wikilink analysis (incoming/outgoing) |
|
|
38
|
-
| `vault_neighborhood` | Graph exploration via BFS wikilink traversal |
|
|
39
|
-
| `vault_query` | Query notes by YAML frontmatter (type, status, tags/tags_any, dates, custom fields, sorting) |
|
|
40
|
-
| `vault_tags` | Discover tags with counts; folder scoping, glob filters, inline tag parsing |
|
|
41
|
-
| `vault_activity` | Session activity log for cross-conversation memory |
|
|
42
|
-
| `vault_trash` | Soft-delete to `.trash/` (Obsidian convention), warns about broken incoming links |
|
|
43
|
-
| `vault_move` | Move/rename files with automatic wikilink updating across vault |
|
|
44
|
-
| `vault_update_frontmatter` | Atomic YAML frontmatter updates (set, create, remove fields; validates enum fields by note type) |
|
|
45
|
-
| `vault_capture` | Signal a PKM-worthy capture (decision, task, research, bug); returns immediately, background hook creates the note |
|
|
30
|
+
| `vault_write` | Create notes from templates with enforced frontmatter (ADRs, research, devlogs, tasks, etc.) |
|
|
31
|
+
| `vault_append` | Add content to notes, with positional insert (after/before heading, end of section) |
|
|
32
|
+
| `vault_edit` | Surgical string replacement for precise edits |
|
|
33
|
+
| `vault_update_frontmatter` | Atomic YAML frontmatter updates (set, create, remove fields; validates enums by note type) |
|
|
34
|
+
| `vault_capture` | Signal a PKM-worthy capture (decision, task, research, bug); background hook creates the note |
|
|
46
35
|
|
|
47
|
-
###
|
|
36
|
+
### Discovery & Search
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `vault_search` | Full-text keyword search across markdown files |
|
|
41
|
+
| `vault_semantic_search` | Conceptual similarity search via OpenAI embeddings — finds related notes even with different wording |
|
|
42
|
+
| `vault_query` | Query by YAML frontmatter (type, status, tags, dates, custom fields) with sorting |
|
|
43
|
+
| `vault_tags` | Discover all tags with per-note counts; folder scoping, glob filters, inline tag parsing |
|
|
44
|
+
| `vault_suggest_links` | Suggest relevant notes to link based on content similarity |
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
vault_read({ path: "devlog" })
|
|
53
|
-
// Resolves to: 01-Projects/Obsidian-MCP/development/devlog.md
|
|
46
|
+
### Graph & Connections
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
| Tool | Description |
|
|
49
|
+
|------|-------------|
|
|
50
|
+
| `vault_links` | Wikilink analysis (incoming and outgoing links for a note) |
|
|
51
|
+
| `vault_neighborhood` | Graph exploration via BFS wikilink traversal — discover related notes by proximity |
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
// Works on vault_peek, vault_links, vault_neighborhood, vault_suggest_links too
|
|
60
|
-
```
|
|
53
|
+
### Reading & Navigation
|
|
61
54
|
|
|
62
|
-
|
|
55
|
+
| Tool | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `vault_read` | Read note contents (pagination by heading, tail, chunk, line range; auto-redirects large files) |
|
|
58
|
+
| `vault_peek` | Inspect file metadata and structure without reading full content |
|
|
59
|
+
| `vault_list` | List files and folders |
|
|
60
|
+
| `vault_recent` | Recently modified files |
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
vault_search({ query: "API design", folder: "Obsidian-MCP" })
|
|
66
|
-
// Resolves folder to: 01-Projects/Obsidian-MCP
|
|
62
|
+
### Organization & Maintenance
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
| Tool | Description |
|
|
65
|
+
|------|-------------|
|
|
66
|
+
| `vault_move` | Move/rename files with automatic wikilink updating across the vault |
|
|
67
|
+
| `vault_trash` | Soft-delete to `.trash/` (Obsidian convention), warns about broken incoming links |
|
|
71
68
|
|
|
72
|
-
|
|
69
|
+
### Session Memory
|
|
70
|
+
|
|
71
|
+
| Tool | Description |
|
|
72
|
+
|------|-------------|
|
|
73
|
+
| `vault_activity` | Cross-conversation memory — logs every tool call with timestamps and session IDs |
|
|
73
74
|
|
|
74
75
|
## Prerequisites
|
|
75
76
|
|
|
@@ -84,14 +85,32 @@ Ambiguous matches return an error listing candidates. Exact paths always work un
|
|
|
84
85
|
|
|
85
86
|
### 1. Install and Set Up
|
|
86
87
|
|
|
87
|
-
**
|
|
88
|
+
**Via Claude Code plugin marketplace** (recommended):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
claude plugin marketplace add AdrianV101/obsidian-pkm-plugin
|
|
92
|
+
claude plugin install obsidian-pkm
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then run the setup skill in Claude Code:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
/obsidian-pkm:setup
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The setup skill walks you through vault path, templates, folder structure, semantic search, and Claude Code registration.
|
|
102
|
+
|
|
103
|
+
**Via npm** (fallback):
|
|
88
104
|
|
|
89
105
|
```bash
|
|
90
|
-
npm install -g pkm
|
|
91
|
-
pkm
|
|
106
|
+
npm install -g obsidian-pkm
|
|
107
|
+
obsidian-pkm init
|
|
92
108
|
```
|
|
93
109
|
|
|
94
|
-
The setup wizard walks you through
|
|
110
|
+
The setup wizard walks you through vault path, templates, folder structure, semantic search, and Claude Code registration. Nothing is written until you confirm each step, and you can press Ctrl+C at any time to cancel.
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary>Wizard step details</summary>
|
|
95
114
|
|
|
96
115
|
**Step 1 — Vault path.** Point to an existing Obsidian vault or create a new one. The wizard resolves `~`, `$HOME`, and relative paths automatically. Safety checks prevent using system directories (`/`, `/home`, etc.) as a vault. For existing non-empty directories you can use it as-is, create a subfolder inside it, or wipe it (with triple confirmation). You'll be offered an optional backup before any changes — this creates a timestamped copy next to the vault (e.g. `PKM-backup-2026-03-21T14-30-00/`).
|
|
97
116
|
|
|
@@ -116,29 +135,31 @@ Existing templates are never overwritten.
|
|
|
116
135
|
|
|
117
136
|
Each `_index.md` has `type: moc` frontmatter. Existing folders and index files are skipped.
|
|
118
137
|
|
|
119
|
-
**Step 4 — OpenAI API key (optional).** Enables `vault_semantic_search` and `vault_suggest_links`. The key is stored only in your Claude Code configuration (`~/.claude.json`) and is used solely for generating text embeddings. You can add this later — see [Enable Semantic Search](#
|
|
138
|
+
**Step 4 — OpenAI API key (optional).** Enables `vault_semantic_search` and `vault_suggest_links`. The key is stored only in your Claude Code configuration (`~/.claude.json`) and is used solely for generating text embeddings. You can add this later — see [Enable Semantic Search](#4-enable-semantic-search-optional).
|
|
120
139
|
|
|
121
140
|
**Step 5 — Claude Code registration.** Registers the MCP server via `claude mcp add -s user`. If `obsidian-pkm` is already registered, you'll be asked whether to overwrite. The exact command is shown for confirmation before running. If the `claude` CLI is not found on PATH, the wizard prints the manual registration command instead.
|
|
122
141
|
|
|
142
|
+
</details>
|
|
143
|
+
|
|
123
144
|
Restart Claude Code after setup. The server provides all tools except semantic search out of the box.
|
|
124
145
|
|
|
125
146
|
**From source:**
|
|
126
147
|
|
|
127
148
|
```bash
|
|
128
|
-
git clone https://github.com/AdrianV101/
|
|
129
|
-
cd
|
|
149
|
+
git clone https://github.com/AdrianV101/obsidian-pkm-plugin.git
|
|
150
|
+
cd obsidian-pkm-plugin
|
|
130
151
|
npm install
|
|
131
152
|
node cli.js init
|
|
132
153
|
```
|
|
133
154
|
|
|
134
|
-
You can also run the wizard without a global install: `npx pkm
|
|
155
|
+
You can also run the wizard without a global install: `npx obsidian-pkm init`.
|
|
135
156
|
|
|
136
157
|
### 2. Manual Registration (alternative)
|
|
137
158
|
|
|
138
159
|
If you prefer to skip the wizard, register directly with the Claude CLI:
|
|
139
160
|
|
|
140
161
|
```bash
|
|
141
|
-
claude mcp add -s user -e VAULT_PATH=/absolute/path/to/your/vault -- obsidian-pkm npx -y pkm
|
|
162
|
+
claude mcp add -s user -e VAULT_PATH=/absolute/path/to/your/vault -- obsidian-pkm npx -y obsidian-pkm@latest
|
|
142
163
|
```
|
|
143
164
|
|
|
144
165
|
For a source install:
|
|
@@ -149,7 +170,15 @@ claude mcp add -s user -e VAULT_PATH=/absolute/path/to/your/vault -- obsidian-pk
|
|
|
149
170
|
|
|
150
171
|
Verify with `claude mcp list` — you should see `obsidian-pkm: ... - Connected`.
|
|
151
172
|
|
|
152
|
-
### 3.
|
|
173
|
+
### 3. Verify It Works
|
|
174
|
+
|
|
175
|
+
Open Claude Code and try:
|
|
176
|
+
|
|
177
|
+
> List the folders in my vault
|
|
178
|
+
|
|
179
|
+
Claude should call `vault_list` and show your vault's directory structure. If it works, the server is connected and ready.
|
|
180
|
+
|
|
181
|
+
### 4. Enable Semantic Search (optional)
|
|
153
182
|
|
|
154
183
|
If you didn't set this up during `init`, add your OpenAI API key by re-registering:
|
|
155
184
|
|
|
@@ -158,12 +187,12 @@ claude mcp remove obsidian-pkm
|
|
|
158
187
|
claude mcp add -s user \
|
|
159
188
|
-e VAULT_PATH=/absolute/path/to/your/vault \
|
|
160
189
|
-e OPENAI_API_KEY=sk-... \
|
|
161
|
-
-- obsidian-pkm npx -y pkm
|
|
190
|
+
-- obsidian-pkm npx -y obsidian-pkm@latest
|
|
162
191
|
```
|
|
163
192
|
|
|
164
193
|
This enables `vault_semantic_search` and `vault_suggest_links`. Uses `text-embedding-3-large` with a SQLite + sqlite-vec index stored at `.obsidian/semantic-index.db`. The index rebuilds automatically — delete the DB file to force a full re-embed.
|
|
165
194
|
|
|
166
|
-
###
|
|
195
|
+
### 5. Enable PKM Hooks (optional)
|
|
167
196
|
|
|
168
197
|
The hook system adds automatic context loading at session start and passive knowledge capture during coding. Requires the [Claude CLI](https://docs.anthropic.com/en/docs/claude-cli) installed and authenticated.
|
|
169
198
|
|
|
@@ -178,7 +207,7 @@ Add to your `~/.claude/settings.json` (alongside the `mcpServers` block):
|
|
|
178
207
|
"hooks": [
|
|
179
208
|
{
|
|
180
209
|
"type": "command",
|
|
181
|
-
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/
|
|
210
|
+
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/obsidian-pkm-plugin/hooks/session-start.js",
|
|
182
211
|
"timeout": 15,
|
|
183
212
|
"statusMessage": "Loading PKM project context..."
|
|
184
213
|
}
|
|
@@ -190,7 +219,7 @@ Add to your `~/.claude/settings.json` (alongside the `mcpServers` block):
|
|
|
190
219
|
"hooks": [
|
|
191
220
|
{
|
|
192
221
|
"type": "command",
|
|
193
|
-
"command": "VAULT_PATH=\"/path/to/your/vault\" /path/to/
|
|
222
|
+
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/obsidian-pkm-plugin/hooks/stop-sweep.js",
|
|
194
223
|
"async": true,
|
|
195
224
|
"timeout": 10
|
|
196
225
|
}
|
|
@@ -203,7 +232,7 @@ Add to your `~/.claude/settings.json` (alongside the `mcpServers` block):
|
|
|
203
232
|
"hooks": [
|
|
204
233
|
{
|
|
205
234
|
"type": "command",
|
|
206
|
-
"command": "VAULT_PATH=\"/path/to/your/vault\" /path/to/
|
|
235
|
+
"command": "VAULT_PATH=\"/path/to/your/vault\" /path/to/obsidian-pkm-plugin/hooks/capture-handler.sh",
|
|
207
236
|
"async": true,
|
|
208
237
|
"timeout": 10
|
|
209
238
|
}
|
|
@@ -214,12 +243,12 @@ Add to your `~/.claude/settings.json` (alongside the `mcpServers` block):
|
|
|
214
243
|
}
|
|
215
244
|
```
|
|
216
245
|
|
|
217
|
-
Replace `/path/to/your/vault` with your Obsidian vault path and `/path/to/
|
|
246
|
+
Replace `/path/to/your/vault` with your Obsidian vault path and `/path/to/obsidian-pkm-plugin` with the path to this repo (or the global npm install location).
|
|
218
247
|
|
|
219
248
|
| Hook | Event | What it does |
|
|
220
249
|
|------|-------|--------------|
|
|
221
250
|
| `session-start.js` | SessionStart | Loads project context (index, devlog, active tasks) at session start |
|
|
222
|
-
| `stop-sweep.
|
|
251
|
+
| `stop-sweep.js` | Stop | PKM librarian: creates structured, graph-linked vault notes from the latest exchange |
|
|
223
252
|
| `capture-handler.sh` | PostToolUse | Creates structured vault notes when `vault_capture` is called |
|
|
224
253
|
|
|
225
254
|
See [hooks/README.md](hooks/README.md) for architecture details and troubleshooting.
|
|
@@ -246,7 +275,7 @@ Vault/
|
|
|
246
275
|
|
|
247
276
|
### Templates
|
|
248
277
|
|
|
249
|
-
`vault_write` loads all `.md` files from `05-Templates/` at startup and enforces frontmatter on every note created. The setup wizard (`pkm
|
|
278
|
+
`vault_write` loads all `.md` files from `05-Templates/` at startup and enforces frontmatter on every note created. The setup wizard (`obsidian-pkm init`) installs these automatically — or you can copy the files from `templates/` manually.
|
|
250
279
|
|
|
251
280
|
13 included templates: `adr`, `daily-note`, `devlog`, `fleeting-note`, `literature-note`, `meeting-notes`, `moc`, `note`, `permanent-note`, `project-index`, `research-note`, `task`, `troubleshooting-log`. Add your own templates to `05-Templates/` and they become available to `vault_write` automatically.
|
|
252
281
|
|
|
@@ -288,15 +317,17 @@ All paths passed to tools are relative to vault root. The server includes path s
|
|
|
288
317
|
|
|
289
318
|
## How It Works
|
|
290
319
|
|
|
291
|
-
**
|
|
320
|
+
**Knowledge creation** is template-based. `vault_write` loads templates from `05-Templates/`, substitutes Templater-compatible variables (`<% tp.date.now("YYYY-MM-DD") %>`, `<% tp.file.title %>`), and validates required frontmatter fields (`type`, `created`, `tags`). This ensures every note in your vault has consistent metadata — making it queryable, sortable, and discoverable from day one. Task notes enforce enum validation on `status` and `priority`; other types accept `project`, `deciders`, `due`, and `source`.
|
|
321
|
+
|
|
322
|
+
**Knowledge discovery** works at two levels. Keyword search (`vault_search`) finds exact terms. Semantic search embeds notes using OpenAI and finds conceptually related content — so "managing overwhelm" surfaces notes about "cognitive load" even if those exact words never appear together. The semantic index watches for file changes in real-time and syncs across machines via Obsidian Sync.
|
|
292
323
|
|
|
293
|
-
**
|
|
324
|
+
**Knowledge connections** are maintained through Obsidian's `[[wikilink]]` graph. `vault_neighborhood` traverses links via BFS to discover related notes by proximity, while `vault_suggest_links` recommends connections you haven't made yet. `vault_move` rewrites wikilinks across the vault when you reorganize, and `vault_trash` warns about links that would break.
|
|
294
325
|
|
|
295
|
-
**
|
|
326
|
+
**Session memory** records every tool call with timestamps and session IDs, so Claude can recall what was read, written, or searched in previous conversations. This turns ephemeral chat sessions into a continuous thread of work.
|
|
296
327
|
|
|
297
|
-
**
|
|
328
|
+
**Passive capture** uses `vault_capture` to signal that something is worth persisting (a decision, task, research finding, or bug). The tool returns immediately — a PostToolUse hook spawns a background agent that creates the structured vault note. Combined with the Stop hook (which sweeps each session for un-captured knowledge), this keeps your vault up to date without interrupting the coding flow.
|
|
298
329
|
|
|
299
|
-
**
|
|
330
|
+
**Fuzzy path resolution** lets read-only tools accept short names instead of full vault paths. `vault_read({ path: "devlog" })` resolves to `01-Projects/MyApp/development/devlog.md` automatically (`.md` extension optional). Folder-scoped tools like `vault_search` and `vault_query` accept partial folder names — `folder: "MyApp"` resolves to `01-Projects/MyApp`. Ambiguous matches return an error listing candidates. Write/destructive tools always require exact paths.
|
|
300
331
|
|
|
301
332
|
## Troubleshooting
|
|
302
333
|
|
|
@@ -304,13 +335,13 @@ All paths passed to tools are relative to vault root. The server includes path s
|
|
|
304
335
|
You need C++ build tools. See [Prerequisites](#prerequisites) for your platform. On Linux, `sudo apt install build-essential python3` usually fixes it.
|
|
305
336
|
|
|
306
337
|
**Server starts but all tool calls fail with ENOENT**
|
|
307
|
-
Your `VAULT_PATH` is wrong or missing. The server validates this at startup and exits with a clear error. Re-register with the correct path: `claude mcp remove obsidian-pkm && claude mcp add -s user -e VAULT_PATH=/correct/path -- obsidian-pkm npx -y pkm
|
|
338
|
+
Your `VAULT_PATH` is wrong or missing. The server validates this at startup and exits with a clear error. Re-register with the correct path: `claude mcp remove obsidian-pkm && claude mcp add -s user -e VAULT_PATH=/correct/path -- obsidian-pkm npx -y obsidian-pkm@latest`
|
|
308
339
|
|
|
309
340
|
**`vault_write` says "no templates available"**
|
|
310
|
-
Run `pkm
|
|
341
|
+
Run `obsidian-pkm init` to install templates, or copy the `templates/` files from this repo into your vault's `05-Templates/` directory. The server loads templates from there at startup.
|
|
311
342
|
|
|
312
343
|
**Semantic search not appearing in tool list**
|
|
313
|
-
Set `OPENAI_API_KEY` in your MCP server registration. See [Enable Semantic Search](#
|
|
344
|
+
Set `OPENAI_API_KEY` in your MCP server registration. See [Enable Semantic Search](#4-enable-semantic-search-optional). Without it, `vault_semantic_search` and `vault_suggest_links` are hidden entirely.
|
|
314
345
|
|
|
315
346
|
**Server not showing up in Claude Code after install**
|
|
316
347
|
Run `claude mcp list` to check. If `obsidian-pkm` is missing, register it with `claude mcp add` (see [Manual Registration](#2-manual-registration-alternative)). Note: editing `~/.claude/settings.json` directly does **not** register MCP servers — use the CLI.
|
package/cli.js
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
|
|
5
|
+
console.warn("");
|
|
6
|
+
console.warn(" pkm-mcp-server has been renamed to obsidian-pkm.");
|
|
7
|
+
console.warn(" Install the new version:");
|
|
8
|
+
console.warn(" Marketplace: claude plugin marketplace add AdrianV101/obsidian-pkm-plugin");
|
|
9
|
+
console.warn(" npm: npm install -g obsidian-pkm");
|
|
10
|
+
console.warn(" Then uninstall the old package: npm uninstall -g pkm-mcp-server");
|
|
11
|
+
console.warn("");
|
|
12
|
+
|
|
5
13
|
const subcommand = process.argv[2];
|
|
6
14
|
|
|
7
15
|
try {
|
|
@@ -11,13 +19,13 @@ try {
|
|
|
11
19
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
12
20
|
const require = createRequire(import.meta.url);
|
|
13
21
|
const { version } = require("./package.json");
|
|
14
|
-
console.log(`pkm
|
|
22
|
+
console.log(`obsidian-pkm v${version}`);
|
|
15
23
|
} else if (!subcommand) {
|
|
16
24
|
const { startServer } = await import("./index.js");
|
|
17
25
|
await startServer();
|
|
18
26
|
} else {
|
|
19
27
|
console.error(`Unknown command: ${subcommand}`);
|
|
20
|
-
console.error("Usage: pkm
|
|
28
|
+
console.error("Usage: obsidian-pkm [init]");
|
|
21
29
|
process.exit(1);
|
|
22
30
|
}
|
|
23
31
|
} catch (e) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup
|
|
3
|
+
description: Configure Obsidian PKM plugin — set vault path, API keys, and verify setup
|
|
4
|
+
allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Obsidian PKM Setup
|
|
8
|
+
|
|
9
|
+
You are configuring the Obsidian PKM plugin. Walk the user through these steps:
|
|
10
|
+
|
|
11
|
+
## Step 1: Vault Path
|
|
12
|
+
|
|
13
|
+
Ask the user for their Obsidian vault path. Validate that:
|
|
14
|
+
- The path exists and is a directory
|
|
15
|
+
- It contains at least one `.md` file
|
|
16
|
+
- Suggest `~/Documents/PKM` as a default
|
|
17
|
+
|
|
18
|
+
If the path is valid, instruct the user to add `export VAULT_PATH="/absolute/path/to/vault"` to their shell profile (`~/.zshrc` or `~/.bashrc`) if not already set. Verify with `echo $VAULT_PATH`.
|
|
19
|
+
|
|
20
|
+
## Step 2: OpenAI API Key (Optional)
|
|
21
|
+
|
|
22
|
+
Ask if they want semantic search features (vault_semantic_search, vault_suggest_links). If yes:
|
|
23
|
+
- Ask for their OpenAI API key
|
|
24
|
+
- Instruct them to add `export OPENAI_API_KEY="sk-..."` to their shell profile
|
|
25
|
+
- Explain this enables 2 additional tools but is completely optional
|
|
26
|
+
|
|
27
|
+
## Step 3: Verify Setup
|
|
28
|
+
|
|
29
|
+
Run these checks:
|
|
30
|
+
1. `echo $VAULT_PATH` — confirm it's set
|
|
31
|
+
2. Count `.md` files in the vault: `find "$VAULT_PATH" -name "*.md" | wc -l`
|
|
32
|
+
3. Check if templates exist: `ls "$VAULT_PATH/05-Templates/"` — if missing, offer to run `obsidian-pkm init` to scaffold the vault structure
|
|
33
|
+
|
|
34
|
+
## Step 4: Migration Check
|
|
35
|
+
|
|
36
|
+
Check if the old `pkm-mcp-server` is installed:
|
|
37
|
+
1. Run `npm list -g pkm-mcp-server --depth=0 2>/dev/null`
|
|
38
|
+
2. Check for stale hooks: `ls ~/.claude/hooks/pkm/ 2>/dev/null`
|
|
39
|
+
|
|
40
|
+
If found:
|
|
41
|
+
- Suggest: `npm uninstall -g pkm-mcp-server`
|
|
42
|
+
- Suggest removing `~/.claude/hooks/pkm/` directory (hooks are now managed by the plugin)
|
|
43
|
+
- Check `~/.claude/settings.json` for old hook entries and offer to clean them up
|
|
44
|
+
|
|
45
|
+
## Step 5: Done
|
|
46
|
+
|
|
47
|
+
Confirm setup is complete. Tell the user:
|
|
48
|
+
- "Your Obsidian PKM plugin is configured. Try asking me to list your vault folders to verify."
|
|
49
|
+
- If OPENAI_API_KEY was set: "Semantic search will build its index in the background on first use."
|
|
50
|
+
- **Important**: "If you just added environment variables to your shell profile, you'll need to restart your Claude Code session (or run `source ~/.zshrc`) for the MCP server and hooks to pick them up."
|
package/handlers.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
FORCE_HARD_CAP,
|
|
24
24
|
CHUNK_SIZE,
|
|
25
25
|
} from "./helpers.js";
|
|
26
|
-
import { exploreNeighborhood, formatNeighborhood, findFilesLinkingTo, rewriteWikilinks } from "./graph.js";
|
|
26
|
+
import { exploreNeighborhood, formatNeighborhood, findFilesLinkingTo, rewriteWikilinks, extractWikilinks } from "./graph.js";
|
|
27
27
|
import { getAllMarkdownFiles, extractFrontmatter } from "./utils.js";
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -63,6 +63,26 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
63
63
|
return resolvePath(resolvedFolder);
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
const SESSION_ID_DISPLAY_LEN = 8;
|
|
67
|
+
|
|
68
|
+
function addToBasenameMap(relativePath) {
|
|
69
|
+
const bn = path.basename(relativePath, ".md").toLowerCase();
|
|
70
|
+
if (!basenameMap.has(bn)) basenameMap.set(bn, []);
|
|
71
|
+
basenameMap.get(bn).push(relativePath);
|
|
72
|
+
allFilesSet.add(relativePath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function removeFromBasenameMap(relativePath) {
|
|
76
|
+
allFilesSet.delete(relativePath);
|
|
77
|
+
const bn = path.basename(relativePath, ".md").toLowerCase();
|
|
78
|
+
const entries = basenameMap.get(bn);
|
|
79
|
+
if (entries) {
|
|
80
|
+
const idx = entries.indexOf(relativePath);
|
|
81
|
+
if (idx !== -1) entries.splice(idx, 1);
|
|
82
|
+
if (entries.length === 0) basenameMap.delete(bn);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
async function handleRead(args) {
|
|
67
87
|
const filePath = resolveFile(args.path);
|
|
68
88
|
const content = await fs.readFile(filePath, "utf-8");
|
|
@@ -193,12 +213,7 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
193
213
|
}
|
|
194
214
|
|
|
195
215
|
// Update basename map with the new file
|
|
196
|
-
|
|
197
|
-
if (!basenameMap.has(newBasename)) {
|
|
198
|
-
basenameMap.set(newBasename, []);
|
|
199
|
-
}
|
|
200
|
-
basenameMap.get(newBasename).push(outputPath);
|
|
201
|
-
allFilesSet.add(outputPath);
|
|
216
|
+
addToBasenameMap(outputPath);
|
|
202
217
|
|
|
203
218
|
const fm = validation.frontmatter;
|
|
204
219
|
const createdStr = fm.created instanceof Date
|
|
@@ -403,11 +418,7 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
403
418
|
const result = { outgoing: [], incoming: [] };
|
|
404
419
|
|
|
405
420
|
if (args.direction !== "incoming") {
|
|
406
|
-
|
|
407
|
-
let match;
|
|
408
|
-
while ((match = linkRegex.exec(content)) !== null) {
|
|
409
|
-
result.outgoing.push(match[1]);
|
|
410
|
-
}
|
|
421
|
+
result.outgoing = extractWikilinks(content);
|
|
411
422
|
}
|
|
412
423
|
|
|
413
424
|
if (args.direction !== "outgoing") {
|
|
@@ -580,20 +591,20 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
580
591
|
|
|
581
592
|
if (entries.length === 0) {
|
|
582
593
|
return {
|
|
583
|
-
content: [{ type: "text", text: `No activity entries found. (current session: ${sessionId.slice(0,
|
|
594
|
+
content: [{ type: "text", text: `No activity entries found. (current session: ${sessionId.slice(0, SESSION_ID_DISPLAY_LEN)})` }]
|
|
584
595
|
};
|
|
585
596
|
}
|
|
586
597
|
|
|
587
598
|
const formatted = entries.map(e => {
|
|
588
599
|
const ts = e.timestamp.replace("T", " ").slice(0, 19);
|
|
589
|
-
const sessionShort = e.session_id.slice(0,
|
|
600
|
+
const sessionShort = e.session_id.slice(0, SESSION_ID_DISPLAY_LEN);
|
|
590
601
|
return `[${ts}] [${sessionShort}] ${e.tool_name}\n${e.args_json}`;
|
|
591
602
|
}).join("\n\n");
|
|
592
603
|
|
|
593
604
|
return {
|
|
594
605
|
content: [{
|
|
595
606
|
type: "text",
|
|
596
|
-
text: `Activity log (${entries.length} entr${entries.length === 1 ? "y" : "ies"}, current session: ${sessionId.slice(0,
|
|
607
|
+
text: `Activity log (${entries.length} entr${entries.length === 1 ? "y" : "ies"}, current session: ${sessionId.slice(0, SESSION_ID_DISPLAY_LEN)}):\n\n${formatted}`
|
|
597
608
|
}]
|
|
598
609
|
};
|
|
599
610
|
}
|
|
@@ -651,13 +662,9 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
651
662
|
}
|
|
652
663
|
if (!body) throw new Error("No content to analyze");
|
|
653
664
|
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
while ((match = linkRegex.exec(inputText)) !== null) {
|
|
658
|
-
const target = match[1];
|
|
659
|
-
linkedNames.add(path.basename(target, ".md").toLowerCase());
|
|
660
|
-
}
|
|
665
|
+
const linkedNames = new Set(
|
|
666
|
+
extractWikilinks(inputText).map(t => path.basename(t, ".md").toLowerCase())
|
|
667
|
+
);
|
|
661
668
|
|
|
662
669
|
const excludeFiles = new Set();
|
|
663
670
|
if (sourcePath) excludeFiles.add(sourcePath);
|
|
@@ -696,7 +703,12 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
696
703
|
const filePath = resolvePath(resolvedRelative);
|
|
697
704
|
|
|
698
705
|
// Verify file exists
|
|
699
|
-
|
|
706
|
+
try {
|
|
707
|
+
await fs.access(filePath);
|
|
708
|
+
} catch (e) {
|
|
709
|
+
if (e.code === "ENOENT") throw new Error(`ENOENT: File not found: ${resolvedRelative}`, { cause: e });
|
|
710
|
+
throw e;
|
|
711
|
+
}
|
|
700
712
|
|
|
701
713
|
// Find incoming links for warning output
|
|
702
714
|
const allFilesList = Array.from(allFilesSet);
|
|
@@ -725,14 +737,7 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
725
737
|
await fs.rename(filePath, trashAbsolute);
|
|
726
738
|
|
|
727
739
|
// Update in-memory basename map
|
|
728
|
-
|
|
729
|
-
const oldBasename = path.basename(resolvedRelative, ".md").toLowerCase();
|
|
730
|
-
const entries = basenameMap.get(oldBasename);
|
|
731
|
-
if (entries) {
|
|
732
|
-
const idx = entries.indexOf(resolvedRelative);
|
|
733
|
-
if (idx !== -1) entries.splice(idx, 1);
|
|
734
|
-
if (entries.length === 0) basenameMap.delete(oldBasename);
|
|
735
|
-
}
|
|
740
|
+
removeFromBasenameMap(resolvedRelative);
|
|
736
741
|
|
|
737
742
|
// Build output
|
|
738
743
|
let text = `Trashed ${resolvedRelative} → ${trashRelative}`;
|
|
@@ -754,7 +759,12 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
754
759
|
const newAbsolute = resolvePath(newRelative);
|
|
755
760
|
|
|
756
761
|
// Verify source exists
|
|
757
|
-
|
|
762
|
+
try {
|
|
763
|
+
await fs.access(oldAbsolute);
|
|
764
|
+
} catch (e) {
|
|
765
|
+
if (e.code === "ENOENT") throw new Error(`ENOENT: File not found: ${oldRelative}`, { cause: e });
|
|
766
|
+
throw e;
|
|
767
|
+
}
|
|
758
768
|
|
|
759
769
|
// Verify destination does NOT exist
|
|
760
770
|
try {
|
|
@@ -775,23 +785,11 @@ export async function createHandlers({ vaultPath, templateRegistry, semanticInde
|
|
|
775
785
|
await fs.rename(oldAbsolute, newAbsolute);
|
|
776
786
|
|
|
777
787
|
// Update basename map: remove old, add new
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const oldEntries = basenameMap.get(oldBasename);
|
|
781
|
-
if (oldEntries) {
|
|
782
|
-
const idx = oldEntries.indexOf(oldRelative);
|
|
783
|
-
if (idx !== -1) oldEntries.splice(idx, 1);
|
|
784
|
-
if (oldEntries.length === 0) basenameMap.delete(oldBasename);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
allFilesSet.add(newRelative);
|
|
788
|
-
const newBasename = path.basename(newRelative, ".md").toLowerCase();
|
|
789
|
-
if (!basenameMap.has(newBasename)) {
|
|
790
|
-
basenameMap.set(newBasename, []);
|
|
791
|
-
}
|
|
792
|
-
basenameMap.get(newBasename).push(newRelative);
|
|
788
|
+
removeFromBasenameMap(oldRelative);
|
|
789
|
+
addToBasenameMap(newRelative);
|
|
793
790
|
|
|
794
791
|
// Determine new link target — use full path if basename is now ambiguous
|
|
792
|
+
const newBasename = path.basename(newRelative, ".md").toLowerCase();
|
|
795
793
|
const newEntries = basenameMap.get(newBasename);
|
|
796
794
|
const isAmbiguous = newEntries && newEntries.length > 1;
|
|
797
795
|
const newLinkTarget = isAmbiguous
|
package/hooks/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
39
39
|
"hooks": [
|
|
40
40
|
{
|
|
41
41
|
"type": "command",
|
|
42
|
-
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/
|
|
42
|
+
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/obsidian-pkm-plugin/hooks/session-start.js",
|
|
43
43
|
"timeout": 15,
|
|
44
44
|
"statusMessage": "Loading PKM project context..."
|
|
45
45
|
}
|
|
@@ -51,7 +51,7 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
51
51
|
"hooks": [
|
|
52
52
|
{
|
|
53
53
|
"type": "command",
|
|
54
|
-
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/
|
|
54
|
+
"command": "VAULT_PATH=\"/path/to/your/vault\" node /path/to/obsidian-pkm-plugin/hooks/stop-sweep.js",
|
|
55
55
|
"async": true,
|
|
56
56
|
"timeout": 10
|
|
57
57
|
}
|
|
@@ -64,7 +64,7 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
64
64
|
"hooks": [
|
|
65
65
|
{
|
|
66
66
|
"type": "command",
|
|
67
|
-
"command": "VAULT_PATH=\"/path/to/your/vault\" /path/to/
|
|
67
|
+
"command": "VAULT_PATH=\"/path/to/your/vault\" /path/to/obsidian-pkm-plugin/hooks/capture-handler.sh",
|
|
68
68
|
"async": true,
|
|
69
69
|
"timeout": 10
|
|
70
70
|
}
|
|
@@ -75,7 +75,7 @@ Add the following to your `~/.claude/settings.json`:
|
|
|
75
75
|
}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Replace `/path/to/your/vault` with the absolute path to your Obsidian vault (e.g., `~/Documents/PKM`), and `/path/to/
|
|
78
|
+
Replace `/path/to/your/vault` with the absolute path to your Obsidian vault (e.g., `~/Documents/PKM`), and `/path/to/obsidian-pkm-plugin` with the absolute path to this repository.
|
|
79
79
|
|
|
80
80
|
## Architecture Notes
|
|
81
81
|
|
package/hooks/capture-handler.sh
CHANGED
|
@@ -15,19 +15,23 @@ trap cleanup EXIT
|
|
|
15
15
|
# Read hook input from stdin
|
|
16
16
|
INPUT=$(cat)
|
|
17
17
|
|
|
18
|
-
# Extract tool_input fields (
|
|
19
|
-
|
|
20
|
-
let b='';
|
|
21
|
-
process.stdin.on('
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
})
|
|
30
|
-
")
|
|
18
|
+
# Extract tool_input fields safely (no eval — stdout capture only)
|
|
19
|
+
TOOL_INPUT=$(echo "$INPUT" | node -e "
|
|
20
|
+
let b=''; process.stdin.on('data',c=>b+=c);
|
|
21
|
+
process.stdin.on('end',()=>{ process.stdout.write(JSON.stringify(JSON.parse(b).tool_input||{})); })
|
|
22
|
+
")
|
|
23
|
+
CAPTURE_TYPE=$(echo "$INPUT" | node -e "
|
|
24
|
+
let b=''; process.stdin.on('data',c=>b+=c);
|
|
25
|
+
process.stdin.on('end',()=>{ process.stdout.write((JSON.parse(b).tool_input||{}).type||''); })
|
|
26
|
+
")
|
|
27
|
+
CAPTURE_TITLE=$(echo "$INPUT" | node -e "
|
|
28
|
+
let b=''; process.stdin.on('data',c=>b+=c);
|
|
29
|
+
process.stdin.on('end',()=>{ process.stdout.write((JSON.parse(b).tool_input||{}).title||''); })
|
|
30
|
+
")
|
|
31
|
+
CAPTURE_CONTENT=$(echo "$INPUT" | node -e "
|
|
32
|
+
let b=''; process.stdin.on('data',c=>b+=c);
|
|
33
|
+
process.stdin.on('end',()=>{ process.stdout.write((JSON.parse(b).tool_input||{}).content||''); })
|
|
34
|
+
")
|
|
31
35
|
|
|
32
36
|
# Skip if missing required fields
|
|
33
37
|
if [ -z "$CAPTURE_TYPE" ] || [ -z "$CAPTURE_TITLE" ] || [ -z "$CAPTURE_CONTENT" ]; then
|
|
@@ -46,7 +50,7 @@ MCP_CONFIG=$(node -e "
|
|
|
46
50
|
if (process.env.OPENAI_API_KEY) env.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
|
47
51
|
const server = useLocal
|
|
48
52
|
? { command: 'node', args: [localIndex], env }
|
|
49
|
-
: { command: 'npx', args: ['-y', 'pkm
|
|
53
|
+
: { command: 'npx', args: ['-y', 'obsidian-pkm@latest'], env };
|
|
50
54
|
console.log(JSON.stringify({ mcpServers: { 'obsidian-pkm': server } }));
|
|
51
55
|
" "$SCRIPT_DIR" "${VAULT_PATH:-$HOME/Documents/PKM}")
|
|
52
56
|
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|clear|compact",
|
|
6
|
+
"hooks": [{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "VAULT_PATH=${VAULT_PATH} node ${CLAUDE_PLUGIN_ROOT}/hooks/session-start.js",
|
|
9
|
+
"timeout": 15
|
|
10
|
+
}]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "mcp__obsidian-pkm__vault_capture",
|
|
16
|
+
"hooks": [{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "VAULT_PATH=${VAULT_PATH} bash ${CLAUDE_PLUGIN_ROOT}/hooks/capture-handler.sh",
|
|
19
|
+
"timeout": 30,
|
|
20
|
+
"async": true
|
|
21
|
+
}]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"Stop": [
|
|
25
|
+
{
|
|
26
|
+
"hooks": [{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "VAULT_PATH=${VAULT_PATH} node ${CLAUDE_PLUGIN_ROOT}/hooks/stop-sweep.js",
|
|
29
|
+
"timeout": 15,
|
|
30
|
+
"async": true
|
|
31
|
+
}]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
package/hooks/stop-sweep.js
CHANGED
|
@@ -61,7 +61,11 @@ async function main() {
|
|
|
61
61
|
|
|
62
62
|
// Resolve project — no project, no captures
|
|
63
63
|
const { projectPath, error } = await resolveProject(cwd, VAULT_PATH);
|
|
64
|
-
if (error
|
|
64
|
+
if (error) {
|
|
65
|
+
logError(`project resolution failed: ${error}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!projectPath) return;
|
|
65
69
|
|
|
66
70
|
// Build MCP config — auto-detect repo (../index.js exists) vs installed (use npx)
|
|
67
71
|
const localIndex = join(__dirname, "..", "index.js");
|
|
@@ -74,7 +78,7 @@ async function main() {
|
|
|
74
78
|
mcpServers: {
|
|
75
79
|
"obsidian-pkm": useLocal
|
|
76
80
|
? { command: "node", args: [localIndex], env: mcpEnv }
|
|
77
|
-
: { command: "npx", args: ["-y", "pkm
|
|
81
|
+
: { command: "npx", args: ["-y", "obsidian-pkm@latest"], env: mcpEnv },
|
|
78
82
|
},
|
|
79
83
|
});
|
|
80
84
|
|
package/index.js
CHANGED
|
@@ -38,7 +38,7 @@ export async function startServer() {
|
|
|
38
38
|
|
|
39
39
|
// Create the server
|
|
40
40
|
const server = new Server(
|
|
41
|
-
{ name: "pkm
|
|
41
|
+
{ name: "obsidian-pkm", version: PKG_VERSION },
|
|
42
42
|
{ capabilities: { tools: {} } }
|
|
43
43
|
);
|
|
44
44
|
|
|
@@ -73,7 +73,7 @@ export async function startServer() {
|
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
name: "vault_peek",
|
|
76
|
-
description: "Inspect a file's metadata and structure without reading full content. Returns file size, frontmatter, heading outline with
|
|
76
|
+
description: "Inspect a file's metadata and structure without reading full content. Returns file size, frontmatter, heading outline with approximate section sizes, and a brief preview. Use this to plan which sections to read from large files.",
|
|
77
77
|
inputSchema: {
|
|
78
78
|
type: "object",
|
|
79
79
|
properties: {
|
package/init.js
CHANGED
|
@@ -192,7 +192,7 @@ export async function dirSize(dirPath) {
|
|
|
192
192
|
export function detectInstallType(filePath) {
|
|
193
193
|
const thisFile = filePath || fileURLToPath(import.meta.url);
|
|
194
194
|
if (thisFile.includes("node_modules")) {
|
|
195
|
-
return { command: "npx", args: ["-y", "pkm
|
|
195
|
+
return { command: "npx", args: ["-y", "obsidian-pkm@latest"] };
|
|
196
196
|
}
|
|
197
197
|
const cliPath = path.join(path.dirname(thisFile), "cli.js");
|
|
198
198
|
return { command: "node", args: [cliPath] };
|
|
@@ -394,7 +394,7 @@ export async function runInit() {
|
|
|
394
394
|
try {
|
|
395
395
|
// ── Step 1: Welcome ──
|
|
396
396
|
console.log(`
|
|
397
|
-
pkm
|
|
397
|
+
obsidian-pkm setup wizard
|
|
398
398
|
|
|
399
399
|
This will walk you through setting up your Obsidian vault for use with the
|
|
400
400
|
PKM MCP server. You'll be asked about 6 things:
|
|
@@ -508,7 +508,7 @@ Nothing is written until you confirm each step. Press Ctrl+C at any time to canc
|
|
|
508
508
|
|
|
509
509
|
let skipRegistration = false;
|
|
510
510
|
if (hasExisting) {
|
|
511
|
-
const overwrite = await confirmPrompt({ message: "Claude Code is already configured for pkm
|
|
511
|
+
const overwrite = await confirmPrompt({ message: "Claude Code is already configured for obsidian-pkm. Overwrite?", default: false });
|
|
512
512
|
if (!overwrite) {
|
|
513
513
|
console.log(" Registration skipped.\n");
|
|
514
514
|
skipRegistration = true;
|
|
@@ -548,7 +548,7 @@ Nothing is written until you confirm each step. Press Ctrl+C at any time to canc
|
|
|
548
548
|
steps.push("MCP server: skipped (registration failed)");
|
|
549
549
|
}
|
|
550
550
|
} else {
|
|
551
|
-
console.log(" Registration: skipped (you can run `pkm
|
|
551
|
+
console.log(" Registration: skipped (you can run `obsidian-pkm init` again later)");
|
|
552
552
|
steps.push("MCP server: skipped");
|
|
553
553
|
}
|
|
554
554
|
} else {
|
|
@@ -678,7 +678,7 @@ To verify, restart Claude Code and try:
|
|
|
678
678
|
"List the folders in my vault"
|
|
679
679
|
|
|
680
680
|
Claude should call vault_list and show your vault's directory structure.
|
|
681
|
-
If that doesn't work, check: https://github.com/AdrianV101/
|
|
681
|
+
If that doesn't work, check: https://github.com/AdrianV101/obsidian-pkm-plugin#troubleshooting
|
|
682
682
|
`);
|
|
683
683
|
} catch (e) {
|
|
684
684
|
if (e.name === "ExitPromptError") {
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pkm-mcp-server",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Claude Code plugin for Obsidian vault integration — 19 MCP tools, hooks, and skills for PKM",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./cli.js"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"pkm
|
|
10
|
+
"obsidian-pkm": "cli.js"
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
13
13
|
"engines": {
|
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
"CHANGELOG.md",
|
|
20
20
|
"hooks/",
|
|
21
21
|
"templates/",
|
|
22
|
-
"sample-project/"
|
|
22
|
+
"sample-project/",
|
|
23
|
+
".claude-plugin/",
|
|
24
|
+
"commands/",
|
|
25
|
+
"skills/",
|
|
26
|
+
".mcp.json"
|
|
23
27
|
],
|
|
24
28
|
"scripts": {
|
|
25
29
|
"start": "node cli.js",
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
},
|
|
30
34
|
"repository": {
|
|
31
35
|
"type": "git",
|
|
32
|
-
"url": "git+https://github.com/AdrianV101/
|
|
36
|
+
"url": "git+https://github.com/AdrianV101/obsidian-pkm-plugin.git"
|
|
33
37
|
},
|
|
34
38
|
"author": "Adrian Verhoosel",
|
|
35
39
|
"license": "MIT",
|
|
@@ -45,15 +49,17 @@
|
|
|
45
49
|
"markdown",
|
|
46
50
|
"notes",
|
|
47
51
|
"semantic-search",
|
|
48
|
-
"wikilinks"
|
|
52
|
+
"wikilinks",
|
|
53
|
+
"plugin",
|
|
54
|
+
"hooks"
|
|
49
55
|
],
|
|
50
|
-
"homepage": "https://github.com/AdrianV101/
|
|
56
|
+
"homepage": "https://github.com/AdrianV101/obsidian-pkm-plugin#readme",
|
|
51
57
|
"bugs": {
|
|
52
|
-
"url": "https://github.com/AdrianV101/
|
|
58
|
+
"url": "https://github.com/AdrianV101/obsidian-pkm-plugin/issues"
|
|
53
59
|
},
|
|
54
60
|
"dependencies": {
|
|
55
61
|
"@inquirer/prompts": "^8.3.2",
|
|
56
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
57
63
|
"better-sqlite3": "^12.6.2",
|
|
58
64
|
"js-yaml": "^4.1.0",
|
|
59
65
|
"sqlite-vec": "^0.1.7"
|
package/sample-project/CLAUDE.md
CHANGED
|
@@ -185,6 +185,7 @@ Use these with `vault_write({ template: "name", path: "...", frontmatter: { tags
|
|
|
185
185
|
| `moc` | Maps of Content (index/hub notes) |
|
|
186
186
|
| `daily-note` | Daily notes |
|
|
187
187
|
| `task` | Structured task notes with status, priority, due date |
|
|
188
|
+
| `note` | Minimal generic notes |
|
|
188
189
|
|
|
189
190
|
## Session End
|
|
190
191
|
|
package/skills/.gitkeep
ADDED
|
File without changes
|