@tekmidian/devon 3.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 +21 -0
- package/README.md +472 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/jxa/escape.d.ts +22 -0
- package/dist/jxa/escape.js +38 -0
- package/dist/jxa/escape.js.map +1 -0
- package/dist/jxa/executor.d.ts +32 -0
- package/dist/jxa/executor.js +65 -0
- package/dist/jxa/executor.js.map +1 -0
- package/dist/jxa/helpers.d.ts +51 -0
- package/dist/jxa/helpers.js +136 -0
- package/dist/jxa/helpers.js.map +1 -0
- package/dist/jxa/types.d.ts +33 -0
- package/dist/jxa/types.js +43 -0
- package/dist/jxa/types.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +65 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +12 -0
- package/dist/setup.js +323 -0
- package/dist/setup.js.map +1 -0
- package/dist/shared/plist/parser.d.ts +29 -0
- package/dist/shared/plist/parser.js +112 -0
- package/dist/shared/plist/parser.js.map +1 -0
- package/dist/shared/plist/reader.d.ts +31 -0
- package/dist/shared/plist/reader.js +127 -0
- package/dist/shared/plist/reader.js.map +1 -0
- package/dist/shared/shell.d.ts +15 -0
- package/dist/shared/shell.js +22 -0
- package/dist/shared/shell.js.map +1 -0
- package/dist/tools/ai/ask-ai-about-documents.d.ts +8 -0
- package/dist/tools/ai/ask-ai-about-documents.js +88 -0
- package/dist/tools/ai/ask-ai-about-documents.js.map +1 -0
- package/dist/tools/ai/check-ai-health.d.ts +8 -0
- package/dist/tools/ai/check-ai-health.js +40 -0
- package/dist/tools/ai/check-ai-health.js.map +1 -0
- package/dist/tools/ai/create-summary-document.d.ts +8 -0
- package/dist/tools/ai/create-summary-document.js +102 -0
- package/dist/tools/ai/create-summary-document.js.map +1 -0
- package/dist/tools/ai/get-ai-tool-documentation.d.ts +8 -0
- package/dist/tools/ai/get-ai-tool-documentation.js +220 -0
- package/dist/tools/ai/get-ai-tool-documentation.js.map +1 -0
- package/dist/tools/application/is-running.d.ts +7 -0
- package/dist/tools/application/is-running.js +24 -0
- package/dist/tools/application/is-running.js.map +1 -0
- package/dist/tools/custom/column-layout.d.ts +9 -0
- package/dist/tools/custom/column-layout.js +244 -0
- package/dist/tools/custom/column-layout.js.map +1 -0
- package/dist/tools/custom/list-smart-groups.d.ts +8 -0
- package/dist/tools/custom/list-smart-groups.js +79 -0
- package/dist/tools/custom/list-smart-groups.js.map +1 -0
- package/dist/tools/custom/list-smart-rules.d.ts +8 -0
- package/dist/tools/custom/list-smart-rules.js +85 -0
- package/dist/tools/custom/list-smart-rules.js.map +1 -0
- package/dist/tools/custom/parse-eml-headers.d.ts +8 -0
- package/dist/tools/custom/parse-eml-headers.js +155 -0
- package/dist/tools/custom/parse-eml-headers.js.map +1 -0
- package/dist/tools/database/get-current-database.d.ts +7 -0
- package/dist/tools/database/get-current-database.js +29 -0
- package/dist/tools/database/get-current-database.js.map +1 -0
- package/dist/tools/database/get-open-databases.d.ts +6 -0
- package/dist/tools/database/get-open-databases.js +32 -0
- package/dist/tools/database/get-open-databases.js.map +1 -0
- package/dist/tools/groups/get-selected-records.d.ts +8 -0
- package/dist/tools/groups/get-selected-records.js +30 -0
- package/dist/tools/groups/get-selected-records.js.map +1 -0
- package/dist/tools/groups/list-group-content.d.ts +8 -0
- package/dist/tools/groups/list-group-content.js +65 -0
- package/dist/tools/groups/list-group-content.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +87 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/intelligence/classify.d.ts +8 -0
- package/dist/tools/intelligence/classify.js +83 -0
- package/dist/tools/intelligence/classify.js.map +1 -0
- package/dist/tools/intelligence/compare.d.ts +8 -0
- package/dist/tools/intelligence/compare.js +121 -0
- package/dist/tools/intelligence/compare.js.map +1 -0
- package/dist/tools/records/convert-record.d.ts +10 -0
- package/dist/tools/records/convert-record.js +77 -0
- package/dist/tools/records/convert-record.js.map +1 -0
- package/dist/tools/records/create-record.d.ts +8 -0
- package/dist/tools/records/create-record.js +64 -0
- package/dist/tools/records/create-record.js.map +1 -0
- package/dist/tools/records/delete-record.d.ts +9 -0
- package/dist/tools/records/delete-record.js +50 -0
- package/dist/tools/records/delete-record.js.map +1 -0
- package/dist/tools/records/duplicate-record.d.ts +8 -0
- package/dist/tools/records/duplicate-record.js +53 -0
- package/dist/tools/records/duplicate-record.js.map +1 -0
- package/dist/tools/records/get-record-by-id.d.ts +8 -0
- package/dist/tools/records/get-record-by-id.js +51 -0
- package/dist/tools/records/get-record-by-id.js.map +1 -0
- package/dist/tools/records/get-record-content.d.ts +7 -0
- package/dist/tools/records/get-record-content.js +48 -0
- package/dist/tools/records/get-record-content.js.map +1 -0
- package/dist/tools/records/get-record-properties.d.ts +7 -0
- package/dist/tools/records/get-record-properties.js +42 -0
- package/dist/tools/records/get-record-properties.js.map +1 -0
- package/dist/tools/records/move-record.d.ts +8 -0
- package/dist/tools/records/move-record.js +60 -0
- package/dist/tools/records/move-record.js.map +1 -0
- package/dist/tools/records/rename-record.d.ts +7 -0
- package/dist/tools/records/rename-record.js +40 -0
- package/dist/tools/records/rename-record.js.map +1 -0
- package/dist/tools/records/replicate-record.d.ts +8 -0
- package/dist/tools/records/replicate-record.js +53 -0
- package/dist/tools/records/replicate-record.js.map +1 -0
- package/dist/tools/records/set-record-properties.d.ts +10 -0
- package/dist/tools/records/set-record-properties.js +76 -0
- package/dist/tools/records/set-record-properties.js.map +1 -0
- package/dist/tools/records/update-record-content.d.ts +7 -0
- package/dist/tools/records/update-record-content.js +43 -0
- package/dist/tools/records/update-record-content.js.map +1 -0
- package/dist/tools/search/lookup-record.d.ts +7 -0
- package/dist/tools/search/lookup-record.js +160 -0
- package/dist/tools/search/lookup-record.js.map +1 -0
- package/dist/tools/search/search.d.ts +8 -0
- package/dist/tools/search/search.js +146 -0
- package/dist/tools/search/search.js.map +1 -0
- package/dist/tools/tags/add-tags.d.ts +7 -0
- package/dist/tools/tags/add-tags.js +47 -0
- package/dist/tools/tags/add-tags.js.map +1 -0
- package/dist/tools/tags/remove-tags.d.ts +7 -0
- package/dist/tools/tags/remove-tags.js +53 -0
- package/dist/tools/tags/remove-tags.js.map +1 -0
- package/dist/tools/web/create-from-url.d.ts +8 -0
- package/dist/tools/web/create-from-url.js +140 -0
- package/dist/tools/web/create-from-url.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Matthias Nott
|
|
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,472 @@
|
|
|
1
|
+
# Devon
|
|
2
|
+
|
|
3
|
+
DEVONthink MCP server for Claude Code. Zero-config setup — one command and you're done.
|
|
4
|
+
|
|
5
|
+
33 tools for full DEVONthink integration — search, CRUD, AI, tags, smart groups, email threading, and more. All MIT-licensed, zero external dependencies beyond the MCP SDK.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @tekmidian/devon setup
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The wizard checks prerequisites, configures `~/.claude.json`, and enables the server. Restart Claude Code and DEVONthink tools are immediately available.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What it provides
|
|
20
|
+
|
|
21
|
+
With this server running, Claude Code can:
|
|
22
|
+
|
|
23
|
+
- Search and browse all open DEVONthink databases
|
|
24
|
+
- Read document content (PDFs, Markdown, plain text, HTML, rich text)
|
|
25
|
+
- Create, update, and delete records
|
|
26
|
+
- Move, replicate, duplicate, and convert records across groups
|
|
27
|
+
- Add and remove tags, classify documents, manage metadata
|
|
28
|
+
- Ask DEVONthink's built-in AI about documents and create summaries
|
|
29
|
+
- Cross-reference emails with archived documents
|
|
30
|
+
- List and navigate database groups
|
|
31
|
+
- List smart groups and smart rules (not accessible via AppleScript)
|
|
32
|
+
- Parse EML headers for email thread correlation
|
|
33
|
+
- Read and copy column layout configurations
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- macOS (DEVONthink is macOS-only)
|
|
40
|
+
- [DEVONthink 3 or 4](https://www.devontechnologies.com/apps/devonthink) installed and running
|
|
41
|
+
- Node.js >= 18
|
|
42
|
+
- Claude Code
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
### Option 1: npx (no install required)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx @tekmidian/devon setup
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Option 2: Global install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install -g @tekmidian/devon
|
|
58
|
+
devon setup
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Option 3: Clone and build
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/mnott/Devon ~/dev/ai/devon
|
|
65
|
+
cd ~/dev/ai/devon
|
|
66
|
+
npm install
|
|
67
|
+
npm run build
|
|
68
|
+
node dist/index.js setup
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Manual configuration
|
|
74
|
+
|
|
75
|
+
If you prefer to configure Claude Code manually, add this to the `mcpServers` section of `~/.claude.json`:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
"devonthink": {
|
|
79
|
+
"type": "stdio",
|
|
80
|
+
"command": "npx",
|
|
81
|
+
"args": ["-y", "@tekmidian/devon", "serve"],
|
|
82
|
+
"env": {}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Or if you have it installed locally:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
"devonthink": {
|
|
90
|
+
"type": "stdio",
|
|
91
|
+
"command": "node",
|
|
92
|
+
"args": ["/path/to/devon/dist/index.js", "serve"],
|
|
93
|
+
"env": {}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Restart Claude Code after editing `~/.claude.json`.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Tools
|
|
102
|
+
|
|
103
|
+
All 33 tools organized by category.
|
|
104
|
+
|
|
105
|
+
### Application
|
|
106
|
+
|
|
107
|
+
| Tool | Description |
|
|
108
|
+
|------|-------------|
|
|
109
|
+
| `is_running` | Check if DEVONthink is running |
|
|
110
|
+
|
|
111
|
+
### Database
|
|
112
|
+
|
|
113
|
+
| Tool | Description |
|
|
114
|
+
|------|-------------|
|
|
115
|
+
| `get_open_databases` | List all open databases |
|
|
116
|
+
| `current_database` | Get the frontmost database |
|
|
117
|
+
|
|
118
|
+
### Records
|
|
119
|
+
|
|
120
|
+
| Tool | Description |
|
|
121
|
+
|------|-------------|
|
|
122
|
+
| `create_record` | Create a new record (markdown, text, HTML, etc.) |
|
|
123
|
+
| `delete_record` | Delete a record by UUID |
|
|
124
|
+
| `get_record_by_identifier` | Get a record by UUID |
|
|
125
|
+
| `get_record_properties` | Get metadata properties of a record |
|
|
126
|
+
| `get_record_content` | Read the content of a record |
|
|
127
|
+
| `update_record_content` | Update a record's content |
|
|
128
|
+
| `set_record_properties` | Set metadata properties on a record |
|
|
129
|
+
| `rename_record` | Rename a record |
|
|
130
|
+
| `move_record` | Move a record to a different group or database |
|
|
131
|
+
| `replicate_record` | Create a replicant of a record in another group |
|
|
132
|
+
| `duplicate_record` | Create an independent copy of a record |
|
|
133
|
+
| `convert_record` | Convert a record to a different type |
|
|
134
|
+
|
|
135
|
+
### Groups
|
|
136
|
+
|
|
137
|
+
| Tool | Description |
|
|
138
|
+
|------|-------------|
|
|
139
|
+
| `list_group_content` | List contents of a group |
|
|
140
|
+
| `selected_records` | Get currently selected records in DEVONthink |
|
|
141
|
+
|
|
142
|
+
### Search
|
|
143
|
+
|
|
144
|
+
| Tool | Description |
|
|
145
|
+
|------|-------------|
|
|
146
|
+
| `search` | Search across databases with DEVONthink query syntax |
|
|
147
|
+
| `lookup_record` | Look up a record by name or path |
|
|
148
|
+
|
|
149
|
+
### Tags
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|------|-------------|
|
|
153
|
+
| `add_tags` | Add tags to a record |
|
|
154
|
+
| `remove_tags` | Remove tags from a record |
|
|
155
|
+
|
|
156
|
+
### Web
|
|
157
|
+
|
|
158
|
+
| Tool | Description |
|
|
159
|
+
|------|-------------|
|
|
160
|
+
| `create_from_url` | Create a record from a URL (markdown, PDF, web archive, formatted note) |
|
|
161
|
+
|
|
162
|
+
### Intelligence
|
|
163
|
+
|
|
164
|
+
| Tool | Description |
|
|
165
|
+
|------|-------------|
|
|
166
|
+
| `classify` | Classify a record using DEVONthink's AI classification |
|
|
167
|
+
| `compare` | Compare two records for similarity |
|
|
168
|
+
|
|
169
|
+
### AI
|
|
170
|
+
|
|
171
|
+
| Tool | Description |
|
|
172
|
+
|------|-------------|
|
|
173
|
+
| `ask_ai_about_documents` | Ask DEVONthink's built-in AI a question about documents |
|
|
174
|
+
| `check_ai_health` | Check if DEVONthink's AI features are available |
|
|
175
|
+
| `create_summary_document` | Create an AI-generated summary of documents |
|
|
176
|
+
| `get_ai_tool_documentation` | Get documentation for DEVONthink's AI capabilities |
|
|
177
|
+
|
|
178
|
+
### Custom extensions
|
|
179
|
+
|
|
180
|
+
These five tools extend the core DEVONthink scripting API with functionality not available through AppleScript.
|
|
181
|
+
|
|
182
|
+
| Tool | Description |
|
|
183
|
+
|------|-------------|
|
|
184
|
+
| `list_smart_groups` | Enumerate all smart groups (reads plist directly) |
|
|
185
|
+
| `list_smart_rules` | Enumerate all smart rules (reads plist directly) |
|
|
186
|
+
| `parse_eml_headers` | Extract Message-ID, References, Subject, etc. from .eml files |
|
|
187
|
+
| `get_column_layout` | Read column layout configuration for a smart group |
|
|
188
|
+
| `copy_column_layout` | Copy column layout from one smart group to another |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Usage
|
|
193
|
+
|
|
194
|
+
Once configured, Claude Code has access to all DEVONthink tools automatically. DEVONthink must be running with at least one database open.
|
|
195
|
+
|
|
196
|
+
Example prompts:
|
|
197
|
+
|
|
198
|
+
- "Search my DEVONthink databases for notes about the Q3 budget"
|
|
199
|
+
- "Find the email from John about the contract and show me related documents"
|
|
200
|
+
- "Create a new markdown note in my Inbox with today's meeting notes"
|
|
201
|
+
- "List all documents tagged 'todo' in my Ablegen database"
|
|
202
|
+
- "Read the content of the PDF I imported yesterday"
|
|
203
|
+
- "List my smart groups"
|
|
204
|
+
- "Parse the headers from this .eml file to find its thread ID"
|
|
205
|
+
- "Ask DEVONthink's AI to summarize these documents"
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Custom tool reference
|
|
210
|
+
|
|
211
|
+
### `list_smart_groups`
|
|
212
|
+
|
|
213
|
+
Parses `~/Library/Application Support/DEVONthink/SmartGroups.plist` and returns all smart groups with their name, UUID, sync date, and `UseUUIDKey` flag.
|
|
214
|
+
|
|
215
|
+
> **Key limitation:** Smart groups are **not accessible via the DEVONthink AppleScript scripting dictionary**. This tool is the only programmatic way to enumerate them.
|
|
216
|
+
|
|
217
|
+
**Parameters:** none
|
|
218
|
+
|
|
219
|
+
**Returns:**
|
|
220
|
+
|
|
221
|
+
| Field | Type | Description |
|
|
222
|
+
|-------|------|-------------|
|
|
223
|
+
| `success` | boolean | Whether the operation succeeded |
|
|
224
|
+
| `smartGroups` | array | List of smart group entries |
|
|
225
|
+
| `totalCount` | number | Total number of smart groups found |
|
|
226
|
+
|
|
227
|
+
Each entry in `smartGroups`:
|
|
228
|
+
|
|
229
|
+
| Field | Type | Description |
|
|
230
|
+
|-------|------|-------------|
|
|
231
|
+
| `name` | string | Display name of the smart group |
|
|
232
|
+
| `uuid` | string | UUID from the `sync.UUID` field — use this with the `search` tool |
|
|
233
|
+
| `syncDate` | string \| null | Last sync date (ISO 8601) |
|
|
234
|
+
| `useUuidKey` | boolean \| null | Whether DEVONthink uses UUID as the key internally |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### `list_smart_rules`
|
|
239
|
+
|
|
240
|
+
Parses `~/Library/Application Support/DEVONthink/SmartRules.plist` and returns all smart rules with name, UUID, enabled state, execution metadata, and sync date.
|
|
241
|
+
|
|
242
|
+
**Parameters:** none
|
|
243
|
+
|
|
244
|
+
**Returns:**
|
|
245
|
+
|
|
246
|
+
| Field | Type | Description |
|
|
247
|
+
|-------|------|-------------|
|
|
248
|
+
| `success` | boolean | Whether the operation succeeded |
|
|
249
|
+
| `smartRules` | array | List of smart rule entries |
|
|
250
|
+
| `totalCount` | number | Total number of smart rules found |
|
|
251
|
+
|
|
252
|
+
Each entry in `smartRules`:
|
|
253
|
+
|
|
254
|
+
| Field | Type | Description |
|
|
255
|
+
|-------|------|-------------|
|
|
256
|
+
| `name` | string | Display name of the smart rule |
|
|
257
|
+
| `uuid` | string | UUID from the `sync.UUID` field |
|
|
258
|
+
| `enabled` | boolean \| null | Whether the rule is currently enabled |
|
|
259
|
+
| `indexOffset` | number \| null | Order index within the rules list |
|
|
260
|
+
| `lastExecution` | number \| null | CFAbsoluteTime timestamp of last execution |
|
|
261
|
+
| `syncDate` | string \| null | Last sync date (ISO 8601) |
|
|
262
|
+
| `useUuidKey` | boolean \| null | Whether DEVONthink uses UUID as the key internally |
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### `parse_eml_headers`
|
|
267
|
+
|
|
268
|
+
Reads an RFC 2822 `.eml` file and extracts the MIME headers needed for email thread correlation.
|
|
269
|
+
|
|
270
|
+
Handles CRLF and LF line endings, folded headers (continuation lines), and RFC 2047 encoded words in Subject, From, and To fields.
|
|
271
|
+
|
|
272
|
+
Only reads the first 64 KB of the file since headers are always at the start.
|
|
273
|
+
|
|
274
|
+
**Parameters:**
|
|
275
|
+
|
|
276
|
+
| Parameter | Type | Required | Description |
|
|
277
|
+
|-----------|------|----------|-------------|
|
|
278
|
+
| `filePath` | string | yes | Absolute path to the `.eml` file |
|
|
279
|
+
|
|
280
|
+
**Returns:**
|
|
281
|
+
|
|
282
|
+
| Field | Type | Description |
|
|
283
|
+
|-------|------|-------------|
|
|
284
|
+
| `success` | boolean | Whether parsing succeeded |
|
|
285
|
+
| `filePath` | string | The path that was read |
|
|
286
|
+
| `messageId` | string \| null | The `Message-ID` header value |
|
|
287
|
+
| `inReplyTo` | string \| null | The `In-Reply-To` header value |
|
|
288
|
+
| `references` | string[] | Array of message IDs from the `References` header |
|
|
289
|
+
| `subject` | string \| null | Decoded subject line |
|
|
290
|
+
| `from` | string \| null | Sender address(es) |
|
|
291
|
+
| `to` | string \| null | Recipient address(es) |
|
|
292
|
+
| `cc` | string \| null | CC address(es) |
|
|
293
|
+
| `date` | string \| null | Date string from the header |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### `get_column_layout`
|
|
298
|
+
|
|
299
|
+
Reads the column layout for a named smart group or smart rule from `~/Library/Preferences/com.devon-technologies.think.plist`.
|
|
300
|
+
|
|
301
|
+
Returns the ordered visible columns, all table view columns (visible and hidden), and column widths. Supports partial name matching.
|
|
302
|
+
|
|
303
|
+
**Parameters:**
|
|
304
|
+
|
|
305
|
+
| Parameter | Type | Required | Description |
|
|
306
|
+
|-----------|------|----------|-------------|
|
|
307
|
+
| `name` | string | yes | Display name of the smart group or smart rule |
|
|
308
|
+
| `uuid` | string | no | UUID fallback if name lookup fails |
|
|
309
|
+
|
|
310
|
+
**Returns:**
|
|
311
|
+
|
|
312
|
+
| Field | Type | Description |
|
|
313
|
+
|-------|------|-------------|
|
|
314
|
+
| `success` | boolean | Whether a layout was found |
|
|
315
|
+
| `name` | string | The name that was searched |
|
|
316
|
+
| `resolvedKey` | string | The actual plist key used |
|
|
317
|
+
| `columns` | string[] \| null | Visible columns in display order |
|
|
318
|
+
| `tableViewColumns` | string[] \| null | All column identifiers (visible + hidden) |
|
|
319
|
+
| `widths` | object \| null | Map of column identifier to width |
|
|
320
|
+
| `keysFound` | string[] | Which plist keys were present |
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
### `copy_column_layout`
|
|
325
|
+
|
|
326
|
+
Copies the column layout from one smart group or smart rule to another. All layout keys are written atomically using Python's `plistlib`.
|
|
327
|
+
|
|
328
|
+
DEVONthink must be restarted (or the smart group window closed and reopened) for the change to take effect.
|
|
329
|
+
|
|
330
|
+
**Parameters:**
|
|
331
|
+
|
|
332
|
+
| Parameter | Type | Required | Description |
|
|
333
|
+
|-----------|------|----------|-------------|
|
|
334
|
+
| `sourceName` | string | yes | Name of the source smart group |
|
|
335
|
+
| `targetName` | string | yes | Name of the target smart group |
|
|
336
|
+
| `sourceUuid` | string | no | UUID fallback for the source |
|
|
337
|
+
| `targetUuid` | string | no | UUID for the target (layout written under UUID key) |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Workflows
|
|
342
|
+
|
|
343
|
+
### Smart group discovery and content querying
|
|
344
|
+
|
|
345
|
+
Smart groups are virtual views defined by search criteria — they are not part of the AppleScript scripting dictionary. Use this two-step pattern:
|
|
346
|
+
|
|
347
|
+
**Step 1:** Enumerate all smart groups.
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
list_smart_groups
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Step 2:** Query the contents using `search` with `groupUuid`.
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
search
|
|
357
|
+
query: ""
|
|
358
|
+
groupUuid: "4A469368-94FD-46D3-9A62-ED7C24D822D8"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
> **Note:** `list_group_content` with a smart group UUID returns email Message-IDs in the `uuid` field (not DEVONthink record UUIDs). Use `search` with `groupUuid` instead — it returns proper records with dates and correct UUIDs.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
### Email thread correlation
|
|
366
|
+
|
|
367
|
+
To link a live email thread back to its archived copy in DEVONthink, use a three-tier matching strategy:
|
|
368
|
+
|
|
369
|
+
**Tier 1 — Thread ID match (highest precision)**
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
get_record_properties uuid: <record_uuid>
|
|
373
|
+
parse_eml_headers filePath: "/path/to/archived/email.eml"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Use `messageId`, `inReplyTo`, and `references` to correlate precisely.
|
|
377
|
+
|
|
378
|
+
**Tier 2 — Subject and sender match**
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
search query: "kind:email subject:\"Contract renewal\" from:jane@example.com"
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Strip `Re:`, `Fwd:`, `AW:`, `WG:` prefixes before searching.
|
|
385
|
+
|
|
386
|
+
**Tier 3 — Subject only (broadest)**
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
search query: "kind:email subject:\"Contract renewal\""
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
### Column layout management
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
get_column_layout name: "Archivieren - Jobs"
|
|
398
|
+
copy_column_layout sourceName: "Archivieren - Jobs" targetName: "New Smart Group"
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Close and reopen the smart group window (or restart DEVONthink) after copying.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## DEVONthink search syntax
|
|
406
|
+
|
|
407
|
+
The `search` tool supports these operators:
|
|
408
|
+
|
|
409
|
+
| Operator | Example | Description |
|
|
410
|
+
|----------|---------|-------------|
|
|
411
|
+
| `kind:` | `kind:email` | Filter by record type |
|
|
412
|
+
| `name:` | `name:"offer letter"` | Match filename or subject |
|
|
413
|
+
| `subject:` | `subject:"interview"` | Email subject field |
|
|
414
|
+
| `from:` | `from:recruiter@co.com` | Sender address |
|
|
415
|
+
| `to:` | `to:user@example.com` | Recipient address |
|
|
416
|
+
| `text:` | `text:"stock options"` | Full-text content search |
|
|
417
|
+
| `tags:` | `tags:jobs` | Tagged records |
|
|
418
|
+
| `date:` | `date:2024-01-01~` | Date range (`~` = after) |
|
|
419
|
+
| Quotes | `"exact phrase"` | Exact phrase match |
|
|
420
|
+
| AND/OR | `from:x OR from:y` | Boolean operators |
|
|
421
|
+
|
|
422
|
+
Combining operators:
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
kind:email from:@company.com subject:"compensation" date:2023-01-01~2024-12-31
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## How it works
|
|
431
|
+
|
|
432
|
+
`devon` is a standalone MCP server built on `@modelcontextprotocol/sdk`. All 33 tools are implemented from scratch under the MIT license with no external dependencies beyond the MCP SDK.
|
|
433
|
+
|
|
434
|
+
The tools communicate with DEVONthink via JXA (JavaScript for Automation) executed through `osascript`. A shared JXA executor handles script construction, escaping, and result parsing. The custom tools (smart groups, smart rules, column layouts) use `PlistBuddy` and Python's `plistlib` to read DEVONthink preference and data files directly.
|
|
435
|
+
|
|
436
|
+
Compatible with both DEVONthink 3 and DEVONthink 4, with automatic app name detection.
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Troubleshooting
|
|
441
|
+
|
|
442
|
+
**"DEVONthink not found"**
|
|
443
|
+
Make sure DEVONthink 3 or 4 is installed in `/Applications` and running.
|
|
444
|
+
|
|
445
|
+
**"No databases found"**
|
|
446
|
+
Open at least one DEVONthink database before using the MCP tools.
|
|
447
|
+
|
|
448
|
+
**Tools not appearing in Claude Code**
|
|
449
|
+
1. Verify `~/.claude.json` has the `devonthink` entry
|
|
450
|
+
2. Restart Claude Code (not just a new session — fully quit and reopen)
|
|
451
|
+
3. Check that DEVONthink is running
|
|
452
|
+
|
|
453
|
+
**AppleScript errors**
|
|
454
|
+
Grant Claude Code (or Terminal) Automation permissions in System Settings > Privacy & Security > Automation.
|
|
455
|
+
|
|
456
|
+
**`list_smart_groups` returns no results or error**
|
|
457
|
+
The plist format varies between DEVONthink versions. Use `plutil -p ~/Library/Application\ Support/DEVONthink/SmartGroups.plist` to inspect the raw format and report an issue.
|
|
458
|
+
|
|
459
|
+
**`get_column_layout` returns "no layout found"**
|
|
460
|
+
The smart group does not yet have a custom column layout saved. Use `copy_column_layout` to copy a layout from another smart group that already has one configured.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Credits
|
|
465
|
+
|
|
466
|
+
This project was inspired by [dvcrn](https://github.com/dvcrn)'s [mcp-server-devonthink](https://github.com/dvcrn/mcp-server-devonthink), which demonstrated the potential of DEVONthink MCP integration. Version 3.0.0 is a clean-room rewrite — all 33 tools are independently implemented under the MIT license.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## License
|
|
471
|
+
|
|
472
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* index.ts — CLI entry point for devon
|
|
4
|
+
*
|
|
5
|
+
* Usage: devon <command>
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* serve Start the in-process MCP server (default)
|
|
9
|
+
* setup Interactive first-time setup — configures ~/.claude.json
|
|
10
|
+
* version Print the version
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* index.ts — CLI entry point for devon
|
|
4
|
+
*
|
|
5
|
+
* Usage: devon <command>
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* serve Start the in-process MCP server (default)
|
|
9
|
+
* setup Interactive first-time setup — configures ~/.claude.json
|
|
10
|
+
* version Print the version
|
|
11
|
+
*/
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { runSetup } from "./setup.js";
|
|
14
|
+
import { createExtendedServer } from "./server.js";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { join, dirname } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
const command = process.argv[2];
|
|
19
|
+
function getVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const distDir = dirname(__filename);
|
|
23
|
+
const pkgPath = join(distDir, "..", "package.json");
|
|
24
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
25
|
+
return pkg.version ?? "unknown";
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "unknown";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function main() {
|
|
32
|
+
switch (command) {
|
|
33
|
+
case "serve":
|
|
34
|
+
case undefined: {
|
|
35
|
+
// Default: start the in-process extended MCP server
|
|
36
|
+
const transport = new StdioServerTransport();
|
|
37
|
+
const { server, cleanup } = await createExtendedServer();
|
|
38
|
+
await server.connect(transport);
|
|
39
|
+
// Graceful shutdown
|
|
40
|
+
const shutdown = async () => {
|
|
41
|
+
await cleanup();
|
|
42
|
+
await server.close();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
};
|
|
45
|
+
process.on("SIGINT", () => {
|
|
46
|
+
void shutdown();
|
|
47
|
+
});
|
|
48
|
+
process.on("SIGTERM", () => {
|
|
49
|
+
void shutdown();
|
|
50
|
+
});
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case "setup": {
|
|
54
|
+
await runSetup();
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case "version":
|
|
58
|
+
case "--version":
|
|
59
|
+
case "-v": {
|
|
60
|
+
process.stdout.write(`devon v${getVersion()}\n`);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
default: {
|
|
64
|
+
process.stderr.write([
|
|
65
|
+
"Usage: devon <command>",
|
|
66
|
+
"",
|
|
67
|
+
"Commands:",
|
|
68
|
+
" serve Start the MCP server (default if no command given)",
|
|
69
|
+
" setup Interactive first-time setup — configures ~/.claude.json",
|
|
70
|
+
" version Print the version",
|
|
71
|
+
"",
|
|
72
|
+
].join("\n"));
|
|
73
|
+
process.exit(command ? 1 : 0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
main().catch((err) => {
|
|
78
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
79
|
+
process.stderr.write(`[devon] Fatal error: ${msg}\n`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* escape.ts — Safe value embedding for JXA scripts and shell commands.
|
|
3
|
+
*
|
|
4
|
+
* jxaLiteral() uses JSON.stringify to produce a valid JavaScript string literal,
|
|
5
|
+
* eliminating the entire injection vulnerability class.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Convert any JS value into a safe JXA literal string.
|
|
9
|
+
* Strings become quoted JS string literals, numbers/booleans pass through,
|
|
10
|
+
* null/undefined become `null`, arrays/objects become JSON.
|
|
11
|
+
*/
|
|
12
|
+
export declare function jxaLiteral(value: unknown): string;
|
|
13
|
+
/**
|
|
14
|
+
* Escape a DEVONthink search query string.
|
|
15
|
+
* Preserves wildcards (* and ?) which are valid search operators.
|
|
16
|
+
* Escapes characters that could break JXA string context.
|
|
17
|
+
*/
|
|
18
|
+
export declare function escapeSearchQuery(query: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Shell-quote a string by wrapping in single quotes and escaping embedded single quotes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function shellQuote(s: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* escape.ts — Safe value embedding for JXA scripts and shell commands.
|
|
3
|
+
*
|
|
4
|
+
* jxaLiteral() uses JSON.stringify to produce a valid JavaScript string literal,
|
|
5
|
+
* eliminating the entire injection vulnerability class.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Convert any JS value into a safe JXA literal string.
|
|
9
|
+
* Strings become quoted JS string literals, numbers/booleans pass through,
|
|
10
|
+
* null/undefined become `null`, arrays/objects become JSON.
|
|
11
|
+
*/
|
|
12
|
+
export function jxaLiteral(value) {
|
|
13
|
+
if (value === null || value === undefined)
|
|
14
|
+
return "null";
|
|
15
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
16
|
+
return String(value);
|
|
17
|
+
if (typeof value === "string")
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
// Arrays and objects: JSON.stringify produces valid JS literals
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Escape a DEVONthink search query string.
|
|
24
|
+
* Preserves wildcards (* and ?) which are valid search operators.
|
|
25
|
+
* Escapes characters that could break JXA string context.
|
|
26
|
+
*/
|
|
27
|
+
export function escapeSearchQuery(query) {
|
|
28
|
+
// JSON.stringify handles all JS escaping; we just unwrap the outer quotes
|
|
29
|
+
// since this will be embedded inside a jxaLiteral() call
|
|
30
|
+
return query;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Shell-quote a string by wrapping in single quotes and escaping embedded single quotes.
|
|
34
|
+
*/
|
|
35
|
+
export function shellQuote(s) {
|
|
36
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=escape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/jxa/escape.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC5D,gEAAgE;IAChE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,0EAA0E;IAC1E,yDAAyD;IACzD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* executor.ts — JXA (JavaScript for Automation) execution via osascript.
|
|
3
|
+
*
|
|
4
|
+
* Provides a JxaExecutor interface for testability (mock in tests)
|
|
5
|
+
* and an OsascriptExecutor implementation that shells out to osascript.
|
|
6
|
+
*/
|
|
7
|
+
/** Result from executing a JXA script */
|
|
8
|
+
export interface JxaResult {
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
}
|
|
12
|
+
/** Injectable interface for JXA execution */
|
|
13
|
+
export interface JxaExecutor {
|
|
14
|
+
run(script: string): JxaResult;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Production executor that runs JXA via `osascript -l JavaScript`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class OsascriptExecutor implements JxaExecutor {
|
|
20
|
+
private timeout;
|
|
21
|
+
private maxBuffer;
|
|
22
|
+
constructor(opts?: {
|
|
23
|
+
timeout?: number;
|
|
24
|
+
maxBuffer?: number;
|
|
25
|
+
});
|
|
26
|
+
run(script: string): JxaResult;
|
|
27
|
+
}
|
|
28
|
+
export declare function getDefaultExecutor(): JxaExecutor;
|
|
29
|
+
/**
|
|
30
|
+
* Override the default executor (for testing).
|
|
31
|
+
*/
|
|
32
|
+
export declare function setDefaultExecutor(executor: JxaExecutor): void;
|