seekstone 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shaq Mughal
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,62 @@
1
+ # seekstone
2
+
3
+ **An Obsidian MCP server — filesystem-direct, low context-tax.**
4
+
5
+ Seekstone reads your Obsidian vault **directly from disk** instead of routing through the Obsidian Local REST API plugin. The practical difference: a search that returns ~1.75 MB / ~459,000 tokens via the REST plugin returns **~3 KB / ~800 tokens** via seekstone — a ~575× reduction. Claude can search and read notes without burning its context window on a single tool call.
6
+
7
+ It runs as a standard [MCP](https://modelcontextprotocol.io) stdio server with no Obsidian app or plugin required — just point it at a vault directory.
8
+
9
+ ## Install
10
+
11
+ ```jsonc
12
+ // Claude Desktop — claude_desktop_config.json
13
+ {
14
+ "mcpServers": {
15
+ "seekstone": {
16
+ "command": "npx",
17
+ "args": ["-y", "seekstone"],
18
+ "env": { "SEEKSTONE_VAULT": "/absolute/path/to/your/vault" }
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ Or with Claude Code:
25
+
26
+ ```bash
27
+ claude mcp add seekstone --env SEEKSTONE_VAULT=/absolute/path/to/your/vault -- npx -y seekstone
28
+ ```
29
+
30
+ Restart the client. On startup seekstone walks the vault and builds an in-memory full-text index (a couple of seconds for a few thousand notes), then keeps it in sync as notes change.
31
+
32
+ ## Configuration
33
+
34
+ | Env var | Required | Description |
35
+ |---|---|---|
36
+ | `SEEKSTONE_VAULT` | yes | Absolute path to the Obsidian vault. |
37
+ | `SEEKSTONE_LOG_LEVEL` | no | `error` \| `warn` \| `info` (default) \| `debug`. |
38
+ | `SEEKSTONE_LOG_FILE` | no | Absolute path; when set, JSON-line logs are appended here (size-rotated). |
39
+ | `SEEKSTONE_WATCH_POLL` | no | Set to `1` to stat-poll for changes instead of native OS events — slower but reliable on network drives, WSL, and some containers. |
40
+
41
+ Works on macOS, Linux, and Windows.
42
+
43
+ ## Tools
44
+
45
+ | Tool | Description |
46
+ |---|---|
47
+ | `search` | Full-text search. Returns ranked ~200-char excerpts, not full notes. Fuzzy, prefix, and phrase queries. |
48
+ | `read_note` | Read the full content of a note by vault-relative path. |
49
+ | `list_notes` | List notes, optionally filtered by folder prefix or tag. |
50
+ | `create_note` | Create a note (optional frontmatter + body); parent dirs created automatically. |
51
+ | `delete_note` | Permanently delete a note. |
52
+ | `move_note` | Move/rename a note; destination dirs created automatically. |
53
+ | `append_note` | Append to a note body without touching frontmatter. |
54
+ | `patch_frontmatter` | Set/update/delete frontmatter keys without reordering existing keys or changing quote style. |
55
+
56
+ ## Requirements
57
+
58
+ - Node.js ≥ 22
59
+
60
+ ## License
61
+
62
+ MIT © Shaq Mughal. Source and issues: <https://github.com/shaqmughal/seekstone>
package/dist/index.js ADDED
@@ -0,0 +1,944 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ // src/tools/append_note.ts
12
+ import { readFile, writeFile } from "fs/promises";
13
+ import { join } from "path";
14
+
15
+ // ../core/src/frontmatter.ts
16
+ import { parse as parseYaml } from "yaml";
17
+ function parseFrontmatter(text) {
18
+ let offset = 0;
19
+ if (text.charCodeAt(0) === 65279) offset = 1;
20
+ const head = text.slice(offset, offset + 4);
21
+ if (head !== "---\n" && text.slice(offset, offset + 5) !== "---\r\n") {
22
+ return {
23
+ present: false,
24
+ data: null,
25
+ keys: [],
26
+ malformed: false,
27
+ bodyStart: 0,
28
+ body: text
29
+ };
30
+ }
31
+ const fmStart = offset + (head === "---\n" ? 4 : 5);
32
+ const closeLF = text.indexOf("\n---\n", fmStart - 1);
33
+ const closeCRLF = text.indexOf("\r\n---\r\n", fmStart - 1);
34
+ let closeIdx = -1;
35
+ let closeLen = 0;
36
+ if (closeLF !== -1 && (closeCRLF === -1 || closeLF < closeCRLF)) {
37
+ closeIdx = closeLF;
38
+ closeLen = 5;
39
+ } else if (closeCRLF !== -1) {
40
+ closeIdx = closeCRLF;
41
+ closeLen = 7;
42
+ }
43
+ if (closeIdx === -1) {
44
+ return {
45
+ present: true,
46
+ data: null,
47
+ keys: [],
48
+ malformed: true,
49
+ bodyStart: 0,
50
+ body: text
51
+ };
52
+ }
53
+ const yamlText = text.slice(fmStart, closeIdx + 1);
54
+ const bodyStart = closeIdx + closeLen;
55
+ const body = text.slice(bodyStart);
56
+ let data = null;
57
+ let malformed = false;
58
+ try {
59
+ const parsed = parseYaml(yamlText);
60
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
61
+ data = parsed;
62
+ } else {
63
+ malformed = parsed !== null && parsed !== void 0;
64
+ }
65
+ } catch {
66
+ malformed = true;
67
+ }
68
+ return {
69
+ present: true,
70
+ data,
71
+ keys: data ? Object.keys(data) : [],
72
+ malformed,
73
+ bodyStart,
74
+ body
75
+ };
76
+ }
77
+
78
+ // src/tools/append_note.ts
79
+ import { z } from "zod";
80
+ var AppendNoteInput = z.object({
81
+ path: z.string().describe("Vault-relative path to the note."),
82
+ content: z.string().min(1).describe(
83
+ "Text to append to the note body. Will be separated from existing content by a blank line."
84
+ )
85
+ });
86
+ async function appendNote(ctx2, input) {
87
+ const absPath = join(ctx2.vaultRoot, input.path);
88
+ if (!absPath.startsWith(ctx2.vaultRoot)) {
89
+ throw new Error(`Path outside vault: ${input.path}`);
90
+ }
91
+ const original = await readFile(absPath, "utf8");
92
+ const fm = parseFrontmatter(original);
93
+ const body = fm.body.endsWith("\n") ? fm.body : `${fm.body}
94
+ `;
95
+ const separator = body.endsWith("\n\n") ? "" : "\n";
96
+ const newBody = `${body}${separator}${input.content}`;
97
+ const header = original.slice(0, fm.bodyStart);
98
+ const newContent = `${header}${newBody}`;
99
+ await writeFile(absPath, newContent, "utf8");
100
+ const cached = ctx2.notes.get(input.path);
101
+ if (cached) {
102
+ cached.body = newBody;
103
+ cached.raw = newContent;
104
+ }
105
+ return { path: input.path, bytesWritten: Buffer.byteLength(newContent, "utf8") };
106
+ }
107
+
108
+ // src/tools/create_note.ts
109
+ import { access, mkdir, writeFile as writeFile2 } from "fs/promises";
110
+ import { dirname, join as join2 } from "path";
111
+ import { stringify as stringifyYaml } from "yaml";
112
+ import { z as z2 } from "zod";
113
+
114
+ // src/index/doc.ts
115
+ import { basename, extname } from "path";
116
+
117
+ // ../core/src/extract.ts
118
+ var INLINE_TAG_RE = /(?:^|[^\w\]/])#([A-Za-z_][\w/-]*)/g;
119
+ function extractInlineTags(body) {
120
+ const out = [];
121
+ for (const m of body.matchAll(INLINE_TAG_RE)) {
122
+ if (m[1]) out.push(m[1]);
123
+ }
124
+ return out;
125
+ }
126
+ function frontmatterTags(fm) {
127
+ if (!fm) return [];
128
+ const raw = fm.tags ?? fm.tag;
129
+ if (raw == null) return [];
130
+ if (Array.isArray(raw)) {
131
+ return raw.filter((t) => typeof t === "string").map((t) => t.replace(/^#/, ""));
132
+ }
133
+ if (typeof raw === "string") {
134
+ return raw.split(/[\s,]+/).filter(Boolean).map((t) => t.replace(/^#/, ""));
135
+ }
136
+ return [];
137
+ }
138
+
139
+ // src/index/doc.ts
140
+ function buildDoc(relPath, raw) {
141
+ const fm = parseFrontmatter(raw);
142
+ const allTags = [.../* @__PURE__ */ new Set([...extractInlineTags(fm.body), ...frontmatterTags(fm.data)])];
143
+ const title = typeof fm.data?.title === "string" && fm.data.title ? fm.data.title : basename(relPath, extname(relPath));
144
+ return {
145
+ id: relPath,
146
+ title,
147
+ body: fm.body,
148
+ tags: allTags.join(" "),
149
+ fmKeys: fm.keys.join(" "),
150
+ raw,
151
+ sizeBytes: Buffer.byteLength(raw, "utf8"),
152
+ mtimeMs: Date.now()
153
+ };
154
+ }
155
+ function upsertDoc(ctx2, doc) {
156
+ if (ctx2.notes.has(doc.id)) ctx2.index.discard(doc.id);
157
+ ctx2.notes.set(doc.id, doc);
158
+ ctx2.index.add(doc);
159
+ }
160
+
161
+ // src/tools/create_note.ts
162
+ var CreateNoteInput = z2.object({
163
+ path: z2.string().describe(
164
+ 'Vault-relative path for the new note, e.g. "Daily Notes/2026-06-01.md". Parent directories are created automatically.'
165
+ ),
166
+ content: z2.string().default("").describe("Body content for the note."),
167
+ frontmatter: z2.record(z2.string(), z2.unknown()).optional().describe("Frontmatter key-value pairs to write as a YAML block at the top of the note."),
168
+ overwrite: z2.boolean().default(false).describe("Overwrite an existing note. Defaults to false \u2014 throws if the note already exists.")
169
+ });
170
+ async function createNote(ctx2, input) {
171
+ const absPath = join2(ctx2.vaultRoot, input.path);
172
+ if (!absPath.startsWith(ctx2.vaultRoot)) {
173
+ throw new Error(`Path outside vault: ${input.path}`);
174
+ }
175
+ if (!input.overwrite) {
176
+ try {
177
+ await access(absPath);
178
+ throw new Error(`Note already exists: ${input.path}. Pass overwrite: true to replace it.`);
179
+ } catch (err) {
180
+ if (err.code !== "ENOENT") throw err;
181
+ }
182
+ }
183
+ let raw = "";
184
+ if (input.frontmatter && Object.keys(input.frontmatter).length > 0) {
185
+ raw = `---
186
+ ${stringifyYaml(input.frontmatter)}---
187
+ `;
188
+ }
189
+ raw += input.content ?? "";
190
+ await mkdir(dirname(absPath), { recursive: true });
191
+ await writeFile2(absPath, raw, "utf8");
192
+ upsertDoc(ctx2, buildDoc(input.path, raw));
193
+ return { path: input.path, bytesWritten: Buffer.byteLength(raw, "utf8") };
194
+ }
195
+
196
+ // src/tools/delete_note.ts
197
+ import { rm } from "fs/promises";
198
+ import { join as join3 } from "path";
199
+ import { z as z3 } from "zod";
200
+ var DeleteNoteInput = z3.object({
201
+ path: z3.string().describe("Vault-relative path of the note to delete.")
202
+ });
203
+ async function deleteNote(ctx2, input) {
204
+ const absPath = join3(ctx2.vaultRoot, input.path);
205
+ if (!absPath.startsWith(ctx2.vaultRoot)) {
206
+ throw new Error(`Path outside vault: ${input.path}`);
207
+ }
208
+ await rm(absPath);
209
+ if (ctx2.notes.has(input.path)) {
210
+ ctx2.index.discard(input.path);
211
+ ctx2.notes.delete(input.path);
212
+ }
213
+ return { path: input.path };
214
+ }
215
+
216
+ // src/tools/list_notes.ts
217
+ import { z as z4 } from "zod";
218
+ var ListNotesInput = z4.object({
219
+ folder: z4.string().optional().describe(
220
+ 'Vault-relative folder prefix to list. Omit for all notes. Example: "Daily Notes/2026".'
221
+ ),
222
+ tag: z4.string().optional().describe("Filter by tag. Include or omit the leading #."),
223
+ limit: z4.number().int().min(1).max(500).default(100).describe("Maximum number of results.")
224
+ });
225
+ function listNotes(ctx2, input) {
226
+ const tag = input.tag?.replace(/^#/, "");
227
+ const results = [];
228
+ for (const [path, note] of ctx2.notes) {
229
+ if (input.folder && !path.startsWith(input.folder)) continue;
230
+ if (tag) {
231
+ const noteTags = note.tags.split(" ").filter(Boolean);
232
+ if (!noteTags.includes(tag)) continue;
233
+ }
234
+ results.push({
235
+ path,
236
+ title: note.title,
237
+ tags: note.tags.split(" ").filter(Boolean),
238
+ sizeBytes: note.sizeBytes
239
+ });
240
+ if (results.length >= input.limit) break;
241
+ }
242
+ return results.sort((a, b) => a.path.localeCompare(b.path));
243
+ }
244
+
245
+ // src/tools/move_note.ts
246
+ import { access as access2, mkdir as mkdir2, rename } from "fs/promises";
247
+ import { dirname as dirname2, join as join4 } from "path";
248
+ import { z as z5 } from "zod";
249
+ var MoveNoteInput = z5.object({
250
+ from: z5.string().describe("Vault-relative path of the note to move."),
251
+ to: z5.string().describe("Vault-relative destination path."),
252
+ overwrite: z5.boolean().default(false).describe("Overwrite the destination if it exists. Defaults to false.")
253
+ });
254
+ async function moveNote(ctx2, input) {
255
+ const absFrom = join4(ctx2.vaultRoot, input.from);
256
+ const absTo = join4(ctx2.vaultRoot, input.to);
257
+ if (!absFrom.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${input.from}`);
258
+ if (!absTo.startsWith(ctx2.vaultRoot)) throw new Error(`Path outside vault: ${input.to}`);
259
+ try {
260
+ await access2(absFrom);
261
+ } catch {
262
+ throw new Error(`Note not found: ${input.from}`);
263
+ }
264
+ if (!input.overwrite) {
265
+ try {
266
+ await access2(absTo);
267
+ throw new Error(
268
+ `Destination already exists: ${input.to}. Pass overwrite: true to replace it.`
269
+ );
270
+ } catch (err) {
271
+ if (err.code !== "ENOENT") throw err;
272
+ }
273
+ }
274
+ await mkdir2(dirname2(absTo), { recursive: true });
275
+ await rename(absFrom, absTo);
276
+ const raw = ctx2.notes.get(input.from)?.raw ?? "";
277
+ if (ctx2.notes.has(input.from)) {
278
+ ctx2.index.discard(input.from);
279
+ ctx2.notes.delete(input.from);
280
+ }
281
+ upsertDoc(ctx2, buildDoc(input.to, raw));
282
+ return { from: input.from, to: input.to };
283
+ }
284
+
285
+ // src/tools/patch_frontmatter.ts
286
+ import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
287
+ import { join as join5 } from "path";
288
+ import { parseDocument } from "yaml";
289
+ import { z as z6 } from "zod";
290
+ var PatchFrontmatterInput = z6.object({
291
+ path: z6.string().describe("Vault-relative path to the note."),
292
+ patch: z6.record(z6.string(), z6.unknown()).describe(
293
+ "Key-value pairs to set in the frontmatter. Existing keys not in the patch are unchanged. Pass null as a value to remove a key."
294
+ )
295
+ });
296
+ async function patchFrontmatter(ctx2, input) {
297
+ const absPath = join5(ctx2.vaultRoot, input.path);
298
+ if (!absPath.startsWith(ctx2.vaultRoot)) {
299
+ throw new Error(`Path outside vault: ${input.path}`);
300
+ }
301
+ const original = await readFile2(absPath, "utf8");
302
+ const fm = parseFrontmatter(original);
303
+ const keysChanged = [];
304
+ const keysAdded = [];
305
+ const keysRemoved = [];
306
+ let newContent;
307
+ if (!fm.present) {
308
+ const doc = parseDocument("");
309
+ for (const [k, v] of Object.entries(input.patch)) {
310
+ if (v !== null) {
311
+ doc.set(k, v);
312
+ keysAdded.push(k);
313
+ }
314
+ }
315
+ newContent = `---
316
+ ${doc.toString()}---
317
+ ${original}`;
318
+ } else {
319
+ const opensWithCRLF = original.startsWith("---\r\n");
320
+ const openLen = opensWithCRLF ? 5 : 4;
321
+ const closeLen = opensWithCRLF ? 7 : 5;
322
+ const yamlText = original.slice(openLen, fm.bodyStart - closeLen);
323
+ const doc = parseDocument(yamlText, { keepSourceTokens: true });
324
+ for (const [k, v] of Object.entries(input.patch)) {
325
+ const existing = doc.has(k);
326
+ if (v === null) {
327
+ if (existing) {
328
+ doc.delete(k);
329
+ keysRemoved.push(k);
330
+ }
331
+ } else if (existing) {
332
+ doc.set(k, v);
333
+ keysChanged.push(k);
334
+ } else {
335
+ doc.set(k, v);
336
+ keysAdded.push(k);
337
+ }
338
+ }
339
+ const head = opensWithCRLF ? "---\r\n" : "---\n";
340
+ const tail = opensWithCRLF ? "---\r\n" : "---\n";
341
+ newContent = `${head}${doc.toString()}${tail}${fm.body}`;
342
+ }
343
+ await writeFile3(absPath, newContent, "utf8");
344
+ const cached = ctx2.notes.get(input.path);
345
+ if (cached) {
346
+ const updated = parseFrontmatter(newContent);
347
+ cached.raw = newContent;
348
+ cached.fmKeys = updated.keys.join(" ");
349
+ }
350
+ return { path: input.path, keysChanged, keysAdded, keysRemoved };
351
+ }
352
+
353
+ // src/tools/read_note.ts
354
+ import { readFile as readFile3 } from "fs/promises";
355
+ import { join as join6 } from "path";
356
+ import { z as z7 } from "zod";
357
+ var ReadNoteInput = z7.object({
358
+ path: z7.string().describe('Vault-relative path to the note, e.g. "Daily Notes/2026-05-29.md".')
359
+ });
360
+ async function readNote(ctx2, input) {
361
+ const absPath = join6(ctx2.vaultRoot, input.path);
362
+ if (!absPath.startsWith(ctx2.vaultRoot)) {
363
+ throw new Error(`Path outside vault: ${input.path}`);
364
+ }
365
+ const content = await readFile3(absPath, "utf8");
366
+ return {
367
+ path: input.path,
368
+ content,
369
+ sizeBytes: Buffer.byteLength(content, "utf8")
370
+ };
371
+ }
372
+
373
+ // src/tools/search.ts
374
+ import { z as z8 } from "zod";
375
+
376
+ // src/index/excerpt.ts
377
+ function extractExcerpt(text, terms, maxLen = 200) {
378
+ const lower = text.toLowerCase();
379
+ let bestIdx = -1;
380
+ for (const term of terms) {
381
+ const idx = lower.indexOf(term.toLowerCase());
382
+ if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) {
383
+ bestIdx = idx;
384
+ }
385
+ }
386
+ if (bestIdx === -1) {
387
+ return truncate(text.trimStart(), maxLen);
388
+ }
389
+ const half = Math.floor(maxLen / 2);
390
+ const start = Math.max(0, bestIdx - half);
391
+ const end = Math.min(text.length, bestIdx + half + (terms[0]?.length ?? 0));
392
+ const slice = text.slice(start, end).trim();
393
+ return `${start > 0 ? "\u2026" : ""}${slice}${end < text.length ? "\u2026" : ""}`;
394
+ }
395
+ function truncate(s, max) {
396
+ if (s.length <= max) return s;
397
+ return `${s.slice(0, max).trimEnd()}\u2026`;
398
+ }
399
+
400
+ // src/tools/search.ts
401
+ var SearchInput = z8.object({
402
+ query: z8.string().min(1).describe("Search query. Supports fuzzy matching and prefix search."),
403
+ limit: z8.number().int().min(1).max(50).default(10).describe("Maximum number of results to return."),
404
+ folder: z8.string().optional().describe("Restrict results to notes under this vault-relative folder prefix."),
405
+ tag: z8.string().optional().describe("Restrict results to notes containing this tag.")
406
+ });
407
+ function search(ctx2, input) {
408
+ const results = ctx2.index.search(input.query, {
409
+ boost: { title: 3, tags: 2, body: 1 },
410
+ fuzzy: 0.2,
411
+ prefix: true
412
+ });
413
+ const terms = input.query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
414
+ const hits = [];
415
+ for (const r of results) {
416
+ if (input.folder && !r.id.startsWith(input.folder)) continue;
417
+ if (input.tag) {
418
+ const note2 = ctx2.notes.get(r.id);
419
+ if (!note2) continue;
420
+ const noteTags2 = note2.tags.split(" ");
421
+ if (!noteTags2.some((t) => t === input.tag || t === input.tag?.replace(/^#/, ""))) continue;
422
+ }
423
+ const note = ctx2.notes.get(r.id);
424
+ const body = note?.body ?? "";
425
+ const noteTags = note?.tags ? note.tags.split(" ").filter(Boolean) : [];
426
+ hits.push({
427
+ path: r.id,
428
+ title: r.title,
429
+ score: r.score,
430
+ excerpt: extractExcerpt(body, terms),
431
+ tags: noteTags
432
+ });
433
+ if (hits.length >= input.limit) break;
434
+ }
435
+ return hits;
436
+ }
437
+
438
+ // src/dispatch.ts
439
+ var META_KEYS = ["path", "from", "to", "folder", "tag", "limit", "overwrite"];
440
+ function safeMeta(args) {
441
+ if (typeof args !== "object" || args === null) return {};
442
+ const a = args;
443
+ const out = {};
444
+ for (const k of META_KEYS) {
445
+ if (a[k] !== void 0) out[k] = a[k];
446
+ }
447
+ if (typeof a.query === "string") out.queryLen = a.query.length;
448
+ return out;
449
+ }
450
+ function resultBytes(r) {
451
+ let n = 0;
452
+ for (const c of r.content) n += Buffer.byteLength(c.text);
453
+ return n;
454
+ }
455
+ async function dispatch(ctx2, name, args, log2) {
456
+ const start = performance.now();
457
+ const meta = safeMeta(args);
458
+ log2.debug("tool start", { tool: name, args });
459
+ try {
460
+ const result = await run(ctx2, name, args);
461
+ const durationMs = Math.round((performance.now() - start) * 100) / 100;
462
+ log2.info("tool ok", { tool: name, durationMs, resultBytes: resultBytes(result), ...meta });
463
+ return result;
464
+ } catch (err) {
465
+ const durationMs = Math.round((performance.now() - start) * 100) / 100;
466
+ const message = err instanceof Error ? err.message : String(err);
467
+ log2.error("tool error", {
468
+ tool: name,
469
+ durationMs,
470
+ error: message,
471
+ stack: err instanceof Error ? err.stack : void 0,
472
+ ...meta
473
+ });
474
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
475
+ }
476
+ }
477
+ async function run(ctx2, name, args) {
478
+ switch (name) {
479
+ case "search": {
480
+ const input = SearchInput.parse(args);
481
+ const hits = search(ctx2, input);
482
+ return { content: [{ type: "text", text: JSON.stringify(hits, null, 2) }] };
483
+ }
484
+ case "read_note": {
485
+ const input = ReadNoteInput.parse(args);
486
+ const result = await readNote(ctx2, input);
487
+ return { content: [{ type: "text", text: result.content }] };
488
+ }
489
+ case "list_notes": {
490
+ const input = ListNotesInput.parse(args);
491
+ const entries = listNotes(ctx2, input);
492
+ return { content: [{ type: "text", text: JSON.stringify(entries, null, 2) }] };
493
+ }
494
+ case "create_note": {
495
+ const input = CreateNoteInput.parse(args);
496
+ const result = await createNote(ctx2, input);
497
+ return {
498
+ content: [{ type: "text", text: `Created ${result.path} (${result.bytesWritten} bytes).` }]
499
+ };
500
+ }
501
+ case "delete_note": {
502
+ const input = DeleteNoteInput.parse(args);
503
+ await deleteNote(ctx2, input);
504
+ return { content: [{ type: "text", text: `Deleted ${input.path}.` }] };
505
+ }
506
+ case "move_note": {
507
+ const input = MoveNoteInput.parse(args);
508
+ const result = await moveNote(ctx2, input);
509
+ return { content: [{ type: "text", text: `Moved ${result.from} \u2192 ${result.to}.` }] };
510
+ }
511
+ case "append_note": {
512
+ const input = AppendNoteInput.parse(args);
513
+ const result = await appendNote(ctx2, input);
514
+ return {
515
+ content: [
516
+ { type: "text", text: `Appended ${result.bytesWritten} bytes to ${result.path}.` }
517
+ ]
518
+ };
519
+ }
520
+ case "patch_frontmatter": {
521
+ const input = PatchFrontmatterInput.parse(args);
522
+ const result = await patchFrontmatter(ctx2, input);
523
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
524
+ }
525
+ default:
526
+ throw new Error(`Unknown tool: ${name}`);
527
+ }
528
+ }
529
+
530
+ // src/index/build.ts
531
+ import { readFile as readFile4 } from "fs/promises";
532
+ import { basename as basename2, extname as extname3 } from "path";
533
+
534
+ // ../core/src/walk.ts
535
+ import { stat } from "fs/promises";
536
+ import { extname as extname2, join as join7, relative, sep } from "path";
537
+ import fg from "fast-glob";
538
+ var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".heic"]);
539
+ var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".mkv", ".avi"]);
540
+ var AUDIO_EXTS = /* @__PURE__ */ new Set([".mp3", ".m4a", ".wav", ".flac", ".ogg"]);
541
+ var IGNORE_GLOBS = [
542
+ "**/.obsidian/**",
543
+ "**/.trash/**",
544
+ "**/.git/**",
545
+ "**/.DS_Store",
546
+ "**/node_modules/**"
547
+ ];
548
+ function classify(relPath) {
549
+ if (relPath.endsWith(".excalidraw.md")) return "note";
550
+ if (relPath.endsWith(".excalidraw")) return "excalidraw";
551
+ const ext = extname2(relPath).toLowerCase();
552
+ if (ext === ".md") return "note";
553
+ if (ext === ".canvas") return "canvas";
554
+ if (ext === ".pdf") return "pdf";
555
+ if (IMAGE_EXTS.has(ext)) return "image";
556
+ if (VIDEO_EXTS.has(ext)) return "video";
557
+ if (AUDIO_EXTS.has(ext)) return "audio";
558
+ return "other";
559
+ }
560
+ async function walkVault(vaultRoot2) {
561
+ const matches = await fg("**/*", {
562
+ cwd: vaultRoot2,
563
+ absolute: true,
564
+ onlyFiles: true,
565
+ followSymbolicLinks: false,
566
+ dot: true,
567
+ // include dotfiles so the ignore globs are the only gate
568
+ ignore: IGNORE_GLOBS,
569
+ stats: true,
570
+ suppressErrors: true
571
+ });
572
+ const entries = [];
573
+ for (const m of matches) {
574
+ const absPath = typeof m === "string" ? m : m.path;
575
+ const stats = typeof m === "string" ? await stat(absPath) : m.stats;
576
+ if (!stats) continue;
577
+ const relPath = relative(vaultRoot2, absPath);
578
+ const topDir = relPath.split(sep)[0] ?? "";
579
+ entries.push({
580
+ absPath,
581
+ relPath,
582
+ topDir,
583
+ ext: extname2(relPath).toLowerCase(),
584
+ kind: classify(relPath),
585
+ sizeBytes: stats.size,
586
+ mtimeMs: stats.mtimeMs
587
+ });
588
+ }
589
+ return entries;
590
+ }
591
+
592
+ // src/index/build.ts
593
+ import MiniSearch from "minisearch";
594
+ async function buildIndex(vaultRoot2) {
595
+ const t0 = Date.now();
596
+ const index2 = new MiniSearch({
597
+ idField: "id",
598
+ fields: ["title", "body", "tags", "fmKeys"],
599
+ storeFields: ["id", "title", "tags", "sizeBytes", "mtimeMs"],
600
+ searchOptions: {
601
+ boost: { title: 3, tags: 2, body: 1 },
602
+ fuzzy: 0.2,
603
+ prefix: true
604
+ }
605
+ });
606
+ const entries = await walkVault(vaultRoot2);
607
+ const noteEntries = entries.filter((e) => e.kind === "note");
608
+ const docs = [];
609
+ const notes2 = /* @__PURE__ */ new Map();
610
+ await Promise.all(
611
+ noteEntries.map(async (entry) => {
612
+ try {
613
+ const raw = await readFile4(entry.absPath, "utf8");
614
+ const fm = parseFrontmatter(raw);
615
+ const inlineTags = extractInlineTags(fm.body);
616
+ const fmTags = frontmatterTags(fm.data);
617
+ const allTags = [.../* @__PURE__ */ new Set([...inlineTags, ...fmTags])];
618
+ const title = typeof fm.data?.title === "string" && fm.data.title ? fm.data.title : basename2(entry.relPath, extname3(entry.relPath));
619
+ const doc = {
620
+ id: entry.relPath,
621
+ title,
622
+ body: fm.body,
623
+ tags: allTags.join(" "),
624
+ fmKeys: fm.keys.join(" "),
625
+ raw,
626
+ sizeBytes: entry.sizeBytes,
627
+ mtimeMs: entry.mtimeMs
628
+ };
629
+ docs.push(doc);
630
+ notes2.set(entry.relPath, doc);
631
+ } catch {
632
+ }
633
+ })
634
+ );
635
+ index2.addAll(docs);
636
+ return { index: index2, notes: notes2, buildMs: Date.now() - t0 };
637
+ }
638
+
639
+ // src/log.ts
640
+ import { appendFileSync, renameSync, statSync } from "fs";
641
+ var ORDER = ["error", "warn", "info", "debug"];
642
+ var DEFAULT_MAX_SIZE = 5 * 1024 * 1024;
643
+ function parseLevel(raw, onBad) {
644
+ if (raw === void 0 || raw === "") return "info";
645
+ const v = raw.trim().toLowerCase();
646
+ for (const lvl of ORDER) {
647
+ if (lvl === v) return lvl;
648
+ }
649
+ onBad(`invalid SEEKSTONE_LOG_LEVEL=${JSON.stringify(raw)}; using "info"`);
650
+ return "info";
651
+ }
652
+ function parseSize(raw) {
653
+ if (raw === void 0) return DEFAULT_MAX_SIZE;
654
+ const m = /^(\d+(?:\.\d+)?)(b|kb|mb|gb)?$/i.exec(raw.trim());
655
+ if (m === null) return DEFAULT_MAX_SIZE;
656
+ const num = Number(m[1]);
657
+ const unit = (m[2] ?? "b").toLowerCase();
658
+ const mult = unit === "gb" ? 1e9 : unit === "mb" ? 1e6 : unit === "kb" ? 1e3 : 1;
659
+ return Math.max(1, Math.floor(num * mult));
660
+ }
661
+ function fmtFields(fields) {
662
+ const parts = [];
663
+ for (const [k, v] of Object.entries(fields)) {
664
+ if (v === void 0) continue;
665
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
666
+ }
667
+ return parts.length > 0 ? ` ${parts.join(" ")}` : "";
668
+ }
669
+ function createLogger(opts = {}) {
670
+ const env = opts.env ?? process.env;
671
+ const stderr = opts.stderr ?? ((line) => void process.stderr.write(line));
672
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
673
+ const level = parseLevel(
674
+ env.SEEKSTONE_LOG_LEVEL,
675
+ (m) => stderr(`WARN ${now().toISOString()} ${m}
676
+ `)
677
+ );
678
+ const threshold = ORDER.indexOf(level);
679
+ const filePath = env.SEEKSTONE_LOG_FILE;
680
+ const maxSize = parseSize(env.SEEKSTONE_LOG_MAX_SIZE);
681
+ let fileOk = false;
682
+ let bytes = 0;
683
+ if (filePath !== void 0 && filePath !== "") {
684
+ try {
685
+ try {
686
+ bytes = statSync(filePath).size;
687
+ } catch {
688
+ bytes = 0;
689
+ }
690
+ appendFileSync(filePath, "");
691
+ fileOk = true;
692
+ } catch (err) {
693
+ const m = err instanceof Error ? err.message : String(err);
694
+ stderr(
695
+ `WARN ${now().toISOString()} cannot write log file ${filePath}: ${m}; file logging disabled
696
+ `
697
+ );
698
+ }
699
+ }
700
+ function writeFile4(record) {
701
+ if (!fileOk || filePath === void 0) return;
702
+ const line = `${JSON.stringify(record)}
703
+ `;
704
+ const len = Buffer.byteLength(line);
705
+ try {
706
+ if (bytes > 0 && bytes + len > maxSize) {
707
+ renameSync(filePath, `${filePath}.1`);
708
+ bytes = 0;
709
+ }
710
+ appendFileSync(filePath, line);
711
+ bytes += len;
712
+ } catch {
713
+ fileOk = false;
714
+ }
715
+ }
716
+ function emit(lvl, msg, fields) {
717
+ if (ORDER.indexOf(lvl) > threshold) return;
718
+ const ts = now().toISOString();
719
+ const f = fields ?? {};
720
+ stderr(`${lvl.toUpperCase().padEnd(5)} ${ts} ${msg}${fmtFields(f)}
721
+ `);
722
+ writeFile4({ t: ts, level: lvl, msg, ...f });
723
+ }
724
+ return {
725
+ level,
726
+ error: (m, f) => emit("error", m, f),
727
+ warn: (m, f) => emit("warn", m, f),
728
+ info: (m, f) => emit("info", m, f),
729
+ debug: (m, f) => emit("debug", m, f)
730
+ };
731
+ }
732
+
733
+ // src/watcher.ts
734
+ import { readFile as readFile5 } from "fs/promises";
735
+ import { join as join8, relative as relative2, sep as sep2 } from "path";
736
+ import chokidar from "chokidar";
737
+ function startWatcher(ctx2, log2, opts) {
738
+ const usePolling = opts?.usePolling ?? process.env.SEEKSTONE_WATCH_POLL === "1";
739
+ const toRel = (abs) => relative2(ctx2.vaultRoot, abs).split(sep2).join("/");
740
+ async function upsert(relPath, op) {
741
+ try {
742
+ const raw = await readFile5(join8(ctx2.vaultRoot, relPath), "utf8");
743
+ upsertDoc(ctx2, buildDoc(relPath, raw));
744
+ log2?.debug("index updated", { path: relPath, op });
745
+ } catch {
746
+ }
747
+ }
748
+ function remove(relPath) {
749
+ if (ctx2.notes.has(relPath)) {
750
+ ctx2.index.discard(relPath);
751
+ ctx2.notes.delete(relPath);
752
+ log2?.debug("index removed", { path: relPath, op: "delete" });
753
+ }
754
+ }
755
+ const onUpsert = (op) => (abs) => {
756
+ const relPath = toRel(abs);
757
+ if (relPath.endsWith(".md")) void upsert(relPath, op);
758
+ };
759
+ const watcher2 = chokidar.watch(ctx2.vaultRoot, {
760
+ // The startup index build already covers existing files.
761
+ ignoreInitial: true,
762
+ usePolling,
763
+ interval: 50,
764
+ binaryInterval: 100,
765
+ // Skip dot-dirs (.obsidian, .git, .trash, …) regardless of where the vault
766
+ // lives — matched on the path relative to the vault root.
767
+ ignored: (p) => relative2(ctx2.vaultRoot, p).split(sep2).some((seg) => seg.startsWith("."))
768
+ });
769
+ watcher2.on("add", onUpsert("add")).on("change", onUpsert("change")).on("unlink", (abs) => {
770
+ const relPath = toRel(abs);
771
+ if (relPath.endsWith(".md")) remove(relPath);
772
+ });
773
+ const ready = new Promise((resolve) => {
774
+ watcher2.once("ready", () => resolve());
775
+ });
776
+ return {
777
+ stop: () => void watcher2.close(),
778
+ ready
779
+ };
780
+ }
781
+
782
+ // src/index.ts
783
+ var log = createLogger();
784
+ var vaultRoot = process.env.SEEKSTONE_VAULT;
785
+ if (!vaultRoot) {
786
+ log.error("SEEKSTONE_VAULT env var is required");
787
+ process.exit(1);
788
+ }
789
+ log.info("building index", { vault: vaultRoot });
790
+ var { index, notes, buildMs } = await buildIndex(vaultRoot);
791
+ log.info("index ready", { notes: notes.size, buildMs });
792
+ var ctx = { vaultRoot, index, notes };
793
+ var watcher = startWatcher(ctx, log);
794
+ process.on("exit", watcher.stop);
795
+ var server = new Server({ name: "seekstone", version: "0.1.0" }, { capabilities: { tools: {} } });
796
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
797
+ tools: [
798
+ {
799
+ name: "search",
800
+ description: "Full-text search across the vault. Returns ranked excerpts (~200 chars) \u2014 not full notes \u2014 to minimise context usage. Supports fuzzy matching and prefix search.",
801
+ inputSchema: {
802
+ type: "object",
803
+ properties: {
804
+ query: { type: "string", description: "Search query." },
805
+ limit: { type: "number", description: "Max results (1\u201350, default 10)." },
806
+ folder: { type: "string", description: "Restrict to a vault-relative folder prefix." },
807
+ tag: { type: "string", description: "Restrict to notes with this tag." }
808
+ },
809
+ required: ["query"]
810
+ }
811
+ },
812
+ {
813
+ name: "read_note",
814
+ description: "Read the full content of a specific note by its vault-relative path. Use search first to find the right path.",
815
+ inputSchema: {
816
+ type: "object",
817
+ properties: {
818
+ path: {
819
+ type: "string",
820
+ description: 'Vault-relative path, e.g. "Daily Notes/2026-05-29.md".'
821
+ }
822
+ },
823
+ required: ["path"]
824
+ }
825
+ },
826
+ {
827
+ name: "list_notes",
828
+ description: "List notes, optionally filtered by folder prefix or tag.",
829
+ inputSchema: {
830
+ type: "object",
831
+ properties: {
832
+ folder: { type: "string", description: "Vault-relative folder prefix." },
833
+ tag: { type: "string", description: "Filter by tag (# prefix optional)." },
834
+ limit: { type: "number", description: "Max results (1\u2013500, default 100)." }
835
+ },
836
+ required: []
837
+ }
838
+ },
839
+ {
840
+ name: "create_note",
841
+ description: "Create a new note at a vault-relative path. Optionally sets frontmatter and body content. Parent directories are created automatically. Fails if the note already exists unless overwrite is true.",
842
+ inputSchema: {
843
+ type: "object",
844
+ properties: {
845
+ path: {
846
+ type: "string",
847
+ description: 'Vault-relative path, e.g. "Daily Notes/2026-06-01.md".'
848
+ },
849
+ content: { type: "string", description: "Body content for the note." },
850
+ frontmatter: {
851
+ type: "object",
852
+ description: "Frontmatter key-value pairs.",
853
+ additionalProperties: true
854
+ },
855
+ overwrite: {
856
+ type: "boolean",
857
+ description: "Overwrite an existing note. Defaults to false."
858
+ }
859
+ },
860
+ required: ["path"]
861
+ }
862
+ },
863
+ {
864
+ name: "delete_note",
865
+ description: "Permanently delete a note from the vault. This cannot be undone.",
866
+ inputSchema: {
867
+ type: "object",
868
+ properties: {
869
+ path: { type: "string", description: "Vault-relative path of the note to delete." }
870
+ },
871
+ required: ["path"]
872
+ }
873
+ },
874
+ {
875
+ name: "move_note",
876
+ description: "Move or rename a note to a new vault-relative path. Parent directories at the destination are created automatically. Fails if the destination already exists unless overwrite is true.",
877
+ inputSchema: {
878
+ type: "object",
879
+ properties: {
880
+ from: { type: "string", description: "Vault-relative source path." },
881
+ to: { type: "string", description: "Vault-relative destination path." },
882
+ overwrite: {
883
+ type: "boolean",
884
+ description: "Overwrite destination if it exists. Defaults to false."
885
+ }
886
+ },
887
+ required: ["from", "to"]
888
+ }
889
+ },
890
+ {
891
+ name: "append_note",
892
+ description: "Append text to a note body without touching the frontmatter. Safe for meeting notes, daily logs, and append-only workflows.",
893
+ inputSchema: {
894
+ type: "object",
895
+ properties: {
896
+ path: { type: "string", description: "Vault-relative path to the note." },
897
+ content: { type: "string", description: "Text to append." }
898
+ },
899
+ required: ["path", "content"]
900
+ }
901
+ },
902
+ {
903
+ name: "patch_frontmatter",
904
+ description: "Set, update, or delete frontmatter keys without reordering existing keys or changing quote style. Pass null as a value to delete a key.",
905
+ inputSchema: {
906
+ type: "object",
907
+ properties: {
908
+ path: { type: "string", description: "Vault-relative path to the note." },
909
+ patch: {
910
+ type: "object",
911
+ description: "Key-value pairs to set. Null value removes the key.",
912
+ additionalProperties: true
913
+ }
914
+ },
915
+ required: ["path", "patch"]
916
+ }
917
+ }
918
+ ]
919
+ }));
920
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
921
+ const { name, arguments: args } = req.params;
922
+ return dispatch(ctx, name, args, log);
923
+ });
924
+ var transport = new StdioServerTransport();
925
+ await server.connect(transport);
926
+ log.info("ready", { tools: 8, transport: "stdio" });
927
+ process.stderr.write(
928
+ `seekstone: add to Claude Desktop:
929
+ ${JSON.stringify(
930
+ {
931
+ mcpServers: {
932
+ seekstone: {
933
+ command: "npx",
934
+ args: ["-y", "seekstone"],
935
+ env: { SEEKSTONE_VAULT: vaultRoot }
936
+ }
937
+ }
938
+ },
939
+ null,
940
+ 2
941
+ )}
942
+ `
943
+ );
944
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/tools/append_note.ts","../../core/src/frontmatter.ts","../src/tools/create_note.ts","../src/index/doc.ts","../../core/src/extract.ts","../src/tools/delete_note.ts","../src/tools/list_notes.ts","../src/tools/move_note.ts","../src/tools/patch_frontmatter.ts","../src/tools/read_note.ts","../src/tools/search.ts","../src/index/excerpt.ts","../src/dispatch.ts","../src/index/build.ts","../../core/src/walk.ts","../src/log.ts","../src/watcher.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n type CallToolResult,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport type { ServerContext } from './context.js';\nimport { dispatch } from './dispatch.js';\nimport { buildIndex } from './index/build.js';\nimport { createLogger } from './log.js';\nimport { startWatcher } from './watcher.js';\n\nconst log = createLogger();\n\nconst vaultRoot = process.env.SEEKSTONE_VAULT;\nif (!vaultRoot) {\n log.error('SEEKSTONE_VAULT env var is required');\n process.exit(1);\n}\n\nlog.info('building index', { vault: vaultRoot });\nconst { index, notes, buildMs } = await buildIndex(vaultRoot);\nlog.info('index ready', { notes: notes.size, buildMs });\n\nconst ctx: ServerContext = { vaultRoot, index, notes };\n\nconst watcher = startWatcher(ctx, log);\nprocess.on('exit', watcher.stop);\n\nconst server = new Server({ name: 'seekstone', version: '0.1.0' }, { capabilities: { tools: {} } });\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: 'search',\n description:\n 'Full-text search across the vault. Returns ranked excerpts (~200 chars) — not full notes — to minimise context usage. Supports fuzzy matching and prefix search.',\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query.' },\n limit: { type: 'number', description: 'Max results (1–50, default 10).' },\n folder: { type: 'string', description: 'Restrict to a vault-relative folder prefix.' },\n tag: { type: 'string', description: 'Restrict to notes with this tag.' },\n },\n required: ['query'],\n },\n },\n {\n name: 'read_note',\n description:\n 'Read the full content of a specific note by its vault-relative path. Use search first to find the right path.',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Vault-relative path, e.g. \"Daily Notes/2026-05-29.md\".',\n },\n },\n required: ['path'],\n },\n },\n {\n name: 'list_notes',\n description: 'List notes, optionally filtered by folder prefix or tag.',\n inputSchema: {\n type: 'object',\n properties: {\n folder: { type: 'string', description: 'Vault-relative folder prefix.' },\n tag: { type: 'string', description: 'Filter by tag (# prefix optional).' },\n limit: { type: 'number', description: 'Max results (1–500, default 100).' },\n },\n required: [],\n },\n },\n {\n name: 'create_note',\n description:\n 'Create a new note at a vault-relative path. Optionally sets frontmatter and body content. Parent directories are created automatically. Fails if the note already exists unless overwrite is true.',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Vault-relative path, e.g. \"Daily Notes/2026-06-01.md\".',\n },\n content: { type: 'string', description: 'Body content for the note.' },\n frontmatter: {\n type: 'object',\n description: 'Frontmatter key-value pairs.',\n additionalProperties: true,\n },\n overwrite: {\n type: 'boolean',\n description: 'Overwrite an existing note. Defaults to false.',\n },\n },\n required: ['path'],\n },\n },\n {\n name: 'delete_note',\n description: 'Permanently delete a note from the vault. This cannot be undone.',\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Vault-relative path of the note to delete.' },\n },\n required: ['path'],\n },\n },\n {\n name: 'move_note',\n description:\n 'Move or rename a note to a new vault-relative path. Parent directories at the destination are created automatically. Fails if the destination already exists unless overwrite is true.',\n inputSchema: {\n type: 'object',\n properties: {\n from: { type: 'string', description: 'Vault-relative source path.' },\n to: { type: 'string', description: 'Vault-relative destination path.' },\n overwrite: {\n type: 'boolean',\n description: 'Overwrite destination if it exists. Defaults to false.',\n },\n },\n required: ['from', 'to'],\n },\n },\n {\n name: 'append_note',\n description:\n 'Append text to a note body without touching the frontmatter. Safe for meeting notes, daily logs, and append-only workflows.',\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Vault-relative path to the note.' },\n content: { type: 'string', description: 'Text to append.' },\n },\n required: ['path', 'content'],\n },\n },\n {\n name: 'patch_frontmatter',\n description:\n 'Set, update, or delete frontmatter keys without reordering existing keys or changing quote style. Pass null as a value to delete a key.',\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Vault-relative path to the note.' },\n patch: {\n type: 'object',\n description: 'Key-value pairs to set. Null value removes the key.',\n additionalProperties: true,\n },\n },\n required: ['path', 'patch'],\n },\n },\n ],\n}));\n\nserver.setRequestHandler(CallToolRequestSchema, async (req): Promise<CallToolResult> => {\n const { name, arguments: args } = req.params;\n return dispatch(ctx, name, args, log);\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\nlog.info('ready', { tools: 8, transport: 'stdio' });\n\nprocess.stderr.write(\n `seekstone: add to Claude Desktop:\\n${JSON.stringify(\n {\n mcpServers: {\n seekstone: {\n command: 'npx',\n args: ['-y', 'seekstone'],\n env: { SEEKSTONE_VAULT: vaultRoot },\n },\n },\n },\n null,\n 2,\n )}\\n`,\n);\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parseFrontmatter } from '@seekstone/core/frontmatter';\nimport { z } from 'zod';\nimport type { ServerContext } from '../context.js';\n\nexport const AppendNoteInput = z.object({\n path: z.string().describe('Vault-relative path to the note.'),\n content: z\n .string()\n .min(1)\n .describe(\n 'Text to append to the note body. Will be separated from existing content by a blank line.',\n ),\n});\nexport type AppendNoteInput = z.infer<typeof AppendNoteInput>;\n\nexport interface AppendNoteResult {\n path: string;\n bytesWritten: number;\n}\n\n/**\n * Append text to a note body without touching the frontmatter.\n *\n * The write contract:\n * - The frontmatter block (if any) is preserved byte-for-byte.\n * - A blank line is inserted before the appended content if the body\n * does not already end with one (keeps Obsidian's visual spacing).\n * - The file is written atomically (overwrite in place, same path).\n */\nexport async function appendNote(\n ctx: ServerContext,\n input: AppendNoteInput,\n): Promise<AppendNoteResult> {\n const absPath = join(ctx.vaultRoot, input.path);\n if (!absPath.startsWith(ctx.vaultRoot)) {\n throw new Error(`Path outside vault: ${input.path}`);\n }\n\n const original = await readFile(absPath, 'utf8');\n const fm = parseFrontmatter(original);\n\n // Ensure body ends with exactly one trailing newline before appending.\n const body = fm.body.endsWith('\\n') ? fm.body : `${fm.body}\\n`;\n const separator = body.endsWith('\\n\\n') ? '' : '\\n';\n const newBody = `${body}${separator}${input.content}`;\n\n // Reconstruct: original header (FM + delimiters) + new body.\n const header = original.slice(0, fm.bodyStart);\n const newContent = `${header}${newBody}`;\n\n await writeFile(absPath, newContent, 'utf8');\n\n // Update the in-memory index entry so subsequent searches reflect the change.\n const cached = ctx.notes.get(input.path);\n if (cached) {\n cached.body = newBody;\n cached.raw = newContent;\n }\n\n return { path: input.path, bytesWritten: Buffer.byteLength(newContent, 'utf8') };\n}\n","import { parse as parseYaml } from 'yaml';\n\nexport interface FrontmatterResult {\n /** true if the note starts with a YAML block delimited by `---` lines. */\n present: boolean;\n /** Parsed object (null if not present or parse error or block empty). */\n data: Record<string, unknown> | null;\n /** Distinct top-level keys (empty if absent or malformed). */\n keys: string[];\n /** True if a `---` block exists but yaml.parse threw. */\n malformed: boolean;\n /** Byte offset where the body (post-frontmatter) starts. */\n bodyStart: number;\n /** Body text, frontmatter stripped. */\n body: string;\n}\n\n/**\n * Split a note into frontmatter + body without round-tripping through any\n * YAML stringifier. We need to know the *bytes* of the FM block so writers\n * can be byte-faithful — that's the write-safety contract.\n *\n * Obsidian's rule: a YAML block is present iff the file starts with `---`\n * on its own line (no leading whitespace, no BOM stripped silently — though\n * we tolerate a leading BOM since Windows users exist).\n */\nexport function parseFrontmatter(text: string): FrontmatterResult {\n let offset = 0;\n if (text.charCodeAt(0) === 0xfeff) offset = 1; // BOM\n\n // Must start with `---\\n` or `---\\r\\n`\n const head = text.slice(offset, offset + 4);\n if (head !== '---\\n' && text.slice(offset, offset + 5) !== '---\\r\\n') {\n return {\n present: false,\n data: null,\n keys: [],\n malformed: false,\n bodyStart: 0,\n body: text,\n };\n }\n const fmStart = offset + (head === '---\\n' ? 4 : 5);\n\n // Find closing `\\n---\\n` (or CRLF variant) — Obsidian requires `---` on its own line.\n // We search line-by-line from fmStart.\n const closeLF = text.indexOf('\\n---\\n', fmStart - 1);\n const closeCRLF = text.indexOf('\\r\\n---\\r\\n', fmStart - 1);\n let closeIdx = -1;\n let closeLen = 0;\n if (closeLF !== -1 && (closeCRLF === -1 || closeLF < closeCRLF)) {\n closeIdx = closeLF;\n closeLen = 5;\n } else if (closeCRLF !== -1) {\n closeIdx = closeCRLF;\n closeLen = 7;\n }\n if (closeIdx === -1) {\n // Unterminated frontmatter — count as malformed, treat whole file as body.\n return {\n present: true,\n data: null,\n keys: [],\n malformed: true,\n bodyStart: 0,\n body: text,\n };\n }\n\n const yamlText = text.slice(fmStart, closeIdx + 1); // include trailing \\n before ---\n const bodyStart = closeIdx + closeLen;\n const body = text.slice(bodyStart);\n\n let data: Record<string, unknown> | null = null;\n let malformed = false;\n try {\n const parsed = parseYaml(yamlText);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n data = parsed as Record<string, unknown>;\n } else {\n // YAML parsed but isn't a mapping — Obsidian also treats this as malformed-for-our-purposes.\n malformed = parsed !== null && parsed !== undefined;\n }\n } catch {\n malformed = true;\n }\n\n return {\n present: true,\n data,\n keys: data ? Object.keys(data) : [],\n malformed,\n bodyStart,\n body,\n };\n}\n","import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { stringify as stringifyYaml } from 'yaml';\nimport { z } from 'zod';\nimport type { ServerContext } from '../context.js';\nimport { buildDoc, upsertDoc } from '../index/doc.js';\n\nexport const CreateNoteInput = z.object({\n path: z\n .string()\n .describe(\n 'Vault-relative path for the new note, e.g. \"Daily Notes/2026-06-01.md\". Parent directories are created automatically.',\n ),\n content: z.string().default('').describe('Body content for the note.'),\n frontmatter: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Frontmatter key-value pairs to write as a YAML block at the top of the note.'),\n overwrite: z\n .boolean()\n .default(false)\n .describe('Overwrite an existing note. Defaults to false — throws if the note already exists.'),\n});\nexport type CreateNoteInput = z.input<typeof CreateNoteInput>;\n\nexport interface CreateNoteResult {\n path: string;\n bytesWritten: number;\n}\n\nexport async function createNote(\n ctx: ServerContext,\n input: CreateNoteInput,\n): Promise<CreateNoteResult> {\n const absPath = join(ctx.vaultRoot, input.path);\n if (!absPath.startsWith(ctx.vaultRoot)) {\n throw new Error(`Path outside vault: ${input.path}`);\n }\n\n if (!input.overwrite) {\n try {\n await access(absPath);\n throw new Error(`Note already exists: ${input.path}. Pass overwrite: true to replace it.`);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n }\n\n let raw = '';\n if (input.frontmatter && Object.keys(input.frontmatter).length > 0) {\n raw = `---\\n${stringifyYaml(input.frontmatter)}---\\n`;\n }\n raw += input.content ?? '';\n\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, raw, 'utf8');\n\n // Sync in-memory index so the new note is immediately searchable.\n upsertDoc(ctx, buildDoc(input.path, raw));\n\n return { path: input.path, bytesWritten: Buffer.byteLength(raw, 'utf8') };\n}\n","import { basename, extname } from 'node:path';\nimport { extractInlineTags, frontmatterTags } from '@seekstone/core/extract';\nimport { parseFrontmatter } from '@seekstone/core/frontmatter';\nimport type { IndexedNote } from './types.js';\n\n/** Build an IndexedNote from a vault-relative path and raw file content. */\nexport function buildDoc(relPath: string, raw: string): IndexedNote {\n const fm = parseFrontmatter(raw);\n const allTags = [...new Set([...extractInlineTags(fm.body), ...frontmatterTags(fm.data)])];\n const title =\n typeof fm.data?.title === 'string' && fm.data.title\n ? fm.data.title\n : basename(relPath, extname(relPath));\n return {\n id: relPath,\n title,\n body: fm.body,\n tags: allTags.join(' '),\n fmKeys: fm.keys.join(' '),\n raw,\n sizeBytes: Buffer.byteLength(raw, 'utf8'),\n mtimeMs: Date.now(),\n };\n}\n\n/** Upsert a document into the context index. Discards any stale entry first. */\nexport function upsertDoc(\n ctx: {\n index: { discard(id: string): void; add(doc: IndexedNote): void };\n notes: Map<string, IndexedNote>;\n },\n doc: IndexedNote,\n): void {\n if (ctx.notes.has(doc.id)) ctx.index.discard(doc.id);\n ctx.notes.set(doc.id, doc);\n ctx.index.add(doc);\n}\n","/**\n * Link / URL / tag extractors. These run on note *bodies* (post-frontmatter)\n * so frontmatter values don't double-count as inline content.\n *\n * Heuristics here intentionally lean toward \"what Obsidian sees\" rather than\n * full CommonMark parsing — we're profiling shape, not rendering. The regexes\n * are deliberately tolerant of code-fence content; counts are reported with\n * that caveat in the README.\n */\n\nexport interface Wikilink {\n /** Raw target before `#` and `|`. May include path segments. */\n target: string;\n /** Optional heading/block reference (`#heading` or `#^block`). */\n fragment: string | null;\n /** Optional display alias. */\n alias: string | null;\n}\n\nconst WIKILINK_RE = /\\[\\[([^\\]|#\\n]+)(#[^\\]|\\n]+)?(\\|[^\\]\\n]+)?\\]\\]/g;\nconst URL_RE = /https?:\\/\\/[^\\s<>\"')]+/g;\n// Obsidian inline tag: must start with letter or underscore, then word chars, /, -.\n// Must not be immediately preceded by `]` (avoids matching `]#` from links) or be\n// inside a numeric-only token like `#123` (Obsidian rejects pure-digit tags).\nconst INLINE_TAG_RE = /(?:^|[^\\w\\]/])#([A-Za-z_][\\w/-]*)/g;\n\nexport function extractWikilinks(body: string): Wikilink[] {\n const out: Wikilink[] = [];\n for (const m of body.matchAll(WIKILINK_RE)) {\n out.push({\n target: (m[1] ?? '').trim(),\n fragment: m[2] ? m[2].slice(1).trim() : null,\n alias: m[3] ? m[3].slice(1).trim() : null,\n });\n }\n return out;\n}\n\nexport function extractUrls(body: string): string[] {\n const out: string[] = [];\n for (const m of body.matchAll(URL_RE)) {\n // Strip trailing punctuation that almost certainly isn't part of the URL.\n out.push((m[0] ?? '').replace(/[.,;:!?)]+$/, ''));\n }\n return out;\n}\n\nexport function extractInlineTags(body: string): string[] {\n const out: string[] = [];\n for (const m of body.matchAll(INLINE_TAG_RE)) {\n if (m[1]) out.push(m[1]);\n }\n return out;\n}\n\n/**\n * Pull tags out of a parsed frontmatter object. Obsidian accepts `tags:` or\n * `tag:`, as either a string (space- or comma-separated) or a list.\n */\nexport function frontmatterTags(fm: Record<string, unknown> | null): string[] {\n if (!fm) return [];\n const raw = fm.tags ?? fm.tag;\n if (raw == null) return [];\n if (Array.isArray(raw)) {\n return raw.filter((t): t is string => typeof t === 'string').map((t) => t.replace(/^#/, ''));\n }\n if (typeof raw === 'string') {\n return raw\n .split(/[\\s,]+/)\n .filter(Boolean)\n .map((t) => t.replace(/^#/, ''));\n }\n return [];\n}\n","import { rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport type { ServerContext } from '../context.js';\n\nexport const DeleteNoteInput = z.object({\n path: z.string().describe('Vault-relative path of the note to delete.'),\n});\nexport type DeleteNoteInput = z.input<typeof DeleteNoteInput>;\n\nexport interface DeleteNoteResult {\n path: string;\n}\n\nexport async function deleteNote(\n ctx: ServerContext,\n input: DeleteNoteInput,\n): Promise<DeleteNoteResult> {\n const absPath = join(ctx.vaultRoot, input.path);\n if (!absPath.startsWith(ctx.vaultRoot)) {\n throw new Error(`Path outside vault: ${input.path}`);\n }\n\n // rm throws ENOENT if the file doesn't exist — let it propagate.\n await rm(absPath);\n\n if (ctx.notes.has(input.path)) {\n ctx.index.discard(input.path);\n ctx.notes.delete(input.path);\n }\n\n return { path: input.path };\n}\n","import { z } from 'zod';\nimport type { ServerContext } from '../context.js';\n\nexport const ListNotesInput = z.object({\n folder: z\n .string()\n .optional()\n .describe(\n 'Vault-relative folder prefix to list. Omit for all notes. Example: \"Daily Notes/2026\".',\n ),\n tag: z.string().optional().describe('Filter by tag. Include or omit the leading #.'),\n limit: z.number().int().min(1).max(500).default(100).describe('Maximum number of results.'),\n});\nexport type ListNotesInput = z.infer<typeof ListNotesInput>;\n\nexport interface NoteEntry {\n path: string;\n title: string;\n tags: string[];\n sizeBytes: number;\n}\n\nexport function listNotes(ctx: ServerContext, input: ListNotesInput): NoteEntry[] {\n const tag = input.tag?.replace(/^#/, '');\n const results: NoteEntry[] = [];\n\n for (const [path, note] of ctx.notes) {\n if (input.folder && !path.startsWith(input.folder)) continue;\n if (tag) {\n const noteTags = note.tags.split(' ').filter(Boolean);\n if (!noteTags.includes(tag)) continue;\n }\n results.push({\n path,\n title: note.title,\n tags: note.tags.split(' ').filter(Boolean),\n sizeBytes: note.sizeBytes,\n });\n if (results.length >= input.limit) break;\n }\n\n return results.sort((a, b) => a.path.localeCompare(b.path));\n}\n","import { access, mkdir, rename } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { z } from 'zod';\nimport type { ServerContext } from '../context.js';\nimport { buildDoc, upsertDoc } from '../index/doc.js';\n\nexport const MoveNoteInput = z.object({\n from: z.string().describe('Vault-relative path of the note to move.'),\n to: z.string().describe('Vault-relative destination path.'),\n overwrite: z\n .boolean()\n .default(false)\n .describe('Overwrite the destination if it exists. Defaults to false.'),\n});\nexport type MoveNoteInput = z.input<typeof MoveNoteInput>;\n\nexport interface MoveNoteResult {\n from: string;\n to: string;\n}\n\nexport async function moveNote(ctx: ServerContext, input: MoveNoteInput): Promise<MoveNoteResult> {\n const absFrom = join(ctx.vaultRoot, input.from);\n const absTo = join(ctx.vaultRoot, input.to);\n\n if (!absFrom.startsWith(ctx.vaultRoot)) throw new Error(`Path outside vault: ${input.from}`);\n if (!absTo.startsWith(ctx.vaultRoot)) throw new Error(`Path outside vault: ${input.to}`);\n\n try {\n await access(absFrom);\n } catch {\n throw new Error(`Note not found: ${input.from}`);\n }\n\n if (!input.overwrite) {\n try {\n await access(absTo);\n throw new Error(\n `Destination already exists: ${input.to}. Pass overwrite: true to replace it.`,\n );\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n }\n\n await mkdir(dirname(absTo), { recursive: true });\n await rename(absFrom, absTo);\n\n // Grab raw before removing the old entry.\n const raw = ctx.notes.get(input.from)?.raw ?? '';\n\n // Remove old index entry.\n if (ctx.notes.has(input.from)) {\n ctx.index.discard(input.from);\n ctx.notes.delete(input.from);\n }\n upsertDoc(ctx, buildDoc(input.to, raw));\n\n return { from: input.from, to: input.to };\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parseFrontmatter } from '@seekstone/core/frontmatter';\nimport { parseDocument } from 'yaml';\nimport { z } from 'zod';\nimport type { ServerContext } from '../context.js';\n\nexport const PatchFrontmatterInput = z.object({\n path: z.string().describe('Vault-relative path to the note.'),\n patch: z\n .record(z.string(), z.unknown())\n .describe(\n 'Key-value pairs to set in the frontmatter. Existing keys not in the patch are unchanged. Pass null as a value to remove a key.',\n ),\n});\nexport type PatchFrontmatterInput = z.infer<typeof PatchFrontmatterInput>;\n\nexport interface PatchFrontmatterResult {\n path: string;\n keysChanged: string[];\n keysAdded: string[];\n keysRemoved: string[];\n}\n\n/**\n * Edit frontmatter values without reordering existing keys or changing\n * quote style. Uses yaml.parseDocument which preserves the CST, so round-trips\n * are byte-faithful for keys not touched by the patch.\n *\n * Patch semantics:\n * - Key present in patch, value non-null → set/update\n * - Key present in patch, value null → delete\n * - Key absent from patch → untouched\n *\n * If the note has no frontmatter, a new `---` block is created at the top.\n */\nexport async function patchFrontmatter(\n ctx: ServerContext,\n input: PatchFrontmatterInput,\n): Promise<PatchFrontmatterResult> {\n const absPath = join(ctx.vaultRoot, input.path);\n if (!absPath.startsWith(ctx.vaultRoot)) {\n throw new Error(`Path outside vault: ${input.path}`);\n }\n\n const original = await readFile(absPath, 'utf8');\n const fm = parseFrontmatter(original);\n\n const keysChanged: string[] = [];\n const keysAdded: string[] = [];\n const keysRemoved: string[] = [];\n\n let newContent: string;\n\n if (!fm.present) {\n // No existing FM — create a minimal block with just the patch keys.\n const doc = parseDocument('');\n for (const [k, v] of Object.entries(input.patch)) {\n if (v !== null) {\n doc.set(k, v);\n keysAdded.push(k);\n }\n }\n newContent = `---\\n${doc.toString()}---\\n${original}`;\n } else {\n // Parse the existing FM block preserving source tokens.\n const opensWithCRLF = original.startsWith('---\\r\\n');\n const openLen = opensWithCRLF ? 5 : 4;\n const closeLen = opensWithCRLF ? 7 : 5;\n const yamlText = original.slice(openLen, fm.bodyStart - closeLen);\n\n const doc = parseDocument(yamlText, { keepSourceTokens: true });\n\n for (const [k, v] of Object.entries(input.patch)) {\n const existing = doc.has(k);\n if (v === null) {\n if (existing) {\n doc.delete(k);\n keysRemoved.push(k);\n }\n } else if (existing) {\n doc.set(k, v);\n keysChanged.push(k);\n } else {\n doc.set(k, v);\n keysAdded.push(k);\n }\n }\n\n const head = opensWithCRLF ? '---\\r\\n' : '---\\n';\n const tail = opensWithCRLF ? '---\\r\\n' : '---\\n';\n newContent = `${head}${doc.toString()}${tail}${fm.body}`;\n }\n\n await writeFile(absPath, newContent, 'utf8');\n\n // Update in-memory cache.\n const cached = ctx.notes.get(input.path);\n if (cached) {\n const updated = parseFrontmatter(newContent);\n cached.raw = newContent;\n cached.fmKeys = updated.keys.join(' ');\n }\n\n return { path: input.path, keysChanged, keysAdded, keysRemoved };\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport type { ServerContext } from '../context.js';\n\nexport const ReadNoteInput = z.object({\n path: z.string().describe('Vault-relative path to the note, e.g. \"Daily Notes/2026-05-29.md\".'),\n});\nexport type ReadNoteInput = z.infer<typeof ReadNoteInput>;\n\nexport interface ReadNoteResult {\n path: string;\n content: string;\n sizeBytes: number;\n}\n\nexport async function readNote(ctx: ServerContext, input: ReadNoteInput): Promise<ReadNoteResult> {\n // Prevent path traversal — the path must not escape the vault root.\n const absPath = join(ctx.vaultRoot, input.path);\n if (!absPath.startsWith(ctx.vaultRoot)) {\n throw new Error(`Path outside vault: ${input.path}`);\n }\n\n const content = await readFile(absPath, 'utf8');\n return {\n path: input.path,\n content,\n sizeBytes: Buffer.byteLength(content, 'utf8'),\n };\n}\n","import { z } from 'zod';\nimport type { ServerContext } from '../context.js';\nimport { extractExcerpt } from '../index/excerpt.js';\nimport type { SearchHit } from '../index/types.js';\n\nexport const SearchInput = z.object({\n query: z.string().min(1).describe('Search query. Supports fuzzy matching and prefix search.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Maximum number of results to return.'),\n folder: z\n .string()\n .optional()\n .describe('Restrict results to notes under this vault-relative folder prefix.'),\n tag: z.string().optional().describe('Restrict results to notes containing this tag.'),\n});\nexport type SearchInput = z.infer<typeof SearchInput>;\n\nexport function search(ctx: ServerContext, input: SearchInput): SearchHit[] {\n const results = ctx.index.search(input.query, {\n boost: { title: 3, tags: 2, body: 1 },\n fuzzy: 0.2,\n prefix: true,\n });\n\n const terms = input.query\n .toLowerCase()\n .split(/\\s+/)\n .filter((t) => t.length > 1);\n\n const hits: SearchHit[] = [];\n for (const r of results) {\n if (input.folder && !r.id.startsWith(input.folder)) continue;\n if (input.tag) {\n const note = ctx.notes.get(r.id);\n if (!note) continue;\n const noteTags = note.tags.split(' ');\n if (!noteTags.some((t) => t === input.tag || t === input.tag?.replace(/^#/, ''))) continue;\n }\n\n const note = ctx.notes.get(r.id);\n const body = note?.body ?? '';\n const noteTags = note?.tags ? note.tags.split(' ').filter(Boolean) : [];\n\n hits.push({\n path: r.id,\n title: r.title as string,\n score: r.score,\n excerpt: extractExcerpt(body, terms),\n tags: noteTags,\n });\n\n if (hits.length >= input.limit) break;\n }\n\n return hits;\n}\n","/**\n * Extract a short excerpt centred on the first occurrence of any query term.\n * Returns at most `maxLen` characters with leading/trailing ellipsis as needed.\n *\n * This is the core context-tax reduction: instead of returning the full note\n * (~7 KB mean, ~70 KB p99), we return ~200 chars of relevant context.\n */\nexport function extractExcerpt(text: string, terms: string[], maxLen = 200): string {\n const lower = text.toLowerCase();\n let bestIdx = -1;\n\n for (const term of terms) {\n const idx = lower.indexOf(term.toLowerCase());\n if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) {\n bestIdx = idx;\n }\n }\n\n if (bestIdx === -1) {\n // No term found in body (hit may have been in title/tags) — return the start.\n return truncate(text.trimStart(), maxLen);\n }\n\n const half = Math.floor(maxLen / 2);\n const start = Math.max(0, bestIdx - half);\n const end = Math.min(text.length, bestIdx + half + (terms[0]?.length ?? 0));\n const slice = text.slice(start, end).trim();\n\n return `${start > 0 ? '…' : ''}${slice}${end < text.length ? '…' : ''}`;\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max) return s;\n return `${s.slice(0, max).trimEnd()}…`;\n}\n","import type { ServerContext } from './context.js';\nimport type { Logger } from './log.js';\nimport { AppendNoteInput, appendNote } from './tools/append_note.js';\nimport { CreateNoteInput, createNote } from './tools/create_note.js';\nimport { DeleteNoteInput, deleteNote } from './tools/delete_note.js';\nimport { ListNotesInput, listNotes } from './tools/list_notes.js';\nimport { MoveNoteInput, moveNote } from './tools/move_note.js';\nimport { PatchFrontmatterInput, patchFrontmatter } from './tools/patch_frontmatter.js';\nimport { ReadNoteInput, readNote } from './tools/read_note.js';\nimport { SearchInput, search } from './tools/search.js';\n\nexport type ToolResult = {\n content: { type: 'text'; text: string }[];\n isError?: boolean;\n};\n\n/** Tool names this dispatcher handles — kept in sync with the server's tool list. */\nexport const HANDLED_TOOLS = [\n 'search',\n 'read_note',\n 'list_notes',\n 'create_note',\n 'delete_note',\n 'move_note',\n 'append_note',\n 'patch_frontmatter',\n] as const;\n\n// Metadata-safe keys: logged at info. Note content (`content`, `frontmatter`,\n// `patch`) and the raw `query` string are intentionally excluded — they only\n// reach logs via the debug-level `args` dump.\nconst META_KEYS = ['path', 'from', 'to', 'folder', 'tag', 'limit', 'overwrite'] as const;\n\nfunction safeMeta(args: unknown): Record<string, unknown> {\n if (typeof args !== 'object' || args === null) return {};\n const a = args as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const k of META_KEYS) {\n if (a[k] !== undefined) out[k] = a[k];\n }\n if (typeof a.query === 'string') out.queryLen = a.query.length; // not the query itself\n return out;\n}\n\nfunction resultBytes(r: ToolResult): number {\n let n = 0;\n for (const c of r.content) n += Buffer.byteLength(c.text);\n return n;\n}\n\n/**\n * Route a tool call to its handler, logging the outcome. Returns an MCP tool\n * result; failures are converted to `{ isError: true }` (never thrown) so the\n * transport stays alive, and are logged at `error` with a stack.\n */\nexport async function dispatch(\n ctx: ServerContext,\n name: string,\n args: unknown,\n log: Logger,\n): Promise<ToolResult> {\n const start = performance.now();\n const meta = safeMeta(args);\n log.debug('tool start', { tool: name, args }); // full args (may include content) — debug only\n try {\n const result = await run(ctx, name, args);\n const durationMs = Math.round((performance.now() - start) * 100) / 100;\n log.info('tool ok', { tool: name, durationMs, resultBytes: resultBytes(result), ...meta });\n return result;\n } catch (err) {\n const durationMs = Math.round((performance.now() - start) * 100) / 100;\n const message = err instanceof Error ? err.message : String(err);\n log.error('tool error', {\n tool: name,\n durationMs,\n error: message,\n stack: err instanceof Error ? err.stack : undefined,\n ...meta,\n });\n return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };\n }\n}\n\nasync function run(ctx: ServerContext, name: string, args: unknown): Promise<ToolResult> {\n switch (name) {\n case 'search': {\n const input = SearchInput.parse(args);\n const hits = search(ctx, input);\n return { content: [{ type: 'text', text: JSON.stringify(hits, null, 2) }] };\n }\n case 'read_note': {\n const input = ReadNoteInput.parse(args);\n const result = await readNote(ctx, input);\n return { content: [{ type: 'text', text: result.content }] };\n }\n case 'list_notes': {\n const input = ListNotesInput.parse(args);\n const entries = listNotes(ctx, input);\n return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] };\n }\n case 'create_note': {\n const input = CreateNoteInput.parse(args);\n const result = await createNote(ctx, input);\n return {\n content: [{ type: 'text', text: `Created ${result.path} (${result.bytesWritten} bytes).` }],\n };\n }\n case 'delete_note': {\n const input = DeleteNoteInput.parse(args);\n await deleteNote(ctx, input);\n return { content: [{ type: 'text', text: `Deleted ${input.path}.` }] };\n }\n case 'move_note': {\n const input = MoveNoteInput.parse(args);\n const result = await moveNote(ctx, input);\n return { content: [{ type: 'text', text: `Moved ${result.from} → ${result.to}.` }] };\n }\n case 'append_note': {\n const input = AppendNoteInput.parse(args);\n const result = await appendNote(ctx, input);\n return {\n content: [\n { type: 'text', text: `Appended ${result.bytesWritten} bytes to ${result.path}.` },\n ],\n };\n }\n case 'patch_frontmatter': {\n const input = PatchFrontmatterInput.parse(args);\n const result = await patchFrontmatter(ctx, input);\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\n }\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { basename, extname } from 'node:path';\nimport { extractInlineTags, frontmatterTags } from '@seekstone/core/extract';\nimport { parseFrontmatter } from '@seekstone/core/frontmatter';\nimport { walkVault } from '@seekstone/core/walk';\nimport MiniSearch from 'minisearch';\nimport type { IndexedNote } from './types.js';\n\nexport type VaultIndex = MiniSearch<IndexedNote>;\n\nexport interface BuildResult {\n index: VaultIndex;\n notes: Map<string, IndexedNote>;\n buildMs: number;\n}\n\nexport async function buildIndex(vaultRoot: string): Promise<BuildResult> {\n const t0 = Date.now();\n\n const index = new MiniSearch<IndexedNote>({\n idField: 'id',\n fields: ['title', 'body', 'tags', 'fmKeys'],\n storeFields: ['id', 'title', 'tags', 'sizeBytes', 'mtimeMs'],\n searchOptions: {\n boost: { title: 3, tags: 2, body: 1 },\n fuzzy: 0.2,\n prefix: true,\n },\n });\n\n const entries = await walkVault(vaultRoot);\n const noteEntries = entries.filter((e) => e.kind === 'note');\n\n const docs: IndexedNote[] = [];\n const notes = new Map<string, IndexedNote>();\n\n await Promise.all(\n noteEntries.map(async (entry) => {\n try {\n const raw = await readFile(entry.absPath, 'utf8');\n const fm = parseFrontmatter(raw);\n const inlineTags = extractInlineTags(fm.body);\n const fmTags = frontmatterTags(fm.data);\n const allTags = [...new Set([...inlineTags, ...fmTags])];\n\n const title =\n typeof fm.data?.title === 'string' && fm.data.title\n ? fm.data.title\n : basename(entry.relPath, extname(entry.relPath));\n\n const doc: IndexedNote = {\n id: entry.relPath,\n title,\n body: fm.body,\n tags: allTags.join(' '),\n fmKeys: fm.keys.join(' '),\n raw,\n sizeBytes: entry.sizeBytes,\n mtimeMs: entry.mtimeMs,\n };\n docs.push(doc);\n notes.set(entry.relPath, doc);\n } catch {\n // Unreadable note — skip silently; index what we can.\n }\n }),\n );\n\n index.addAll(docs);\n\n return { index, notes, buildMs: Date.now() - t0 };\n}\n","import { stat } from 'node:fs/promises';\nimport { extname, join, relative, sep } from 'node:path';\nimport fg from 'fast-glob';\n\n/**\n * Categories we care about. Anything else falls into `other`.\n * Excalidraw saves as `.excalidraw` (sometimes `.excalidraw.md` — handled in classify()).\n */\nexport type FileKind =\n | 'note'\n | 'image'\n | 'pdf'\n | 'excalidraw'\n | 'canvas'\n | 'video'\n | 'audio'\n | 'other';\n\nexport interface FileEntry {\n absPath: string;\n relPath: string;\n topDir: string;\n ext: string;\n kind: FileKind;\n sizeBytes: number;\n mtimeMs: number;\n}\n\nconst IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.heic']);\nconst VIDEO_EXTS = new Set(['.mp4', '.mov', '.webm', '.mkv', '.avi']);\nconst AUDIO_EXTS = new Set(['.mp3', '.m4a', '.wav', '.flac', '.ogg']);\n\n/**\n * Obsidian's own dot-directories plus VCS noise. We deliberately don't ignore\n * dotfiles at the *file* level — users sometimes commit `.gitkeep` etc. — but\n * any top-level dotted directory is treated as non-vault content.\n */\nconst IGNORE_GLOBS = [\n '**/.obsidian/**',\n '**/.trash/**',\n '**/.git/**',\n '**/.DS_Store',\n '**/node_modules/**',\n];\n\nexport function classify(relPath: string): FileKind {\n // Excalidraw plugin sometimes writes `.excalidraw.md` — keep those as notes\n // (they still have YAML frontmatter and links), but pure `.excalidraw` is canvas-like.\n if (relPath.endsWith('.excalidraw.md')) return 'note';\n if (relPath.endsWith('.excalidraw')) return 'excalidraw';\n const ext = extname(relPath).toLowerCase();\n if (ext === '.md') return 'note';\n if (ext === '.canvas') return 'canvas';\n if (ext === '.pdf') return 'pdf';\n if (IMAGE_EXTS.has(ext)) return 'image';\n if (VIDEO_EXTS.has(ext)) return 'video';\n if (AUDIO_EXTS.has(ext)) return 'audio';\n return 'other';\n}\n\nexport async function walkVault(vaultRoot: string): Promise<FileEntry[]> {\n // We use fast-glob with `stats: true` so we can avoid an extra stat() round per file.\n const matches = await fg('**/*', {\n cwd: vaultRoot,\n absolute: true,\n onlyFiles: true,\n followSymbolicLinks: false,\n dot: true, // include dotfiles so the ignore globs are the only gate\n ignore: IGNORE_GLOBS,\n stats: true,\n suppressErrors: true,\n });\n\n const entries: FileEntry[] = [];\n for (const m of matches) {\n // fast-glob with stats: true returns objects; without, strings. Narrow here.\n const absPath = typeof m === 'string' ? m : m.path;\n const stats = typeof m === 'string' ? await stat(absPath) : m.stats;\n if (!stats) continue;\n const relPath = relative(vaultRoot, absPath);\n const topDir = relPath.split(sep)[0] ?? '';\n entries.push({\n absPath,\n relPath,\n topDir,\n ext: extname(relPath).toLowerCase(),\n kind: classify(relPath),\n sizeBytes: stats.size,\n mtimeMs: stats.mtimeMs,\n });\n }\n return entries;\n}\n\nexport function _testJoin(...parts: string[]): string {\n return join(...parts);\n}\n","import { appendFileSync, renameSync, statSync } from 'node:fs';\n\n/**\n * A tiny zero-dependency leveled logger for the MCP server.\n *\n * Two sinks, never stdout (stdout carries the JSON-RPC stdio transport):\n * - stderr: concise human-readable lines, always on (Claude Desktop captures it).\n * - file: JSON-lines, opt-in via SEEKSTONE_LOG_FILE, size-rotated.\n *\n * Privacy: callers pass metadata fields at info/warn/error and only attach note\n * content (bodies, frontmatter values, raw queries) at `debug`. The logger does\n * not inspect or redact fields — that contract lives at the call sites.\n *\n * Note: file writes use appendFileSync. File logging is opt-in and the per-line\n * cost is sub-millisecond, comfortably inside tool latency budgets; the default\n * (stderr-only) path does no disk I/O. An async write queue is a future option.\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug';\n\nconst ORDER: readonly LogLevel[] = ['error', 'warn', 'info', 'debug'] as const;\nconst DEFAULT_MAX_SIZE = 5 * 1024 * 1024;\n\nexport interface Logger {\n readonly level: LogLevel;\n error(msg: string, fields?: Record<string, unknown>): void;\n warn(msg: string, fields?: Record<string, unknown>): void;\n info(msg: string, fields?: Record<string, unknown>): void;\n debug(msg: string, fields?: Record<string, unknown>): void;\n}\n\nexport interface LoggerOptions {\n /** Env to read SEEKSTONE_LOG_* from. Defaults to process.env. */\n env?: Record<string, string | undefined>;\n /** stderr sink — injectable for tests. Defaults to process.stderr. Never stdout. */\n stderr?: (line: string) => void;\n /** Clock — injectable for deterministic tests. */\n now?: () => Date;\n}\n\nfunction parseLevel(raw: string | undefined, onBad: (m: string) => void): LogLevel {\n if (raw === undefined || raw === '') return 'info';\n const v = raw.trim().toLowerCase();\n for (const lvl of ORDER) {\n if (lvl === v) return lvl;\n }\n onBad(`invalid SEEKSTONE_LOG_LEVEL=${JSON.stringify(raw)}; using \"info\"`);\n return 'info';\n}\n\nfunction parseSize(raw: string | undefined): number {\n if (raw === undefined) return DEFAULT_MAX_SIZE;\n const m = /^(\\d+(?:\\.\\d+)?)(b|kb|mb|gb)?$/i.exec(raw.trim());\n if (m === null) return DEFAULT_MAX_SIZE;\n const num = Number(m[1]);\n const unit = (m[2] ?? 'b').toLowerCase();\n const mult = unit === 'gb' ? 1e9 : unit === 'mb' ? 1e6 : unit === 'kb' ? 1e3 : 1;\n return Math.max(1, Math.floor(num * mult));\n}\n\nfunction fmtFields(fields: Record<string, unknown>): string {\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) continue;\n parts.push(`${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const env = opts.env ?? process.env;\n // Default sink is stderr — deliberately never process.stdout.\n const stderr = opts.stderr ?? ((line: string): void => void process.stderr.write(line));\n const now = opts.now ?? ((): Date => new Date());\n\n const level = parseLevel(env.SEEKSTONE_LOG_LEVEL, (m) =>\n stderr(`WARN ${now().toISOString()} ${m}\\n`),\n );\n const threshold = ORDER.indexOf(level);\n\n const filePath = env.SEEKSTONE_LOG_FILE;\n const maxSize = parseSize(env.SEEKSTONE_LOG_MAX_SIZE);\n let fileOk = false;\n let bytes = 0;\n if (filePath !== undefined && filePath !== '') {\n try {\n try {\n bytes = statSync(filePath).size;\n } catch {\n bytes = 0;\n }\n appendFileSync(filePath, ''); // verify writable / create\n fileOk = true;\n } catch (err) {\n const m = err instanceof Error ? err.message : String(err);\n stderr(\n `WARN ${now().toISOString()} cannot write log file ${filePath}: ${m}; file logging disabled\\n`,\n );\n }\n }\n\n function writeFile(record: Record<string, unknown>): void {\n if (!fileOk || filePath === undefined) return;\n const line = `${JSON.stringify(record)}\\n`;\n const len = Buffer.byteLength(line);\n try {\n if (bytes > 0 && bytes + len > maxSize) {\n renameSync(filePath, `${filePath}.1`);\n bytes = 0;\n }\n appendFileSync(filePath, line);\n bytes += len;\n } catch {\n fileOk = false; // stop trying; logging must never crash the request path\n }\n }\n\n function emit(lvl: LogLevel, msg: string, fields?: Record<string, unknown>): void {\n if (ORDER.indexOf(lvl) > threshold) return; // below threshold: do no work\n const ts = now().toISOString();\n const f = fields ?? {};\n stderr(`${lvl.toUpperCase().padEnd(5)} ${ts} ${msg}${fmtFields(f)}\\n`);\n writeFile({ t: ts, level: lvl, msg, ...f });\n }\n\n return {\n level,\n error: (m, f) => emit('error', m, f),\n warn: (m, f) => emit('warn', m, f),\n info: (m, f) => emit('info', m, f),\n debug: (m, f) => emit('debug', m, f),\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { join, relative, sep } from 'node:path';\nimport chokidar from 'chokidar';\nimport type { ServerContext } from './context.js';\nimport { buildDoc, upsertDoc } from './index/doc.js';\nimport type { Logger } from './log.js';\n\n/**\n * Watch the vault for .md changes and keep the in-memory index in sync.\n *\n * Uses chokidar rather than `fs.watch(..., { recursive: true })`: recursive\n * native watching is unsupported on Linux, where fs.watch silently misses\n * nested changes. chokidar gives reliable, recursive, cross-platform events.\n *\n * Returns `stop()` to tear down the watcher and a `ready` promise that\n * resolves once chokidar has finished its initial scan and is live.\n */\nexport interface WatcherHandle {\n stop: () => void;\n ready: Promise<void>;\n}\n\nexport interface WatcherOptions {\n /**\n * Stat-poll instead of using native OS file events. Slower but reliable on\n * filesystems where native events are unreliable (network mounts, WSL, some\n * containers) and deterministic under load. Defaults to false, or true when\n * SEEKSTONE_WATCH_POLL=1.\n */\n usePolling?: boolean;\n}\n\nexport function startWatcher(\n ctx: ServerContext,\n log?: Logger,\n opts?: WatcherOptions,\n): WatcherHandle {\n const usePolling = opts?.usePolling ?? process.env.SEEKSTONE_WATCH_POLL === '1';\n // Index keys are forward-slash vault-relative paths on every platform.\n const toRel = (abs: string): string => relative(ctx.vaultRoot, abs).split(sep).join('/');\n\n async function upsert(relPath: string, op: 'add' | 'change'): Promise<void> {\n try {\n const raw = await readFile(join(ctx.vaultRoot, relPath), 'utf8');\n upsertDoc(ctx, buildDoc(relPath, raw));\n log?.debug('index updated', { path: relPath, op });\n } catch {\n // File vanished between event and read — ignore; an unlink will follow.\n }\n }\n\n function remove(relPath: string): void {\n if (ctx.notes.has(relPath)) {\n ctx.index.discard(relPath);\n ctx.notes.delete(relPath);\n log?.debug('index removed', { path: relPath, op: 'delete' });\n }\n }\n\n const onUpsert = (op: 'add' | 'change') => (abs: string) => {\n const relPath = toRel(abs);\n if (relPath.endsWith('.md')) void upsert(relPath, op);\n };\n\n const watcher = chokidar.watch(ctx.vaultRoot, {\n // The startup index build already covers existing files.\n ignoreInitial: true,\n usePolling,\n interval: 50,\n binaryInterval: 100,\n // Skip dot-dirs (.obsidian, .git, .trash, …) regardless of where the vault\n // lives — matched on the path relative to the vault root.\n ignored: (p: string) =>\n relative(ctx.vaultRoot, p)\n .split(sep)\n .some((seg) => seg.startsWith('.')),\n });\n\n watcher\n .on('add', onUpsert('add'))\n .on('change', onUpsert('change'))\n .on('unlink', (abs: string) => {\n const relPath = toRel(abs);\n if (relPath.endsWith('.md')) remove(relPath);\n });\n\n const ready = new Promise<void>((resolve) => {\n watcher.once('ready', () => resolve());\n });\n\n return {\n stop: () => void watcher.close(),\n ready,\n };\n}\n"],"mappings":"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","names":["ctx","writeFile","join","z","ctx","z","ctx","join","writeFile","join","z","ctx","z","ctx","access","mkdir","dirname","join","z","z","ctx","join","access","mkdir","dirname","readFile","writeFile","join","z","ctx","join","readFile","writeFile","readFile","join","z","ctx","z","z","ctx","note","noteTags","ctx","log","readFile","basename","extname","extname","join","vaultRoot","vaultRoot","index","notes","readFile","basename","extname","writeFile","readFile","join","relative","sep","ctx","log","relative","sep","readFile","join","watcher"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "seekstone",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "Seekstone MCP server — filesystem-direct Obsidian vault access, low context-tax.",
6
+ "keywords": [
7
+ "obsidian",
8
+ "mcp",
9
+ "model-context-protocol",
10
+ "claude",
11
+ "markdown",
12
+ "notes",
13
+ "vault",
14
+ "knowledge-base"
15
+ ],
16
+ "license": "MIT",
17
+ "author": "Shaq Mughal",
18
+ "homepage": "https://github.com/shaqmughal/seekstone#readme",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/shaqmughal/seekstone.git",
22
+ "directory": "packages/server"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/shaqmughal/seekstone/issues"
26
+ },
27
+ "engines": {
28
+ "node": ">=22"
29
+ },
30
+ "bin": {
31
+ "seekstone": "./dist/index.js"
32
+ },
33
+ "main": "./dist/index.js",
34
+ "files": ["dist"],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "start": "tsx src/index.ts",
40
+ "build": "tsup",
41
+ "prepack": "tsup",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.12.0",
47
+ "chokidar": "^4.0.3",
48
+ "fast-glob": "^3.3.2",
49
+ "minisearch": "^7.1.1",
50
+ "yaml": "^2.6.1",
51
+ "zod": "^3.24.0"
52
+ },
53
+ "devDependencies": {
54
+ "@seekstone/core": "*"
55
+ }
56
+ }