pkm-mcp-server 1.0.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/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/activity.js +147 -0
- package/embeddings.js +672 -0
- package/graph.js +340 -0
- package/handlers.js +871 -0
- package/helpers.js +855 -0
- package/index.js +498 -0
- package/package.json +63 -0
- package/sample-project/CLAUDE.md +193 -0
- package/templates/adr.md +52 -0
- package/templates/daily-note.md +19 -0
- package/templates/devlog.md +35 -0
- package/templates/fleeting-note.md +11 -0
- package/templates/literature-note.md +25 -0
- package/templates/meeting-notes.md +28 -0
- package/templates/moc.md +22 -0
- package/templates/permanent-note.md +26 -0
- package/templates/project-index.md +38 -0
- package/templates/research-note.md +35 -0
- package/templates/task.md +22 -0
- package/templates/troubleshooting-log.md +32 -0
- package/utils.js +31 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2026-02-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- MCP server with 18 tools for Obsidian vault interaction
|
|
13
|
+
- `vault_read` with pagination support (heading-based, tail lines, tail sections, chunk, line range); auto-redirects large files (>80k chars) to peek data; `force` param to bypass redirect (hard-capped at ~400k chars)
|
|
14
|
+
- `vault_peek` for inspecting file metadata and structure without reading full content (size, frontmatter, heading outline with line numbers, preview)
|
|
15
|
+
- `vault_write` with template-based note creation enforcing YAML frontmatter
|
|
16
|
+
- `vault_append` with positional insert (after heading, before heading, end of section)
|
|
17
|
+
- `vault_edit` for surgical single-occurrence string replacement
|
|
18
|
+
- `vault_update_frontmatter` — safe, atomic YAML frontmatter field updates (set, create, delete fields with protected required fields)
|
|
19
|
+
- `vault_search` for full-text search across markdown files
|
|
20
|
+
- `vault_semantic_search` using OpenAI `text-embedding-3-large` embeddings with SQLite-vec storage
|
|
21
|
+
- `vault_suggest_links` for smart link discovery based on content similarity
|
|
22
|
+
- `vault_list` and `vault_recent` for directory listing and recently modified files
|
|
23
|
+
- `vault_links` for wikilink analysis (`[[...]]` syntax)
|
|
24
|
+
- `vault_neighborhood` for graph context exploration via BFS wikilink traversal
|
|
25
|
+
- `vault_query` for querying notes by YAML frontmatter metadata (type, status, tags, dates, custom fields, sorting)
|
|
26
|
+
- `vault_tags` for tag discovery with per-note counts, folder scoping, and glob patterns
|
|
27
|
+
- `vault_activity` for cross-session memory via activity logging with session IDs
|
|
28
|
+
- `vault_trash` — soft-delete to `.trash/` (Obsidian convention) with broken incoming link warnings
|
|
29
|
+
- `vault_move` — move/rename files with automatic wikilink updating across vault
|
|
30
|
+
- Fuzzy path resolution for read-only tools (resolve by basename, `.md` extension optional)
|
|
31
|
+
- Fuzzy folder resolution for search/query/tags/recent tools (partial name matching)
|
|
32
|
+
- 12 Obsidian note templates (project index, ADR, devlog, permanent note, research note, troubleshooting log, fleeting note, literature note, meeting notes, map of content, daily note, task)
|
|
33
|
+
- Sample `CLAUDE.md` for integrating PKM workflows into code repositories
|
|
34
|
+
- Background `fs.watch` indexer for keeping semantic search index fresh
|
|
35
|
+
- Metadata schema documentation (`06-System/metadata-schema.md`)
|
|
36
|
+
- GitHub Actions CI workflow
|
|
37
|
+
- Comprehensive test suite (417 tests) for helpers, handlers, graph module, and activity log
|
|
38
|
+
- ESLint configuration for code quality
|
|
39
|
+
- EditorConfig for consistent formatting
|
|
40
|
+
- Community files: CONTRIBUTING.md, CODE_OF_CONDUCT.md, LICENSE (MIT)
|
|
41
|
+
|
|
42
|
+
### Security
|
|
43
|
+
- Path traversal prevention on all vault operations
|
|
44
|
+
- Write tools require exact paths to prevent accidental modifications
|
|
45
|
+
- Ambiguous fuzzy path matches return errors instead of guessing
|
|
46
|
+
- Prototype pollution protection on frontmatter key validation (`__proto__`, `constructor`, `prototype` blocked)
|
|
47
|
+
- ReDoS vulnerability prevention in `vault_list` glob pattern — linear-time glob matching
|
|
48
|
+
- Atomic file creation in `vault_write` (`wx` flag) prevents race conditions
|
|
49
|
+
- Error messages sanitized to prevent leaking absolute vault paths
|
|
50
|
+
|
|
51
|
+
[Unreleased]: https://github.com/AdrianV101/Obsidian-MCP/compare/v1.0.0...HEAD
|
|
52
|
+
[1.0.0]: https://github.com/AdrianV101/Obsidian-MCP/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adrian Verhoosel Azpiroz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Obsidian PKM MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/pkm-mcp-server)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://github.com/AdrianV101/Obsidian-MCP/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
An MCP (Model Context Protocol) server that gives Claude Code full read/write access to your Obsidian vault. 18 tools for note CRUD, full-text search, semantic search, graph traversal, metadata queries, and session activity tracking. Published on npm as [`pkm-mcp-server`](https://www.npmjs.com/package/pkm-mcp-server).
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Claude Code is powerful for writing code, but it forgets everything between sessions. This server turns your Obsidian vault into persistent, structured memory that Claude can read and write natively.
|
|
13
|
+
|
|
14
|
+
- **Session continuity** - Claude logs what it did and can pick up where it left off
|
|
15
|
+
- **Structured knowledge** - ADRs, research notes, devlogs created from enforced templates, not freeform text dumps
|
|
16
|
+
- **Semantic recall** - "find my notes about caching strategies" works even if you never used the word "caching"
|
|
17
|
+
- **Graph context** - Claude can explore related notes by following wikilinks, not just keyword matches
|
|
18
|
+
|
|
19
|
+
Without this, every Claude Code session starts from scratch. With it, your AI assistant has a working memory that compounds over time.
|
|
20
|
+
|
|
21
|
+
https://github.com/user-attachments/assets/58ad9c9b-d987-4728-89e7-33de20b73a38
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
| Tool | Description |
|
|
26
|
+
|------|-------------|
|
|
27
|
+
| `vault_read` | Read note contents (pagination by heading, tail, chunk, line range; auto-redirects large files to peek data) |
|
|
28
|
+
| `vault_peek` | Inspect file metadata and structure without reading full content |
|
|
29
|
+
| `vault_write` | Create notes from templates (enforces frontmatter) |
|
|
30
|
+
| `vault_append` | Append to notes, with positional insert (after/before heading, end of section) |
|
|
31
|
+
| `vault_edit` | Surgical string replacement |
|
|
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, 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) |
|
|
45
|
+
|
|
46
|
+
### Fuzzy Path Resolution
|
|
47
|
+
|
|
48
|
+
Read-only tools accept short names that resolve to full vault paths:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
vault_read({ path: "devlog" })
|
|
52
|
+
// Resolves to: 01-Projects/Obsidian-MCP/development/devlog.md
|
|
53
|
+
|
|
54
|
+
vault_read({ path: "devlog.md" })
|
|
55
|
+
// Same result — .md extension is optional
|
|
56
|
+
|
|
57
|
+
vault_links({ path: "alpha" })
|
|
58
|
+
// Works on vault_links, vault_neighborhood, vault_suggest_links too
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Folder-scoped tools accept partial folder names:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
vault_search({ query: "API design", folder: "Obsidian-MCP" })
|
|
65
|
+
// Resolves folder to: 01-Projects/Obsidian-MCP
|
|
66
|
+
|
|
67
|
+
vault_tags({ folder: "Obsidian-MCP" })
|
|
68
|
+
// Works on vault_search, vault_query, vault_tags, vault_recent
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Ambiguous matches return an error listing candidates. Exact paths always work unchanged.
|
|
72
|
+
|
|
73
|
+
## Prerequisites
|
|
74
|
+
|
|
75
|
+
- **Node.js >= 20** (Node 18 is EOL; uses native `fetch` and ES modules)
|
|
76
|
+
- **An MCP-compatible client** such as [Claude Code](https://claude.ai/code)
|
|
77
|
+
- **C++ build tools** for `better-sqlite3` native addon:
|
|
78
|
+
- **macOS**: `xcode-select --install`
|
|
79
|
+
- **Linux**: `sudo apt install build-essential python3` (Debian/Ubuntu) or equivalent
|
|
80
|
+
- **Windows**: Install [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) with the "Desktop development with C++" workload
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
### 1. Install
|
|
85
|
+
|
|
86
|
+
**From npm** (recommended):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g pkm-mcp-server
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**From source:**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone https://github.com/AdrianV101/Obsidian-MCP.git
|
|
96
|
+
cd Obsidian-MCP
|
|
97
|
+
npm install
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2. Register with Claude Code
|
|
101
|
+
|
|
102
|
+
Add to `~/.claude/settings.json`:
|
|
103
|
+
|
|
104
|
+
**If installed from npm:**
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"obsidian-pkm": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "pkm-mcp-server"],
|
|
112
|
+
"env": {
|
|
113
|
+
"VAULT_PATH": "/absolute/path/to/your/obsidian/vault"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**If installed from source:**
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcpServers": {
|
|
125
|
+
"obsidian-pkm": {
|
|
126
|
+
"command": "node",
|
|
127
|
+
"args": ["/absolute/path/to/index.js"],
|
|
128
|
+
"env": {
|
|
129
|
+
"VAULT_PATH": "/absolute/path/to/your/obsidian/vault"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Restart Claude Code. The server provides all tools except semantic search out of the box.
|
|
137
|
+
|
|
138
|
+
### 3. Enable Semantic Search (optional)
|
|
139
|
+
|
|
140
|
+
Add your OpenAI API key to the env block:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
"env": {
|
|
144
|
+
"VAULT_PATH": "/absolute/path/to/your/obsidian/vault",
|
|
145
|
+
"OPENAI_API_KEY": "sk-..."
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
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.
|
|
150
|
+
|
|
151
|
+
## Vault Structure
|
|
152
|
+
|
|
153
|
+
The server works with any Obsidian vault. The included templates assume this layout:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Vault/
|
|
157
|
+
├── 00-Inbox/
|
|
158
|
+
├── 01-Projects/
|
|
159
|
+
│ └── ProjectName/
|
|
160
|
+
│ ├── _index.md
|
|
161
|
+
│ ├── planning/
|
|
162
|
+
│ ├── research/
|
|
163
|
+
│ └── development/decisions/
|
|
164
|
+
├── 02-Areas/
|
|
165
|
+
├── 03-Resources/
|
|
166
|
+
├── 04-Archive/
|
|
167
|
+
├── 05-Templates/ # Note templates loaded by vault_write
|
|
168
|
+
└── 06-System/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Templates
|
|
172
|
+
|
|
173
|
+
Copy the files from `templates/` into your vault's `05-Templates/` folder. `vault_write` loads all `.md` files from that directory at startup and enforces frontmatter on every note created.
|
|
174
|
+
|
|
175
|
+
Included templates: `project-index`, `adr`, `devlog`, `permanent-note`, `research-note`, `troubleshooting-log`, `fleeting-note`, `literature-note`, `meeting-notes`, `moc`, `daily-note`, `task`. Add your own templates to `05-Templates/` and they become available to `vault_write` automatically.
|
|
176
|
+
|
|
177
|
+
### CLAUDE.md for Your Projects
|
|
178
|
+
|
|
179
|
+
`sample-project/CLAUDE.md` is a template you can drop into any code repository to wire up Claude Code with your vault. It defines context loading, documentation rules, and ADR/devlog conventions.
|
|
180
|
+
|
|
181
|
+
## Architecture
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
graph LR
|
|
185
|
+
CC[Claude Code] -->|MCP protocol| IDX[index.js]
|
|
186
|
+
IDX --> HND[handlers.js]
|
|
187
|
+
HND --> H[helpers.js]
|
|
188
|
+
HND --> G[graph.js]
|
|
189
|
+
HND --> E[embeddings.js]
|
|
190
|
+
HND --> A[activity.js]
|
|
191
|
+
HND --> U[utils.js]
|
|
192
|
+
HND -->|read/write| V[(Obsidian Vault)]
|
|
193
|
+
E -->|embeddings API| OAI[OpenAI]
|
|
194
|
+
E -->|vector store| DB[(SQLite + sqlite-vec)]
|
|
195
|
+
A -->|activity log| DB2[(SQLite)]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
├── index.js # MCP server, tool definitions, request routing
|
|
200
|
+
├── handlers.js # Tool handler implementations
|
|
201
|
+
├── helpers.js # Pure functions (path security, filtering, templates)
|
|
202
|
+
├── graph.js # Wikilink resolution and BFS graph traversal
|
|
203
|
+
├── embeddings.js # Semantic index (OpenAI embeddings, SQLite + sqlite-vec)
|
|
204
|
+
├── activity.js # Activity log (session tracking, SQLite)
|
|
205
|
+
├── utils.js # Shared utilities (frontmatter parsing, file listing)
|
|
206
|
+
├── templates/ # Obsidian note templates
|
|
207
|
+
└── sample-project/ # Sample CLAUDE.md for your repos
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
All paths passed to tools are relative to vault root. The server includes path security to prevent directory traversal.
|
|
211
|
+
|
|
212
|
+
## How It Works
|
|
213
|
+
|
|
214
|
+
**Note 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`).
|
|
215
|
+
|
|
216
|
+
**Semantic search** embeds notes on startup and watches for changes via `fs.watch`. Long notes are chunked by `##` headings. The index is a regenerable cache stored in `.obsidian/` so it syncs across machines via Obsidian Sync. The initial sync runs in the background — search is available immediately but may return incomplete results until sync finishes (a progress message is shown).
|
|
217
|
+
|
|
218
|
+
**Graph exploration** resolves `[[wikilinks]]` to file paths (handling aliases, headings, and ambiguous basenames), then does BFS traversal to return notes grouped by hop distance.
|
|
219
|
+
|
|
220
|
+
**Activity logging** records every tool call with timestamps and session IDs, enabling Claude to recall what happened in previous conversations.
|
|
221
|
+
|
|
222
|
+
## Troubleshooting
|
|
223
|
+
|
|
224
|
+
**`better-sqlite3` build fails during install**
|
|
225
|
+
You need C++ build tools. See [Prerequisites](#prerequisites) for your platform. On Linux, `sudo apt install build-essential python3` usually fixes it.
|
|
226
|
+
|
|
227
|
+
**Server starts but all tool calls fail with ENOENT**
|
|
228
|
+
Your `VAULT_PATH` is wrong or missing. The server now validates this at startup and exits with a clear error. Set it explicitly in your `settings.json` env block.
|
|
229
|
+
|
|
230
|
+
**`vault_write` says "no templates available"**
|
|
231
|
+
Copy the `templates/` files from this repo into your vault's `05-Templates/` directory. The server loads templates from there at startup.
|
|
232
|
+
|
|
233
|
+
**Semantic search not appearing in tool list**
|
|
234
|
+
Set `OPENAI_API_KEY` in your `settings.json` env block. Without it, `vault_semantic_search` and `vault_suggest_links` are hidden entirely.
|
|
235
|
+
|
|
236
|
+
**Semantic index not updating after file changes**
|
|
237
|
+
Check your Node version with `node -v`. The file watcher uses `fs.watch({ recursive: true })` which requires Node.js >= 18.13 on Linux.
|
|
238
|
+
|
|
239
|
+
## Contributing
|
|
240
|
+
|
|
241
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style guidelines, and the pull request process before submitting changes.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT
|
|
246
|
+
|
package/activity.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Persistent activity log backed by SQLite.
|
|
7
|
+
* Records every MCP tool call with timestamps and session IDs,
|
|
8
|
+
* enabling cross-session memory for Claude conversations.
|
|
9
|
+
*/
|
|
10
|
+
export class ActivityLog {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} opts
|
|
13
|
+
* @param {string} opts.vaultPath - absolute path to vault root
|
|
14
|
+
* @param {string} opts.sessionId - UUID for the current session
|
|
15
|
+
*/
|
|
16
|
+
constructor({ vaultPath, sessionId }) {
|
|
17
|
+
this.vaultPath = vaultPath;
|
|
18
|
+
this.sessionId = sessionId;
|
|
19
|
+
this.dbPath = path.join(vaultPath, ".obsidian", "activity-log.db");
|
|
20
|
+
this.db = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Create the SQLite database and activity table if they don't exist. */
|
|
24
|
+
async initialize() {
|
|
25
|
+
const dbDir = path.dirname(this.dbPath);
|
|
26
|
+
await fs.mkdir(dbDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
this.db = new Database(this.dbPath);
|
|
29
|
+
this.db.pragma("journal_mode = WAL");
|
|
30
|
+
this.db.pragma("journal_size_limit = 32000000");
|
|
31
|
+
|
|
32
|
+
this.db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS activity (
|
|
34
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
35
|
+
timestamp TEXT NOT NULL,
|
|
36
|
+
session_id TEXT NOT NULL,
|
|
37
|
+
tool_name TEXT NOT NULL,
|
|
38
|
+
args_json TEXT NOT NULL
|
|
39
|
+
);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_activity_session ON activity(session_id);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_activity_tool ON activity(tool_name);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_activity_timestamp ON activity(timestamp);
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Record a tool invocation.
|
|
48
|
+
* @param {string} toolName - MCP tool name
|
|
49
|
+
* @param {Object} [args] - tool arguments
|
|
50
|
+
*/
|
|
51
|
+
log(toolName, args) {
|
|
52
|
+
if (!this.db) return;
|
|
53
|
+
|
|
54
|
+
this.db.prepare(
|
|
55
|
+
"INSERT INTO activity (timestamp, session_id, tool_name, args_json) VALUES (?, ?, ?, ?)"
|
|
56
|
+
).run(
|
|
57
|
+
new Date().toISOString(),
|
|
58
|
+
this.sessionId,
|
|
59
|
+
toolName,
|
|
60
|
+
JSON.stringify(args || {})
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Query activity entries with optional filters.
|
|
66
|
+
* @param {Object} [opts]
|
|
67
|
+
* @param {number} [opts.limit=50]
|
|
68
|
+
* @param {string} [opts.tool] - filter by tool name
|
|
69
|
+
* @param {string} [opts.session] - filter by session ID
|
|
70
|
+
* @param {string} [opts.since] - ISO timestamp lower bound
|
|
71
|
+
* @param {string} [opts.before] - ISO timestamp upper bound
|
|
72
|
+
* @param {string} [opts.path] - substring match on args JSON
|
|
73
|
+
* @returns {Object[]} matching activity rows
|
|
74
|
+
*/
|
|
75
|
+
query({ limit = 50, tool, session, since, before, path: pathFilter } = {}) {
|
|
76
|
+
if (!this.db) return [];
|
|
77
|
+
|
|
78
|
+
let sql = "SELECT * FROM activity WHERE 1=1";
|
|
79
|
+
const params = [];
|
|
80
|
+
|
|
81
|
+
if (tool) {
|
|
82
|
+
sql += " AND tool_name = ?";
|
|
83
|
+
params.push(tool);
|
|
84
|
+
}
|
|
85
|
+
if (session) {
|
|
86
|
+
sql += " AND session_id = ?";
|
|
87
|
+
params.push(session);
|
|
88
|
+
}
|
|
89
|
+
if (since) {
|
|
90
|
+
sql += " AND timestamp >= ?";
|
|
91
|
+
params.push(since);
|
|
92
|
+
}
|
|
93
|
+
if (before) {
|
|
94
|
+
sql += " AND timestamp <= ?";
|
|
95
|
+
params.push(before);
|
|
96
|
+
}
|
|
97
|
+
if (pathFilter) {
|
|
98
|
+
sql += " AND args_json LIKE ? ESCAPE '\\'";
|
|
99
|
+
const escaped = pathFilter.replace(/[%_\\]/g, "\\$&");
|
|
100
|
+
params.push(`%${escaped}%`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
104
|
+
params.push(limit);
|
|
105
|
+
|
|
106
|
+
return this.db.prepare(sql).all(...params);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Delete activity entries, optionally filtered.
|
|
111
|
+
* @param {Object} [opts]
|
|
112
|
+
* @param {string} [opts.session] - delete only this session
|
|
113
|
+
* @param {string} [opts.tool] - delete only this tool
|
|
114
|
+
* @param {string} [opts.before] - delete entries before this timestamp
|
|
115
|
+
* @returns {number} number of deleted rows
|
|
116
|
+
*/
|
|
117
|
+
clear({ session, tool, before } = {}) {
|
|
118
|
+
if (!this.db) return 0;
|
|
119
|
+
|
|
120
|
+
let sql = "DELETE FROM activity WHERE 1=1";
|
|
121
|
+
const params = [];
|
|
122
|
+
|
|
123
|
+
if (session) {
|
|
124
|
+
sql += " AND session_id = ?";
|
|
125
|
+
params.push(session);
|
|
126
|
+
}
|
|
127
|
+
if (tool) {
|
|
128
|
+
sql += " AND tool_name = ?";
|
|
129
|
+
params.push(tool);
|
|
130
|
+
}
|
|
131
|
+
if (before) {
|
|
132
|
+
sql += " AND timestamp < ?";
|
|
133
|
+
params.push(before);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const result = this.db.prepare(sql).run(...params);
|
|
137
|
+
return result.changes;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Close the database connection. */
|
|
141
|
+
shutdown() {
|
|
142
|
+
if (this.db) {
|
|
143
|
+
this.db.close();
|
|
144
|
+
this.db = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|