@disco_trooper/apple-notes-mcp 1.2.0 → 1.4.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/README.md +136 -24
- package/package.json +13 -9
- package/src/config/claude.test.ts +47 -0
- package/src/config/claude.ts +106 -0
- package/src/config/constants.ts +11 -2
- package/src/config/paths.test.ts +40 -0
- package/src/config/paths.ts +86 -0
- package/src/db/arrow-fix.test.ts +101 -0
- package/src/db/lancedb.test.ts +209 -2
- package/src/db/lancedb.ts +373 -7
- package/src/embeddings/cache.test.ts +150 -0
- package/src/embeddings/cache.ts +204 -0
- package/src/embeddings/index.ts +21 -2
- package/src/embeddings/local.ts +61 -10
- package/src/embeddings/openrouter.ts +233 -11
- package/src/graph/export.test.ts +81 -0
- package/src/graph/export.ts +163 -0
- package/src/graph/extract.test.ts +90 -0
- package/src/graph/extract.ts +52 -0
- package/src/graph/queries.test.ts +156 -0
- package/src/graph/queries.ts +224 -0
- package/src/index.ts +376 -10
- package/src/notes/crud.test.ts +148 -3
- package/src/notes/crud.ts +250 -5
- package/src/notes/read.ts +83 -68
- package/src/search/chunk-indexer.test.ts +353 -0
- package/src/search/chunk-indexer.ts +254 -0
- package/src/search/chunk-search.test.ts +327 -0
- package/src/search/chunk-search.ts +298 -0
- package/src/search/indexer.ts +151 -109
- package/src/search/refresh.test.ts +173 -0
- package/src/search/refresh.ts +151 -0
- package/src/setup.ts +46 -67
- package/src/utils/chunker.test.ts +182 -0
- package/src/utils/chunker.ts +170 -0
- package/src/utils/content-filter.test.ts +225 -0
- package/src/utils/content-filter.ts +275 -0
- package/src/utils/runtime.test.ts +70 -0
- package/src/utils/runtime.ts +40 -0
package/README.md
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
# apple-notes-mcp
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@disco_trooper/apple-notes-mcp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.apple.com/macos/)
|
|
6
|
+
[](https://bun.sh)
|
|
7
|
+
[](https://modelcontextprotocol.io)
|
|
8
|
+
|
|
3
9
|
MCP server for Apple Notes with semantic search and CRUD operations. Claude searches, reads, creates, updates, and manages your Apple Notes through natural language.
|
|
4
10
|
|
|
5
11
|
## Features
|
|
6
12
|
|
|
13
|
+
- **Chunk-Based Search** - Long notes split into chunks for accurate matching (NEW!)
|
|
14
|
+
- **Query Caching** - 60x faster repeated searches (NEW!)
|
|
15
|
+
- **Knowledge Graph** - Tags, links, and related notes discovery (NEW!)
|
|
16
|
+
- **Hybrid Search** - Vector + keyword search with Reciprocal Rank Fusion
|
|
7
17
|
- **Semantic Search** - Find notes by meaning, not keywords
|
|
8
|
-
- **Hybrid Search** - Combine vector and keyword search for better results
|
|
9
18
|
- **Full CRUD** - Create, read, update, delete, and move notes
|
|
10
19
|
- **Incremental Indexing** - Re-embed only changed notes
|
|
11
|
-
- **Dual Embedding
|
|
12
|
-
- **Claude Code Integration** - Works with Claude Code CLI
|
|
20
|
+
- **Dual Embedding** - Local HuggingFace or OpenRouter API
|
|
13
21
|
|
|
14
|
-
##
|
|
22
|
+
## What's New in 1.4
|
|
15
23
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
24
|
+
- **Smart Refresh** - Search auto-reindexes changed notes. No manual `index-notes` needed.
|
|
25
|
+
- **Batch Operations** - Delete or move multiple notes by title or folder.
|
|
26
|
+
- **Purge Index** - Clear all indexed data when switching models or fixing corruption.
|
|
27
|
+
- **Parent Document Retriever** - Splits long notes into 500-char chunks with 100-char overlap.
|
|
28
|
+
- **60x faster cached queries** - Query embedding cache eliminates redundant API calls.
|
|
29
|
+
- **4-6x faster indexing** - Parallel processing and batch embeddings.
|
|
19
30
|
|
|
20
31
|
## Installation
|
|
21
32
|
|
|
@@ -23,34 +34,43 @@ MCP server for Apple Notes with semantic search and CRUD operations. Claude sear
|
|
|
23
34
|
|
|
24
35
|
```bash
|
|
25
36
|
npm install -g @disco_trooper/apple-notes-mcp
|
|
37
|
+
apple-notes-mcp
|
|
26
38
|
```
|
|
27
39
|
|
|
40
|
+
The setup wizard guides you through:
|
|
41
|
+
1. Choosing your embedding provider (local or OpenRouter)
|
|
42
|
+
2. Configuring API keys if needed
|
|
43
|
+
3. Setting up Claude Code integration
|
|
44
|
+
4. Indexing your notes
|
|
45
|
+
|
|
28
46
|
### From source
|
|
29
47
|
|
|
30
48
|
```bash
|
|
31
49
|
git clone https://github.com/disco-trooper/apple-notes-mcp.git
|
|
32
50
|
cd apple-notes-mcp
|
|
33
51
|
bun install
|
|
52
|
+
bun run start
|
|
34
53
|
```
|
|
35
54
|
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- macOS (uses Apple Notes via JXA)
|
|
58
|
+
- [Bun](https://bun.sh) runtime
|
|
59
|
+
- Apple Notes app with notes
|
|
60
|
+
|
|
36
61
|
## Quick Start
|
|
37
62
|
|
|
38
|
-
Run the
|
|
63
|
+
Run the command after installation:
|
|
39
64
|
|
|
40
65
|
```bash
|
|
41
|
-
|
|
66
|
+
apple-notes-mcp
|
|
42
67
|
```
|
|
43
68
|
|
|
44
|
-
The wizard
|
|
45
|
-
1. Chooses your embedding provider (local or OpenRouter)
|
|
46
|
-
2. Configures API keys if needed
|
|
47
|
-
3. Sets auto-indexing preferences
|
|
48
|
-
4. Adds to Claude Code configuration
|
|
49
|
-
5. Indexes your notes
|
|
69
|
+
The setup wizard starts automatically on first run. Restart Claude Code after setup to use the MCP tools.
|
|
50
70
|
|
|
51
71
|
## Configuration
|
|
52
72
|
|
|
53
|
-
|
|
73
|
+
Configuration stored in `~/.apple-notes-mcp/.env`:
|
|
54
74
|
|
|
55
75
|
| Variable | Description | Default |
|
|
56
76
|
|----------|-------------|---------|
|
|
@@ -61,6 +81,14 @@ Store configuration in `.env`:
|
|
|
61
81
|
| `INDEX_TTL` | Auto-reindex interval in seconds | - |
|
|
62
82
|
| `DEBUG` | Enable debug logging | `false` |
|
|
63
83
|
|
|
84
|
+
To reconfigure:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
apple-notes-mcp setup
|
|
88
|
+
# or from source:
|
|
89
|
+
bun run setup
|
|
90
|
+
```
|
|
91
|
+
|
|
64
92
|
### Embedding Providers
|
|
65
93
|
|
|
66
94
|
**Local (default)**: Uses HuggingFace Transformers with `Xenova/multilingual-e5-small`. Free, runs locally, ~200MB download.
|
|
@@ -74,14 +102,14 @@ See [docs/models.md](docs/models.md) for model comparison.
|
|
|
74
102
|
### Search & Discovery
|
|
75
103
|
|
|
76
104
|
#### `search-notes`
|
|
77
|
-
|
|
105
|
+
Hybrid vector + fulltext search.
|
|
78
106
|
|
|
79
107
|
```
|
|
80
108
|
query: "meeting notes from last week"
|
|
81
109
|
folder: "Work" # optional, filter by folder
|
|
82
|
-
limit: 10
|
|
83
|
-
mode: "hybrid"
|
|
84
|
-
include_content: false
|
|
110
|
+
limit: 10 # default: 20
|
|
111
|
+
mode: "hybrid" # hybrid, keyword, or semantic
|
|
112
|
+
include_content: false # include full content vs preview
|
|
85
113
|
```
|
|
86
114
|
|
|
87
115
|
#### `list-notes`
|
|
@@ -91,7 +119,7 @@ Count indexed notes.
|
|
|
91
119
|
List all Apple Notes folders.
|
|
92
120
|
|
|
93
121
|
#### `get-note`
|
|
94
|
-
Get
|
|
122
|
+
Get note content by title.
|
|
95
123
|
|
|
96
124
|
```
|
|
97
125
|
title: "My Note" # or "Work/My Note" for disambiguation
|
|
@@ -100,13 +128,15 @@ title: "My Note" # or "Work/My Note" for disambiguation
|
|
|
100
128
|
### Indexing
|
|
101
129
|
|
|
102
130
|
#### `index-notes`
|
|
103
|
-
Index
|
|
131
|
+
Index notes for semantic search.
|
|
104
132
|
|
|
105
133
|
```
|
|
106
134
|
mode: "incremental" # incremental (default) or full
|
|
107
135
|
force: false # force reindex even if TTL hasn't expired
|
|
108
136
|
```
|
|
109
137
|
|
|
138
|
+
Use `mode: "full"` to create the chunk index for better long-note search. First full index takes longer as it generates chunks, but subsequent searches run fast.
|
|
139
|
+
|
|
110
140
|
#### `reindex-note`
|
|
111
141
|
Re-index a single note after manual edits.
|
|
112
142
|
|
|
@@ -150,16 +180,92 @@ title: "My Note"
|
|
|
150
180
|
folder: "Archive"
|
|
151
181
|
```
|
|
152
182
|
|
|
183
|
+
#### `batch-delete`
|
|
184
|
+
Delete multiple notes at once.
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
titles: ["Note 1", "Note 2"] # OR folder: "Old Project"
|
|
188
|
+
confirm: true # required for safety
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### `batch-move`
|
|
192
|
+
Move multiple notes to a target folder.
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
titles: ["Note 1", "Note 2"] # OR sourceFolder: "Old"
|
|
196
|
+
targetFolder: "Archive" # required
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Index Management
|
|
200
|
+
|
|
201
|
+
#### `purge-index`
|
|
202
|
+
Clear all indexed data. Use when switching embedding models or to fix corrupted index.
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
confirm: true # required for safety
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
After purging, run `index-notes` to rebuild.
|
|
209
|
+
|
|
210
|
+
### Knowledge Graph
|
|
211
|
+
|
|
212
|
+
#### `list-tags`
|
|
213
|
+
List all tags with occurrence counts.
|
|
214
|
+
|
|
215
|
+
#### `search-by-tag`
|
|
216
|
+
Find notes with a specific tag.
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
tag: "project"
|
|
220
|
+
folder: "Work" # optional
|
|
221
|
+
limit: 20 # default: 20
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### `related-notes`
|
|
225
|
+
Find notes related to a source note.
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
title: "My Note"
|
|
229
|
+
types: ["tag", "link", "similar"] # default: all
|
|
230
|
+
limit: 10 # default: 10
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `export-graph`
|
|
234
|
+
Export knowledge graph for visualization.
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
format: "json" # json or graphml
|
|
238
|
+
folder: "Work" # optional filter
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Supported Formats:**
|
|
242
|
+
- `json` - For custom visualization (D3.js, web apps)
|
|
243
|
+
- `graphml` - For professional tools (Gephi, yEd, Cytoscape)
|
|
244
|
+
|
|
153
245
|
## Claude Code Setup
|
|
154
246
|
|
|
155
|
-
### Automatic (
|
|
247
|
+
### Automatic (recommended)
|
|
156
248
|
|
|
157
|
-
|
|
249
|
+
The setup wizard automatically adds apple-notes-mcp to Claude Code. Run `apple-notes-mcp` after installation.
|
|
158
250
|
|
|
159
251
|
### Manual
|
|
160
252
|
|
|
161
253
|
Add to `~/.claude.json`:
|
|
162
254
|
|
|
255
|
+
For npm installation:
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"mcpServers": {
|
|
259
|
+
"apple-notes": {
|
|
260
|
+
"command": "apple-notes-mcp",
|
|
261
|
+
"args": [],
|
|
262
|
+
"env": {}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
For source installation:
|
|
163
269
|
```json
|
|
164
270
|
{
|
|
165
271
|
"mcpServers": {
|
|
@@ -205,6 +311,12 @@ Ensure Apple Notes runs and contains notes. Grant automation permissions when pr
|
|
|
205
311
|
# Type check
|
|
206
312
|
bun run check
|
|
207
313
|
|
|
314
|
+
# Run tests
|
|
315
|
+
bun run test
|
|
316
|
+
|
|
317
|
+
# Run with coverage
|
|
318
|
+
bun run test:coverage
|
|
319
|
+
|
|
208
320
|
# Run with debug logging
|
|
209
321
|
DEBUG=true bun run start
|
|
210
322
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@disco_trooper/apple-notes-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "MCP server for Apple Notes with semantic search and CRUD operations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -10,22 +10,25 @@
|
|
|
10
10
|
"dev": "bun --watch run src/index.ts",
|
|
11
11
|
"check": "bun run tsc --noEmit",
|
|
12
12
|
"test": "vitest run",
|
|
13
|
-
"test:watch": "vitest"
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:coverage": "vitest run --coverage"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@
|
|
17
|
-
"@lancedb/lancedb": "^0.13.0",
|
|
17
|
+
"@clack/prompts": "^0.8.0",
|
|
18
18
|
"@huggingface/transformers": "^3.0.0",
|
|
19
|
-
"
|
|
19
|
+
"@lancedb/lancedb": "^0.13.0",
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
21
|
+
"apache-arrow": "^21.1.0",
|
|
22
|
+
"dotenv": "^16.4.0",
|
|
20
23
|
"marked": "^15.0.0",
|
|
21
24
|
"run-jxa": "^3.0.0",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"dotenv": "^16.4.0"
|
|
25
|
+
"turndown": "^7.2.0",
|
|
26
|
+
"zod": "^3.24.0"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
29
|
"@types/bun": "^1.1.0",
|
|
28
30
|
"@types/turndown": "^5.0.0",
|
|
31
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
29
32
|
"typescript": "^5.7.0",
|
|
30
33
|
"vitest": "^4.0.16"
|
|
31
34
|
},
|
|
@@ -39,7 +42,8 @@
|
|
|
39
42
|
"author": "disco-trooper",
|
|
40
43
|
"license": "MIT",
|
|
41
44
|
"bin": {
|
|
42
|
-
"apple-notes-mcp": "./src/index.ts"
|
|
45
|
+
"apple-notes-mcp": "./src/index.ts",
|
|
46
|
+
"apple-notes-mcp-setup": "./src/setup.ts"
|
|
43
47
|
},
|
|
44
48
|
"files": [
|
|
45
49
|
"src",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/config/claude.test.ts
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
3
|
+
|
|
4
|
+
// Mock paths.js before importing claude.ts
|
|
5
|
+
vi.mock("./paths.js", () => ({
|
|
6
|
+
isNpmInstall: vi.fn(),
|
|
7
|
+
getProjectRoot: vi.fn(() => "/mock/project"),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe("claude config", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
vi.resetModules();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("getClaudeConfigEntry", () => {
|
|
17
|
+
it("should return npm command when npm install", async () => {
|
|
18
|
+
const { isNpmInstall } = await import("./paths.js");
|
|
19
|
+
vi.mocked(isNpmInstall).mockReturnValue(true);
|
|
20
|
+
|
|
21
|
+
const { getClaudeConfigEntry } = await import("./claude.js");
|
|
22
|
+
const entry = getClaudeConfigEntry();
|
|
23
|
+
|
|
24
|
+
expect(entry.command).toBe("apple-notes-mcp");
|
|
25
|
+
expect(entry.args).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return bun command when source install", async () => {
|
|
29
|
+
const { isNpmInstall } = await import("./paths.js");
|
|
30
|
+
vi.mocked(isNpmInstall).mockReturnValue(false);
|
|
31
|
+
|
|
32
|
+
const { getClaudeConfigEntry } = await import("./claude.js");
|
|
33
|
+
const entry = getClaudeConfigEntry();
|
|
34
|
+
|
|
35
|
+
expect(entry.command).toBe("bun");
|
|
36
|
+
expect(entry.args[0]).toBe("run");
|
|
37
|
+
expect(entry.args[1]).toContain("/mock/project/src/index.ts");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("getClaudeConfigPath", () => {
|
|
42
|
+
it("should return path to ~/.claude.json", async () => {
|
|
43
|
+
const { getClaudeConfigPath } = await import("./claude.js");
|
|
44
|
+
expect(getClaudeConfigPath()).toMatch(/\.claude\.json$/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/config/claude.ts
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code configuration management.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import { isNpmInstall, getProjectRoot } from "./paths.js";
|
|
10
|
+
|
|
11
|
+
const CLAUDE_CONFIG_PATH = path.join(os.homedir(), ".claude.json");
|
|
12
|
+
|
|
13
|
+
interface ClaudeServerEntry {
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
env: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ClaudeConfig {
|
|
20
|
+
mcpServers?: Record<string, ClaudeServerEntry>;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate the appropriate MCP server entry based on install method.
|
|
26
|
+
*/
|
|
27
|
+
export function getClaudeConfigEntry(): ClaudeServerEntry {
|
|
28
|
+
if (isNpmInstall()) {
|
|
29
|
+
return {
|
|
30
|
+
command: "apple-notes-mcp",
|
|
31
|
+
args: [],
|
|
32
|
+
env: {},
|
|
33
|
+
};
|
|
34
|
+
} else {
|
|
35
|
+
const projectRoot = getProjectRoot();
|
|
36
|
+
return {
|
|
37
|
+
command: "bun",
|
|
38
|
+
args: ["run", path.join(projectRoot, "src", "index.ts")],
|
|
39
|
+
env: {},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read existing Claude config.
|
|
46
|
+
*/
|
|
47
|
+
export function readClaudeConfig(): ClaudeConfig | null {
|
|
48
|
+
if (!fs.existsSync(CLAUDE_CONFIG_PATH)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_PATH, "utf-8");
|
|
54
|
+
return JSON.parse(content);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write Claude config with our MCP server entry.
|
|
62
|
+
*/
|
|
63
|
+
export function writeClaudeConfig(entry: ClaudeServerEntry): boolean {
|
|
64
|
+
let config = readClaudeConfig();
|
|
65
|
+
|
|
66
|
+
if (!config) {
|
|
67
|
+
config = {
|
|
68
|
+
mcpServers: {
|
|
69
|
+
"apple-notes": entry,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
const mcpServers = (config.mcpServers || {}) as Record<string, ClaudeServerEntry>;
|
|
74
|
+
mcpServers["apple-notes"] = entry;
|
|
75
|
+
config.mcpServers = mcpServers;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
fs.writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if existing Claude config uses different install method.
|
|
88
|
+
* Returns null if no existing config, otherwise returns the method.
|
|
89
|
+
*/
|
|
90
|
+
export function getExistingInstallMethod(): "npm" | "source" | null {
|
|
91
|
+
const config = readClaudeConfig();
|
|
92
|
+
const entry = config?.mcpServers?.["apple-notes"];
|
|
93
|
+
|
|
94
|
+
if (!entry) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return entry.command === "apple-notes-mcp" ? "npm" : "source";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get Claude config path for display.
|
|
103
|
+
*/
|
|
104
|
+
export function getClaudeConfigPath(): string {
|
|
105
|
+
return CLAUDE_CONFIG_PATH;
|
|
106
|
+
}
|
package/src/config/constants.ts
CHANGED
|
@@ -32,10 +32,19 @@ export const ERROR_MESSAGE_MAX_LENGTH = 200;
|
|
|
32
32
|
export const MAX_RETRIES = 3;
|
|
33
33
|
export const RATE_LIMIT_BACKOFF_BASE_MS = 2000;
|
|
34
34
|
|
|
35
|
-
// Indexing
|
|
36
|
-
export const EMBEDDING_DELAY_MS = 300;
|
|
35
|
+
// Indexing (EMBEDDING_DELAY_MS removed - not needed with batch processing)
|
|
37
36
|
|
|
38
37
|
// Search tuning
|
|
39
38
|
export const HYBRID_SEARCH_MIN_FETCH = 40;
|
|
40
39
|
export const FOLDER_FILTER_MULTIPLIER = 3;
|
|
41
40
|
export const PREVIEW_TRUNCATE_RATIO = 0.7;
|
|
41
|
+
|
|
42
|
+
// Knowledge Graph
|
|
43
|
+
export const DEFAULT_RELATED_NOTES_LIMIT = 10;
|
|
44
|
+
export const GRAPH_TAG_WEIGHT = 0.8;
|
|
45
|
+
export const GRAPH_LINK_WEIGHT = 1.0;
|
|
46
|
+
export const GRAPH_SIMILAR_WEIGHT = 0.5;
|
|
47
|
+
|
|
48
|
+
// Chunking settings
|
|
49
|
+
export const DEFAULT_CHUNK_SIZE = 500;
|
|
50
|
+
export const DEFAULT_CHUNK_OVERLAP = 100;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/config/paths.test.ts
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
|
|
4
|
+
describe("config paths", () => {
|
|
5
|
+
describe("getConfigDir", () => {
|
|
6
|
+
it("should return ~/.apple-notes-mcp", async () => {
|
|
7
|
+
const { getConfigDir } = await import("./paths.js");
|
|
8
|
+
expect(getConfigDir()).toMatch(/\.apple-notes-mcp$/);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("getEnvPath", () => {
|
|
13
|
+
it("should return config dir + .env", async () => {
|
|
14
|
+
const { getEnvPath, getConfigDir } = await import("./paths.js");
|
|
15
|
+
expect(getEnvPath()).toBe(`${getConfigDir()}/.env`);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("getDataDir", () => {
|
|
20
|
+
it("should return config dir + data", async () => {
|
|
21
|
+
const { getDataDir, getConfigDir } = await import("./paths.js");
|
|
22
|
+
expect(getDataDir()).toBe(`${getConfigDir()}/data`);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("isNpmInstall", () => {
|
|
27
|
+
it("should detect npm install from path", async () => {
|
|
28
|
+
const { isNpmInstall } = await import("./paths.js");
|
|
29
|
+
// Running from source, should be false
|
|
30
|
+
expect(isNpmInstall()).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("getProjectRoot", () => {
|
|
35
|
+
it("should return project root directory", async () => {
|
|
36
|
+
const { getProjectRoot } = await import("./paths.js");
|
|
37
|
+
expect(getProjectRoot()).toMatch(/apple-notes-mcp$/);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/config/paths.ts
|
|
2
|
+
/**
|
|
3
|
+
* Unified configuration paths for both source and npm installations.
|
|
4
|
+
* All user config lives in ~/.apple-notes-mcp/ regardless of install method.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import * as os from "node:os";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the config directory path.
|
|
13
|
+
* Always ~/.apple-notes-mcp/ for consistency across install methods.
|
|
14
|
+
*/
|
|
15
|
+
export function getConfigDir(): string {
|
|
16
|
+
return path.join(os.homedir(), ".apple-notes-mcp");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the .env file path.
|
|
21
|
+
*/
|
|
22
|
+
export function getEnvPath(): string {
|
|
23
|
+
return path.join(getConfigDir(), ".env");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the data directory path (for LanceDB).
|
|
28
|
+
*/
|
|
29
|
+
export function getDataDir(): string {
|
|
30
|
+
return path.join(getConfigDir(), "data");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if configuration exists.
|
|
35
|
+
*/
|
|
36
|
+
export function hasConfig(): boolean {
|
|
37
|
+
return fs.existsSync(getEnvPath());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Ensure config directory exists.
|
|
42
|
+
*/
|
|
43
|
+
export function ensureConfigDir(): void {
|
|
44
|
+
const configDir = getConfigDir();
|
|
45
|
+
if (!fs.existsSync(configDir)) {
|
|
46
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detect if running from npm global install vs source.
|
|
52
|
+
* Used to generate appropriate Claude Code config.
|
|
53
|
+
*/
|
|
54
|
+
export function isNpmInstall(): boolean {
|
|
55
|
+
const scriptPath = new URL(import.meta.url).pathname;
|
|
56
|
+
return (
|
|
57
|
+
scriptPath.includes("/node_modules/") ||
|
|
58
|
+
scriptPath.includes("/.npm/") ||
|
|
59
|
+
// Global npm install on macOS
|
|
60
|
+
scriptPath.includes("/lib/node_modules/")
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the project root directory (for source installs).
|
|
66
|
+
*/
|
|
67
|
+
export function getProjectRoot(): string {
|
|
68
|
+
// Navigate up from src/config/paths.ts to project root
|
|
69
|
+
return path.resolve(path.dirname(new URL(import.meta.url).pathname), "../..");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if legacy config exists in project root.
|
|
74
|
+
* Used for migration from old setup.
|
|
75
|
+
*/
|
|
76
|
+
export function hasLegacyConfig(): boolean {
|
|
77
|
+
const legacyPath = path.join(getProjectRoot(), ".env");
|
|
78
|
+
return fs.existsSync(legacyPath) && !isNpmInstall();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get legacy config path for migration.
|
|
83
|
+
*/
|
|
84
|
+
export function getLegacyEnvPath(): string {
|
|
85
|
+
return path.join(getProjectRoot(), ".env");
|
|
86
|
+
}
|