@mauricio.wolff/mcp-obsidian 0.4.1 → 0.5.1

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.
@@ -0,0 +1,105 @@
1
+ import { join } from 'path';
2
+ import { readFile, readdir } from 'node:fs/promises';
3
+ export class SearchService {
4
+ vaultPath;
5
+ pathFilter;
6
+ constructor(vaultPath, pathFilter) {
7
+ this.vaultPath = vaultPath;
8
+ this.pathFilter = pathFilter;
9
+ }
10
+ async search(params) {
11
+ const { query, limit = 5, searchContent = true, searchFrontmatter = false, caseSensitive = false } = params;
12
+ if (!query || query.trim().length === 0) {
13
+ throw new Error('Search query cannot be empty');
14
+ }
15
+ const results = [];
16
+ const maxLimit = Math.min(limit, 20);
17
+ // Recursively find all .md files
18
+ const markdownFiles = await this.findMarkdownFiles(this.vaultPath);
19
+ for (const fullPath of markdownFiles) {
20
+ // Convert absolute path back to relative path
21
+ const relativePath = fullPath.substring(this.vaultPath.length + 1).replace(/\\/g, '/');
22
+ if (!this.pathFilter.isAllowed(relativePath))
23
+ continue;
24
+ if (results.length >= maxLimit)
25
+ break;
26
+ try {
27
+ const content = await readFile(fullPath, 'utf-8');
28
+ let searchableText = '';
29
+ // Prepare search text based on options
30
+ if (searchContent && searchFrontmatter) {
31
+ searchableText = content;
32
+ }
33
+ else if (searchContent) {
34
+ // Remove frontmatter from search
35
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
36
+ searchableText = frontmatterMatch ? content.slice(frontmatterMatch[0].length) : content;
37
+ }
38
+ else if (searchFrontmatter) {
39
+ // Search only frontmatter
40
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
41
+ searchableText = frontmatterMatch ? frontmatterMatch[1] || '' : '';
42
+ }
43
+ const searchIn = caseSensitive ? searchableText : searchableText.toLowerCase();
44
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
45
+ const index = searchIn.indexOf(searchQuery);
46
+ if (index !== -1) {
47
+ // Extract excerpt around first match
48
+ const excerptStart = Math.max(0, index - 50);
49
+ const excerptEnd = Math.min(searchableText.length, index + searchQuery.length + 50);
50
+ let excerpt = searchableText.slice(excerptStart, excerptEnd).trim();
51
+ // Add ellipsis if excerpt is truncated
52
+ if (excerptStart > 0)
53
+ excerpt = '...' + excerpt;
54
+ if (excerptEnd < searchableText.length)
55
+ excerpt = excerpt + '...';
56
+ // Count total matches
57
+ let matchCount = 0;
58
+ let searchIndex = 0;
59
+ while ((searchIndex = searchIn.indexOf(searchQuery, searchIndex)) !== -1) {
60
+ matchCount++;
61
+ searchIndex += searchQuery.length;
62
+ }
63
+ // Find line number of first match
64
+ const lines = searchableText.slice(0, index).split('\n');
65
+ const lineNumber = lines.length;
66
+ // Extract title from filename
67
+ const title = relativePath.split('/').pop()?.replace(/\.md$/, '') || relativePath;
68
+ results.push({
69
+ path: relativePath,
70
+ title: title,
71
+ excerpt: excerpt,
72
+ matchCount: matchCount,
73
+ lineNumber: lineNumber
74
+ });
75
+ }
76
+ }
77
+ catch (error) {
78
+ // Skip files that can't be read
79
+ continue;
80
+ }
81
+ }
82
+ return results;
83
+ }
84
+ async findMarkdownFiles(dirPath) {
85
+ const markdownFiles = [];
86
+ try {
87
+ const entries = await readdir(dirPath, { withFileTypes: true });
88
+ for (const entry of entries) {
89
+ const fullPath = join(dirPath, entry.name);
90
+ if (entry.isDirectory()) {
91
+ // Recursively search subdirectories
92
+ const subFiles = await this.findMarkdownFiles(fullPath);
93
+ markdownFiles.push(...subFiles);
94
+ }
95
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
96
+ markdownFiles.push(fullPath);
97
+ }
98
+ }
99
+ }
100
+ catch (error) {
101
+ // Skip directories that can't be read
102
+ }
103
+ return markdownFiles;
104
+ }
105
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
2
  "name": "@mauricio.wolff/mcp-obsidian",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Lightweight MCP server for safe Obsidian vault access",
5
5
  "author": "bitbonsai",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "main": "server.ts",
9
9
  "bin": {
10
- "mcp-obsidian": "./server.ts",
11
- "@mauricio.wolff/mcp-obsidian": "./server.ts"
10
+ "mcp-obsidian": "./dist/server.js",
11
+ "@mauricio.wolff/mcp-obsidian": "./dist/server.js"
12
12
  },
13
13
  "files": [
14
- "server.ts",
15
- "src/**/*",
14
+ "dist/**/*",
16
15
  "README.md",
17
16
  "LICENSE"
18
17
  ],
19
18
  "scripts": {
20
- "start": "bun run server.ts",
21
- "test": "bun test",
22
- "test:watch": "bun test --watch",
23
- "prepublishOnly": "bun test",
24
- "prepack": "bun install",
19
+ "start": "tsx server.ts",
20
+ "build": "tsc --project tsconfig.build.json",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "prepublishOnly": "npm run build && npm test",
24
+ "prepack": "npm install",
25
25
  "publish:dry": "npm publish --dry-run",
26
26
  "publish:beta": "npm publish --tag beta",
27
27
  "publish:latest": "npm publish"
@@ -31,10 +31,15 @@
31
31
  "gray-matter": "^4.0.3"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/bun": "latest"
34
+ "@types/node": "^20.0.0",
35
+ "tsx": "^4.0.0",
36
+ "typescript": "^5.0.0",
37
+ "vitest": "^1.0.0",
38
+ "js-yaml": "^4.1.0",
39
+ "@types/js-yaml": "^4.0.0"
35
40
  },
36
41
  "engines": {
37
- "bun": ">=1.0.0"
42
+ "node": ">=18.0.0"
38
43
  },
39
44
  "repository": {
40
45
  "type": "git",
@@ -46,7 +51,7 @@
46
51
  "model-context-protocol",
47
52
  "claude",
48
53
  "ai",
49
- "bun",
54
+ "node",
50
55
  "filesystem",
51
56
  "frontmatter",
52
57
  "yaml"
package/server.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -13,7 +13,7 @@ import { SearchService } from "./src/search.js";
13
13
 
14
14
  const vaultPath = process.argv[2];
15
15
  if (!vaultPath) {
16
- console.error("Usage: bun server.ts /path/to/vault");
16
+ console.error("Usage: npx @mauricio.wolff/mcp-obsidian /path/to/vault");
17
17
  process.exit(1);
18
18
  }
19
19
 
@@ -25,7 +25,7 @@ const searchService = new SearchService(vaultPath, pathFilter);
25
25
 
26
26
  const server = new Server({
27
27
  name: "mcp-obsidian",
28
- version: "0.3.0"
28
+ version: "0.5.1"
29
29
  }, {
30
30
  capabilities: {
31
31
  tools: {},