@heznpc/imcp 0.1.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.ko.md ADDED
@@ -0,0 +1,131 @@
1
+ # iMcp
2
+
3
+ Apple Notes용 MCP 서버. 완전한 CRUD, 일괄 스캔, 스마트 비교 — AI를 Apple 생태계에 연결합니다.
4
+
5
+ > [English](README.md)
6
+
7
+ ## 특징
8
+
9
+ - **12개 도구** — 메모 목록/검색/읽기/생성/수정/삭제/이동 + 폴더 관리 + 일괄 작업
10
+ - **3개 프롬프트** — 메모 정리, 중복 찾기, 주간 리뷰
11
+ - **JXA 기반** — JavaScript for Automation으로 Apple Notes 직접 제어
12
+ - **stdio 전송** — 안전한 로컬 통신, 네트워크 노출 없음
13
+ - **Safety Annotations** — 모든 도구에 readOnly/destructive 힌트 적용
14
+
15
+ ## 유사 프로젝트 비교
16
+
17
+ | | **iMcp** | **apple-mcp** | **iMCP** | **mcp-apple-notes** |
18
+ |---|---|---|---|---|
19
+ | 개발자 | heznpc | supermemoryai | loopwork-ai | RafalWilinski |
20
+ | 상태 | **활성** | 아카이브 | 활성 | 저활성 |
21
+ | 언어 | TypeScript | TypeScript | Swift | TypeScript |
22
+ | 대상 앱 | Notes | Notes 등 7개 앱 | Calendar 등 10개 서비스 (Notes 미지원) | Notes |
23
+ | Notes CRUD | **전체 (생성/읽기/수정/삭제/이동)** | 일부 | 없음 | 읽기 + 생성만 |
24
+ | 도구 수 | 12 | 7 (앱당 1개 + enum) | 28 | 5 |
25
+ | 일괄 작업 | scan / compare / bulk_move | 없음 | 없음 | 없음 |
26
+ | 프롬프트 | 3개 (정리, 중복, 리뷰) | 없음 | 없음 | 없음 |
27
+ | Safety Annotations | 전체 적용 | 없음 | 없음 | 없음 |
28
+ | 전송 방식 | stdio | stdio | TCP/Bonjour | stdio |
29
+ | 설치 | `npx @heznpc/imcp` | `bunx apple-mcp` | Homebrew cask / 앱 다운로드 | 수동 clone |
30
+ | 검색 | 키워드 검색 | 키워드 검색 | — | 벡터 + 전문 검색 (RAG) |
31
+
32
+ ### 핵심 차이점
33
+
34
+ - **apple-mcp** (3K+ 스타) — 가장 인기 있었으나 2026년 1월 아카이브. 더 이상 유지보수되지 않음
35
+ - **iMCP** (loopwork-ai) — 네이티브 Swift 앱으로 가장 많은 서비스 지원. 그러나 **Notes를 지원하지 않고**, 대부분 읽기 전용이며, Bonjour로 네트워크 노출
36
+ - **mcp-apple-notes** — RAG/시맨틱 검색이 독특한 장점이나, 도구가 5개뿐이고 CRUD가 불완전하며 npm 미배포
37
+ - **iMcp** — Notes에 집중한 완전한 CRUD + 일괄 작업 + 가이드 프롬프트. `npx` 한 줄 실행, stdio로 보안 안전
38
+
39
+ ## 빠른 시작
40
+
41
+ ### Claude Desktop
42
+
43
+ `~/Library/Application Support/Claude/claude_desktop_config.json`에 추가:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "imcp": {
49
+ "command": "npx",
50
+ "args": ["-y", "@heznpc/imcp"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### Claude Code
57
+
58
+ ```bash
59
+ claude mcp add imcp -- npx -y @heznpc/imcp
60
+ ```
61
+
62
+ ### 로컬 개발
63
+
64
+ ```bash
65
+ git clone https://github.com/heznpc/iMcp.git
66
+ cd iMcp
67
+ npm install
68
+ npm run build
69
+ ```
70
+
71
+ 이후 MCP 클라이언트에서 `node dist/index.js`를 실행합니다.
72
+
73
+ ## 도구
74
+
75
+ ### 메모
76
+
77
+ | 도구 | 설명 | 타입 |
78
+ |------|------|------|
79
+ | `list_notes` | 전체 메모 목록 (제목, 폴더, 날짜) | 읽기 |
80
+ | `search_notes` | 키워드로 제목/본문 검색 | 읽기 |
81
+ | `read_note` | 특정 메모의 전체 내용 조회 | 읽기 |
82
+ | `create_note` | HTML 본문으로 새 메모 생성 | 쓰기 |
83
+ | `update_note` | 메모 본문 전체 교체 | 파괴적 |
84
+ | `delete_note` | 메모 삭제 (최근 삭제로 이동) | 파괴적 |
85
+ | `move_note` | 메모를 다른 폴더로 이동 | 파괴적 |
86
+
87
+ ### 폴더
88
+
89
+ | 도구 | 설명 | 타입 |
90
+ |------|------|------|
91
+ | `list_folders` | 전체 폴더 목록 (메모 수 포함) | 읽기 |
92
+ | `create_folder` | 새 폴더 생성 | 쓰기 |
93
+
94
+ ### 일괄 작업
95
+
96
+ | 도구 | 설명 | 타입 |
97
+ |------|------|------|
98
+ | `scan_notes` | 메타데이터와 미리보기로 전체 메모 스캔 | 읽기 |
99
+ | `compare_notes` | 2~5개 메모를 나란히 비교 | 읽기 |
100
+ | `bulk_move_notes` | 여러 메모를 한 번에 이동 | 파괴적 |
101
+
102
+ ## 프롬프트
103
+
104
+ 가이드 워크플로우로 메모를 정리할 수 있습니다:
105
+
106
+ - **organize-notes** — 전체 메모 스캔, 주제별 분류, 폴더 생성 및 이동
107
+ - **find-duplicates** — 중복/유사 메모 찾기, 내용 비교, 정리 제안
108
+ - **weekly-review** — 지난 1주일 메모 요약 및 정리 제안
109
+
110
+ ## 설정
111
+
112
+ | 환경 변수 | 기본값 | 설명 |
113
+ |----------|--------|------|
114
+ | `IMCP_INCLUDE_SHARED` | `false` | 공유 메모/폴더를 결과에 포함 |
115
+
116
+ ## 요구 사항
117
+
118
+ - macOS (Apple Notes는 macOS에서만 동작)
119
+ - Node.js >= 18
120
+ - 첫 실행 시 Notes.app 제어를 위한 자동화 권한 요청
121
+
122
+ ## 제약 사항
123
+
124
+ - `move_note` / `bulk_move_notes`: Apple Notes에 네이티브 이동 명령이 없어 복사 후 삭제 방식. 새 ID 부여, 날짜 초기화, 첨부파일 손실.
125
+ - `update_note`: 본문 전체를 교체. 부분 보존이 필요하면 먼저 read_note로 읽을 것. 첨부파일 손실 가능.
126
+ - 검색은 클라이언트 사이드 (Apple Notes 스크립팅에 네이티브 검색 API 없음).
127
+ - 비밀번호로 보호된 메모는 읽을 수 없음.
128
+
129
+ ## 라이선스
130
+
131
+ MIT
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # iMcp
2
+
3
+ MCP server for Apple Notes. Full CRUD, bulk scan, and smart comparison — connect any AI to your Apple ecosystem.
4
+
5
+ > [한국어](README.ko.md)
6
+
7
+ ## Features
8
+
9
+ - **12 tools** — list, search, read, create, update, delete, move notes + folder management + bulk operations
10
+ - **3 prompts** — organize notes, find duplicates, weekly review
11
+ - **JXA-based** — direct Apple Notes automation via JavaScript for Automation
12
+ - **stdio transport** — secure local communication, no network exposure
13
+ - **Safety annotations** — every tool tagged with readOnly/destructive hints
14
+
15
+ ## Quick Start
16
+
17
+ ### Claude Desktop
18
+
19
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "imcp": {
25
+ "command": "npx",
26
+ "args": ["-y", "@heznpc/imcp"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ### Claude Code
33
+
34
+ ```bash
35
+ claude mcp add imcp -- npx -y @heznpc/imcp
36
+ ```
37
+
38
+ ### Local Development
39
+
40
+ ```bash
41
+ git clone https://github.com/heznpc/iMcp.git
42
+ cd iMcp
43
+ npm install
44
+ npm run build
45
+ ```
46
+
47
+ Then point your MCP client to `node dist/index.js`.
48
+
49
+ ## Tools
50
+
51
+ ### Notes
52
+
53
+ | Tool | Description | Type |
54
+ |------|-------------|------|
55
+ | `list_notes` | List all notes with title, folder, and dates | read |
56
+ | `search_notes` | Search notes by keyword in title and body | read |
57
+ | `read_note` | Read full content of a note by ID | read |
58
+ | `create_note` | Create a new note with HTML body | write |
59
+ | `update_note` | Replace entire body of a note | destructive |
60
+ | `delete_note` | Delete a note (moved to Recently Deleted) | destructive |
61
+ | `move_note` | Move a note to another folder | destructive |
62
+
63
+ ### Folders
64
+
65
+ | Tool | Description | Type |
66
+ |------|-------------|------|
67
+ | `list_folders` | List all folders with note counts | read |
68
+ | `create_folder` | Create a new folder | write |
69
+
70
+ ### Bulk Operations
71
+
72
+ | Tool | Description | Type |
73
+ |------|-------------|------|
74
+ | `scan_notes` | Bulk scan notes with metadata and preview | read |
75
+ | `compare_notes` | Compare 2-5 notes side by side | read |
76
+ | `bulk_move_notes` | Move multiple notes at once | destructive |
77
+
78
+ ## Prompts
79
+
80
+ Use these guided workflows to organize your notes:
81
+
82
+ - **organize-notes** — Scan all notes, classify by topic, create folders, and move notes
83
+ - **find-duplicates** — Find duplicate or similar notes, compare content, and suggest cleanup
84
+ - **weekly-review** — Summarize notes from the past week and suggest actions
85
+
86
+ ## Configuration
87
+
88
+ | Environment Variable | Default | Description |
89
+ |---------------------|---------|-------------|
90
+ | `IMCP_INCLUDE_SHARED` | `false` | Include shared notes/folders in results |
91
+
92
+ ## Requirements
93
+
94
+ - macOS (Apple Notes requires macOS)
95
+ - Node.js >= 18
96
+ - First run will prompt for Automation permission to control Notes.app
97
+
98
+ ## Limitations
99
+
100
+ - `move_note` / `bulk_move_notes`: Apple Notes has no native move command. Notes are copied and deleted, resulting in new IDs, reset dates, and lost attachments.
101
+ - `update_note`: Replaces entire body. Read the note first to preserve content. Attachments may be lost.
102
+ - Search is client-side (no native search API in Apple Notes scripting).
103
+ - Password-protected notes cannot be read.
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerNoteTools } from "./notes/tools.js";
5
+ import { registerNotePrompts } from "./notes/prompts.js";
6
+ import { parseConfig } from "./shared/config.js";
7
+ const config = parseConfig();
8
+ const server = new McpServer({
9
+ name: "imcp",
10
+ version: "0.1.0",
11
+ });
12
+ registerNoteTools(server, config);
13
+ registerNotePrompts(server);
14
+ async function main() {
15
+ const transport = new StdioServerTransport();
16
+ await server.connect(transport);
17
+ console.error(`iMcp server running on stdio (shared notes: ${config.includeShared ? "on" : "off"})`);
18
+ }
19
+ main().catch((error) => {
20
+ console.error("Fatal error:", error);
21
+ process.exit(1);
22
+ });
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;AAE7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAClC,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAE5B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,+CAA+C,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;AACvG,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerNotePrompts(server: McpServer): void;
@@ -0,0 +1,52 @@
1
+ import { z } from "zod";
2
+ function userPrompt(description, text) {
3
+ return {
4
+ description,
5
+ messages: [{ role: "user", content: { type: "text", text } }],
6
+ };
7
+ }
8
+ export function registerNotePrompts(server) {
9
+ server.prompt("organize-notes", { folder: z.string().optional().describe("Source folder to organize. Defaults to 'Notes'.") }, ({ folder }) => {
10
+ const target = folder ?? "Notes";
11
+ return userPrompt("Scan all notes, classify by topic, create folders, and move notes into them.", `Apple Notes의 "${target}" 폴더에 있는 메모를 정리해줘.
12
+
13
+ 다음 단계를 반드시 iMcp 도구를 사용해서 실행해:
14
+
15
+ 1. scan_notes(folder: "${target}")로 해당 폴더의 메모를 스캔해. 메모가 많으면 offset으로 페이징해서 전부 확인해
16
+ 2. 스캔 결과를 분석해서 주제별로 분류해
17
+ 3. 분류 결과를 나에게 보여주고 확인을 받아
18
+ 4. 확인 후 create_folder로 필요한 폴더를 생성해
19
+ 5. bulk_move_notes로 메모를 해당 폴더로 실제 이동시켜
20
+
21
+ 중요: MD 파일을 작성하지 말고, 실제 Apple Notes 앱에서 폴더를 만들고 메모를 이동해야 해.`);
22
+ });
23
+ server.prompt("find-duplicates", { folder: z.string().optional().describe("Folder to scan for duplicates. Defaults to all.") }, ({ folder }) => {
24
+ const scope = folder ? `"${folder}" 폴더의` : "전체";
25
+ return userPrompt("Find duplicate or similar notes, compare content, and suggest cleanup.", `Apple Notes에서 ${scope} 중복/유사 메모를 찾아서 정리해줘.
26
+
27
+ 다음 단계를 반드시 iMcp 도구를 사용해서 실행해:
28
+
29
+ 1. scan_notes로 메모를 스캔해 (folder 파라미터로 범위 지정 가능, offset으로 페이징)
30
+ 2. 제목이나 미리보기가 비슷한 메모 그룹을 찾아
31
+ 3. compare_notes로 유사 메모들의 전체 내용을 비교해
32
+ 4. 각 그룹별로 "어떤 걸 남기고 어떤 걸 삭제할지" 제안해
33
+ 5. 나의 확인 후 delete_note로 중복 메모를 삭제해
34
+
35
+ 중요: 삭제 전에 반드시 나에게 확인을 받아. 미리보기만 보고 판단하지 말고 compare_notes로 전체 내용을 확인해.`);
36
+ });
37
+ server.prompt("weekly-review", "Summarize notes from the past week and suggest organization actions.", () => {
38
+ const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
39
+ return userPrompt("Summarize notes from the past week and suggest organization actions.", `Apple Notes 주간 리뷰를 해줘.
40
+
41
+ 다음 단계를 반드시 iMcp 도구를 사용해서 실행해:
42
+
43
+ 1. scan_notes로 전체 메모를 스캔해 (메모가 많으면 offset으로 페이징)
44
+ 2. ${oneWeekAgo} 이후에 생성되거나 수정된 메모만 필터링해
45
+ 3. 이번 주 메모를 주제별로 요약해줘
46
+ 4. 정리가 필요한 메모가 있으면 제안해 (미분류, 빈 메모 등)
47
+ 5. 내가 원하면 폴더 이동이나 삭제를 실행해
48
+
49
+ 중요: 실제 iMcp 도구를 사용해서 Apple Notes 데이터를 조회해.`);
50
+ });
51
+ }
52
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/notes/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,SAAS,UAAU,CAAC,WAAmB,EAAE,IAAY;IACnD,OAAO;QACL,WAAW;QACX,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,EAAE,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,MAAM,CACX,gBAAgB,EAChB,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC,EAAE,EAC7F,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC;QACjC,OAAO,UAAU,CACf,8EAA8E,EAC9E,iBAAiB,MAAM;;;;yBAIN,MAAM;;;;;;2DAM4B,CACpD,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,MAAM,CACX,iBAAiB,EACjB,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC,EAAE,EAC7F,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAChD,OAAO,UAAU,CACf,wEAAwE,EACxE,iBAAiB,KAAK;;;;;;;;;;sEAUwC,CAC/D,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,MAAM,CACX,eAAe,EACf,sEAAsE,EACtE,GAAG,EAAE;QACH,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QAChF,OAAO,UAAU,CACf,sEAAsE,EACtE;;;;;KAKH,UAAU;;;;;2CAK4B,CACpC,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare function listNotesScript(folder?: string): string;
2
+ export declare function searchNotesScript(query: string, limit: number): string;
3
+ export declare function readNoteScript(id: string): string;
4
+ export declare function createNoteScript(body: string, folder?: string): string;
5
+ export declare function createNoteSharedScript(body: string, folder?: string): string;
6
+ export declare function guardSharedBulkScript(ids: string[]): string;
7
+ export declare function guardSharedScript(id: string): string;
8
+ export declare function updateNoteScript(id: string, body: string): string;
9
+ export declare function deleteNoteScript(id: string): string;
10
+ export declare function listFoldersScript(): string;
11
+ export declare function createFolderScript(name: string, account?: string): string;
12
+ export declare function scanNotesScript(limit: number, previewLength: number, offset: number, folder?: string): string;
13
+ export declare function compareNotesScript(ids: string[]): string;
14
+ export declare function moveNoteScript(id: string, targetFolder: string): string;
15
+ export declare function bulkMoveNotesScript(ids: string[], targetFolder: string): string;
@@ -0,0 +1,348 @@
1
+ // JXA scripts for Apple Notes automation.
2
+ // Each function returns a JXA script string to be executed via osascript.
3
+ export function listNotesScript(folder) {
4
+ if (folder) {
5
+ return `
6
+ const Notes = Application('Notes');
7
+ const folders = Notes.folders.whose({name: '${esc(folder)}'})();
8
+ if (folders.length === 0) throw new Error('Folder not found: ${esc(folder)}');
9
+ const f = folders[0];
10
+ const names = f.notes.name();
11
+ const ids = f.notes.id();
12
+ const creationDates = f.notes.creationDate();
13
+ const modificationDates = f.notes.modificationDate();
14
+ const shareds = f.notes.shared();
15
+ const result = names.map((name, i) => ({
16
+ id: ids[i],
17
+ name: name,
18
+ folder: '${esc(folder)}',
19
+ creationDate: creationDates[i].toISOString(),
20
+ modificationDate: modificationDates[i].toISOString(),
21
+ shared: shareds[i]
22
+ }));
23
+ JSON.stringify(result);
24
+ `;
25
+ }
26
+ return `
27
+ const Notes = Application('Notes');
28
+ const names = Notes.notes.name();
29
+ const ids = Notes.notes.id();
30
+ const creationDates = Notes.notes.creationDate();
31
+ const modificationDates = Notes.notes.modificationDate();
32
+ const containers = Notes.notes.container();
33
+ const shareds = Notes.notes.shared();
34
+ const result = names.map((name, i) => ({
35
+ id: ids[i],
36
+ name: name,
37
+ folder: containers[i].name(),
38
+ creationDate: creationDates[i].toISOString(),
39
+ modificationDate: modificationDates[i].toISOString(),
40
+ shared: shareds[i]
41
+ }));
42
+ JSON.stringify(result);
43
+ `;
44
+ }
45
+ export function searchNotesScript(query, limit) {
46
+ return `
47
+ const Notes = Application('Notes');
48
+ const names = Notes.notes.name();
49
+ const ids = Notes.notes.id();
50
+ const plaintexts = Notes.notes.plaintext();
51
+ const creationDates = Notes.notes.creationDate();
52
+ const modificationDates = Notes.notes.modificationDate();
53
+ const containers = Notes.notes.container();
54
+ const shareds = Notes.notes.shared();
55
+ const q = '${esc(query)}'.toLowerCase();
56
+ const result = [];
57
+ for (let i = 0; i < names.length && result.length < ${limit}; i++) {
58
+ if (names[i].toLowerCase().includes(q) || plaintexts[i].toLowerCase().includes(q)) {
59
+ result.push({
60
+ id: ids[i],
61
+ name: names[i],
62
+ folder: containers[i].name(),
63
+ creationDate: creationDates[i].toISOString(),
64
+ modificationDate: modificationDates[i].toISOString(),
65
+ preview: plaintexts[i].substring(0, 200),
66
+ shared: shareds[i]
67
+ });
68
+ }
69
+ }
70
+ JSON.stringify({total: names.length, returned: result.length, notes: result});
71
+ `;
72
+ }
73
+ export function readNoteScript(id) {
74
+ return `
75
+ const Notes = Application('Notes');
76
+ const note = Notes.notes.byId('${esc(id)}');
77
+ JSON.stringify({
78
+ id: note.id(),
79
+ name: note.name(),
80
+ body: note.body(),
81
+ plaintext: note.plaintext(),
82
+ creationDate: note.creationDate().toISOString(),
83
+ modificationDate: note.modificationDate().toISOString(),
84
+ folder: note.container().name(),
85
+ shared: note.shared(),
86
+ passwordProtected: note.passwordProtected()
87
+ });
88
+ `;
89
+ }
90
+ export function createNoteScript(body, folder) {
91
+ if (folder) {
92
+ return `
93
+ const Notes = Application('Notes');
94
+ const folders = Notes.folders.whose({name: '${esc(folder)}'})();
95
+ if (folders.length === 0) throw new Error('Folder not found: ${esc(folder)}');
96
+ if (folders[0].shared()) throw new Error('Cannot create notes in shared folder: ${esc(folder)}. Set IMCP_INCLUDE_SHARED=true to allow.');
97
+ const note = Notes.Note({body: '${esc(body)}'});
98
+ folders[0].notes.push(note);
99
+ JSON.stringify({id: note.id(), name: note.name()});
100
+ `;
101
+ }
102
+ return `
103
+ const Notes = Application('Notes');
104
+ const note = Notes.Note({body: '${esc(body)}'});
105
+ Notes.defaultAccount().defaultFolder().notes.push(note);
106
+ JSON.stringify({id: note.id(), name: note.name()});
107
+ `;
108
+ }
109
+ export function createNoteSharedScript(body, folder) {
110
+ if (folder) {
111
+ return `
112
+ const Notes = Application('Notes');
113
+ const folders = Notes.folders.whose({name: '${esc(folder)}'})();
114
+ if (folders.length === 0) throw new Error('Folder not found: ${esc(folder)}');
115
+ const note = Notes.Note({body: '${esc(body)}'});
116
+ folders[0].notes.push(note);
117
+ JSON.stringify({id: note.id(), name: note.name()});
118
+ `;
119
+ }
120
+ return `
121
+ const Notes = Application('Notes');
122
+ const note = Notes.Note({body: '${esc(body)}'});
123
+ Notes.defaultAccount().defaultFolder().notes.push(note);
124
+ JSON.stringify({id: note.id(), name: note.name()});
125
+ `;
126
+ }
127
+ export function guardSharedBulkScript(ids) {
128
+ const idsArray = ids.map((id) => `'${esc(id)}'`).join(",");
129
+ return `
130
+ const Notes = Application('Notes');
131
+ const ids = [${idsArray}];
132
+ const sharedIds = ids.filter(id => Notes.notes.byId(id).shared());
133
+ JSON.stringify({sharedIds: sharedIds});
134
+ `;
135
+ }
136
+ export function guardSharedScript(id) {
137
+ return `
138
+ const Notes = Application('Notes');
139
+ const note = Notes.notes.byId('${esc(id)}');
140
+ JSON.stringify({shared: note.shared()});
141
+ `;
142
+ }
143
+ export function updateNoteScript(id, body) {
144
+ return `
145
+ const Notes = Application('Notes');
146
+ const note = Notes.notes.byId('${esc(id)}');
147
+ note.body = '${esc(body)}';
148
+ JSON.stringify({id: note.id(), name: note.name()});
149
+ `;
150
+ }
151
+ export function deleteNoteScript(id) {
152
+ return `
153
+ const Notes = Application('Notes');
154
+ const note = Notes.notes.byId('${esc(id)}');
155
+ const name = note.name();
156
+ Notes.delete(note);
157
+ JSON.stringify({deleted: true, name: name});
158
+ `;
159
+ }
160
+ export function listFoldersScript() {
161
+ return `
162
+ const Notes = Application('Notes');
163
+ const accounts = Notes.accounts();
164
+ const result = [];
165
+ for (const acct of accounts) {
166
+ const aName = acct.name();
167
+ const fIds = acct.folders.id();
168
+ const fNames = acct.folders.name();
169
+ const fShared = acct.folders.shared();
170
+ for (let i = 0; i < fIds.length; i++) {
171
+ result.push({
172
+ id: fIds[i],
173
+ name: fNames[i],
174
+ account: aName,
175
+ noteCount: acct.folders[i].notes.length,
176
+ shared: fShared[i]
177
+ });
178
+ }
179
+ }
180
+ JSON.stringify(result);
181
+ `;
182
+ }
183
+ export function createFolderScript(name, account) {
184
+ if (account) {
185
+ return `
186
+ const Notes = Application('Notes');
187
+ const acct = Notes.accounts.whose({name: '${esc(account)}'})()[0];
188
+ if (!acct) throw new Error('Account not found: ${esc(account)}');
189
+ const existing = acct.folders.whose({name: '${esc(name)}'})();
190
+ if (existing.length > 0) JSON.stringify({id: existing[0].id(), name: existing[0].name(), existing: true});
191
+ else {
192
+ const folder = Notes.Folder({name: '${esc(name)}'});
193
+ acct.folders.push(folder);
194
+ JSON.stringify({id: folder.id(), name: folder.name(), existing: false});
195
+ }
196
+ `;
197
+ }
198
+ return `
199
+ const Notes = Application('Notes');
200
+ const acct = Notes.defaultAccount();
201
+ const existing = acct.folders.whose({name: '${esc(name)}'})();
202
+ if (existing.length > 0) JSON.stringify({id: existing[0].id(), name: existing[0].name(), existing: true});
203
+ else {
204
+ const folder = Notes.Folder({name: '${esc(name)}'});
205
+ acct.folders.push(folder);
206
+ JSON.stringify({id: folder.id(), name: folder.name(), existing: false});
207
+ }
208
+ `;
209
+ }
210
+ export function scanNotesScript(limit, previewLength, offset, folder) {
211
+ if (folder) {
212
+ return `
213
+ const Notes = Application('Notes');
214
+ const folders = Notes.folders.whose({name: '${esc(folder)}'})();
215
+ if (folders.length === 0) throw new Error('Folder not found: ${esc(folder)}');
216
+ const f = folders[0];
217
+ const names = f.notes.name();
218
+ const ids = f.notes.id();
219
+ const creationDates = f.notes.creationDate();
220
+ const modificationDates = f.notes.modificationDate();
221
+ const shareds = f.notes.shared();
222
+ const start = Math.min(${offset}, names.length);
223
+ const end = Math.min(start + ${limit}, names.length);
224
+ const result = [];
225
+ for (let i = start; i < end; i++) {
226
+ const pt = f.notes[i].plaintext();
227
+ result.push({
228
+ id: ids[i],
229
+ name: names[i],
230
+ folder: '${esc(folder)}',
231
+ creationDate: creationDates[i].toISOString(),
232
+ modificationDate: modificationDates[i].toISOString(),
233
+ preview: pt.substring(0, ${previewLength}),
234
+ charCount: pt.length,
235
+ shared: shareds[i]
236
+ });
237
+ }
238
+ JSON.stringify({total: names.length, offset: start, returned: result.length, notes: result});
239
+ `;
240
+ }
241
+ return `
242
+ const Notes = Application('Notes');
243
+ const names = Notes.notes.name();
244
+ const ids = Notes.notes.id();
245
+ const creationDates = Notes.notes.creationDate();
246
+ const modificationDates = Notes.notes.modificationDate();
247
+ const containers = Notes.notes.container();
248
+ const shareds = Notes.notes.shared();
249
+ const start = Math.min(${offset}, names.length);
250
+ const end = Math.min(start + ${limit}, names.length);
251
+ const result = [];
252
+ for (let i = start; i < end; i++) {
253
+ const pt = Notes.notes[i].plaintext();
254
+ result.push({
255
+ id: ids[i],
256
+ name: names[i],
257
+ folder: containers[i].name(),
258
+ creationDate: creationDates[i].toISOString(),
259
+ modificationDate: modificationDates[i].toISOString(),
260
+ preview: pt.substring(0, ${previewLength}),
261
+ charCount: pt.length,
262
+ shared: shareds[i]
263
+ });
264
+ }
265
+ JSON.stringify({total: names.length, offset: start, returned: result.length, notes: result});
266
+ `;
267
+ }
268
+ export function compareNotesScript(ids) {
269
+ const idsArray = ids.map((id) => `'${esc(id)}'`).join(",");
270
+ return `
271
+ const Notes = Application('Notes');
272
+ const targetIds = [${idsArray}];
273
+ const result = targetIds.map(id => {
274
+ const note = Notes.notes.byId(id);
275
+ const text = note.plaintext();
276
+ return {
277
+ id: note.id(),
278
+ name: note.name(),
279
+ plaintext: text,
280
+ folder: note.container().name(),
281
+ creationDate: note.creationDate().toISOString(),
282
+ modificationDate: note.modificationDate().toISOString(),
283
+ charCount: text.length,
284
+ shared: note.shared()
285
+ };
286
+ });
287
+ JSON.stringify(result);
288
+ `;
289
+ }
290
+ export function moveNoteScript(id, targetFolder) {
291
+ return `
292
+ const Notes = Application('Notes');
293
+ const note = Notes.notes.byId('${esc(id)}');
294
+ const folders = Notes.folders.whose({name: '${esc(targetFolder)}'})();
295
+ if (folders.length === 0) throw new Error('Folder not found: ${esc(targetFolder)}');
296
+ const body = note.body();
297
+ const originalName = note.name();
298
+ const newNote = Notes.Note({body: body});
299
+ folders[0].notes.push(newNote);
300
+ const newId = newNote.id();
301
+ if (!newId) throw new Error('Failed to create note in target folder');
302
+ Notes.delete(note);
303
+ JSON.stringify({
304
+ originalName: originalName,
305
+ newId: newId,
306
+ newName: newNote.name(),
307
+ targetFolder: '${esc(targetFolder)}'
308
+ });
309
+ `;
310
+ }
311
+ export function bulkMoveNotesScript(ids, targetFolder) {
312
+ const idsArray = ids.map((id) => `'${esc(id)}'`).join(",");
313
+ return `
314
+ const Notes = Application('Notes');
315
+ const folders = Notes.folders.whose({name: '${esc(targetFolder)}'})();
316
+ if (folders.length === 0) throw new Error('Folder not found: ${esc(targetFolder)}');
317
+ const targetIds = [${idsArray}];
318
+ const results = [];
319
+ for (const id of targetIds) {
320
+ try {
321
+ const note = Notes.notes.byId(id);
322
+ const body = note.body();
323
+ const originalName = note.name();
324
+ const newNote = Notes.Note({body: body});
325
+ folders[0].notes.push(newNote);
326
+ const newId = newNote.id();
327
+ if (!newId) throw new Error('Failed to create note copy');
328
+ Notes.delete(note);
329
+ results.push({success: true, originalName, newId: newId});
330
+ } catch(e) {
331
+ results.push({success: false, id: id, error: e.message});
332
+ }
333
+ }
334
+ JSON.stringify({targetFolder: '${esc(targetFolder)}', moved: results.filter(r => r.success).length, failed: results.filter(r => !r.success).length, results: results});
335
+ `;
336
+ }
337
+ function esc(str) {
338
+ return str
339
+ .replace(/\0/g, "")
340
+ .replace(/\\/g, "\\\\")
341
+ .replace(/'/g, "\\'")
342
+ .replace(/\n/g, "\\n")
343
+ .replace(/\r/g, "\\r")
344
+ .replace(/\t/g, "\\t")
345
+ .replace(/\u2028/g, "\\u2028")
346
+ .replace(/\u2029/g, "\\u2029");
347
+ }
348
+ //# sourceMappingURL=scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/notes/scripts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ImcpConfig } from "../shared/config.js";
3
+ export declare function registerNoteTools(server: McpServer, config: ImcpConfig): void;
@@ -0,0 +1,325 @@
1
+ import { z } from "zod";
2
+ import { runJxa } from "../shared/jxa.js";
3
+ import { listNotesScript, searchNotesScript, readNoteScript, createNoteScript, createNoteSharedScript, updateNoteScript, deleteNoteScript, guardSharedScript, guardSharedBulkScript, listFoldersScript, createFolderScript, scanNotesScript, compareNotesScript, moveNoteScript, bulkMoveNotesScript, } from "./scripts.js";
4
+ function ok(data) {
5
+ return {
6
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
7
+ };
8
+ }
9
+ function err(message) {
10
+ return {
11
+ content: [{ type: "text", text: message }],
12
+ isError: true,
13
+ };
14
+ }
15
+ function filterShared(items, includeShared) {
16
+ if (includeShared)
17
+ return items;
18
+ return items.filter((item) => !item.shared);
19
+ }
20
+ async function guardShared(id, includeShared) {
21
+ if (includeShared)
22
+ return null;
23
+ const result = await runJxa(guardSharedScript(id));
24
+ if (result.shared) {
25
+ return "This note is shared. Modifying shared notes is disabled by default. Set IMCP_INCLUDE_SHARED=true to allow.";
26
+ }
27
+ return null;
28
+ }
29
+ export function registerNoteTools(server, config) {
30
+ const { includeShared } = config;
31
+ // --- Layer 1: CRUD ---
32
+ server.registerTool("list_notes", {
33
+ title: "List Notes",
34
+ description: "List all notes with title, folder, and dates. Optionally filter by folder name.",
35
+ inputSchema: {
36
+ folder: z.string().optional().describe("Filter by folder name"),
37
+ },
38
+ annotations: {
39
+ readOnlyHint: true,
40
+ destructiveHint: false,
41
+ idempotentHint: true,
42
+ openWorldHint: false,
43
+ },
44
+ }, async ({ folder }) => {
45
+ try {
46
+ const result = await runJxa(listNotesScript(folder));
47
+ return ok(filterShared(result, includeShared));
48
+ }
49
+ catch (e) {
50
+ return err(`Failed to list notes: ${e instanceof Error ? e.message : String(e)}`);
51
+ }
52
+ });
53
+ server.registerTool("search_notes", {
54
+ title: "Search Notes",
55
+ description: "Search notes by keyword in title and body. Returns matching notes with a 200-char preview.",
56
+ inputSchema: {
57
+ query: z.string().describe("Search keyword"),
58
+ limit: z.number().int().min(1).max(500).optional().default(50).describe("Max results to return (default: 50)"),
59
+ },
60
+ annotations: {
61
+ readOnlyHint: true,
62
+ destructiveHint: false,
63
+ idempotentHint: true,
64
+ openWorldHint: false,
65
+ },
66
+ }, async ({ query, limit }) => {
67
+ try {
68
+ const result = await runJxa(searchNotesScript(query, limit));
69
+ result.notes = filterShared(result.notes, includeShared);
70
+ result.returned = result.notes.length;
71
+ return ok(result);
72
+ }
73
+ catch (e) {
74
+ return err(`Failed to search notes: ${e instanceof Error ? e.message : String(e)}`);
75
+ }
76
+ });
77
+ server.registerTool("read_note", {
78
+ title: "Read Note",
79
+ description: "Read the full content of a specific note by its ID. Returns HTML body and plaintext.",
80
+ inputSchema: {
81
+ id: z.string().describe("Note ID (x-coredata:// format)"),
82
+ },
83
+ annotations: {
84
+ readOnlyHint: true,
85
+ destructiveHint: false,
86
+ idempotentHint: true,
87
+ openWorldHint: false,
88
+ },
89
+ }, async ({ id }) => {
90
+ try {
91
+ const result = await runJxa(readNoteScript(id));
92
+ if (!includeShared && result.shared)
93
+ return err("This note is shared. Set IMCP_INCLUDE_SHARED=true to access shared notes.");
94
+ return ok(result);
95
+ }
96
+ catch (e) {
97
+ return err(`Failed to read note: ${e instanceof Error ? e.message : String(e)}`);
98
+ }
99
+ });
100
+ server.registerTool("create_note", {
101
+ title: "Create Note",
102
+ description: "Create a new note with HTML body. The first line of the body becomes the note title automatically. Optionally specify a target folder.",
103
+ inputSchema: {
104
+ body: z.string().describe("Note content in HTML (e.g. '<h1>Title</h1><p>Body text</p>')"),
105
+ folder: z.string().optional().describe("Target folder name"),
106
+ },
107
+ annotations: {
108
+ readOnlyHint: false,
109
+ destructiveHint: false,
110
+ idempotentHint: false,
111
+ openWorldHint: false,
112
+ },
113
+ }, async ({ body, folder }) => {
114
+ try {
115
+ const script = includeShared
116
+ ? createNoteSharedScript(body, folder)
117
+ : createNoteScript(body, folder);
118
+ const result = await runJxa(script);
119
+ return ok(result);
120
+ }
121
+ catch (e) {
122
+ return err(`Failed to create note: ${e instanceof Error ? e.message : String(e)}`);
123
+ }
124
+ });
125
+ server.registerTool("update_note", {
126
+ title: "Update Note",
127
+ description: "Replace the entire body of an existing note. WARNING: This overwrites all content. Read the note first if you need to preserve parts of it. Attachments may be lost.",
128
+ inputSchema: {
129
+ id: z.string().describe("Note ID (x-coredata:// format)"),
130
+ body: z.string().describe("New HTML body to replace existing content"),
131
+ },
132
+ annotations: {
133
+ readOnlyHint: false,
134
+ destructiveHint: true,
135
+ idempotentHint: true,
136
+ openWorldHint: false,
137
+ },
138
+ }, async ({ id, body }) => {
139
+ try {
140
+ const blocked = await guardShared(id, includeShared);
141
+ if (blocked)
142
+ return err(blocked);
143
+ const result = await runJxa(updateNoteScript(id, body));
144
+ return ok(result);
145
+ }
146
+ catch (e) {
147
+ return err(`Failed to update note: ${e instanceof Error ? e.message : String(e)}`);
148
+ }
149
+ });
150
+ server.registerTool("delete_note", {
151
+ title: "Delete Note",
152
+ description: "Delete a note by ID. The note is moved to Recently Deleted and permanently removed after 30 days.",
153
+ inputSchema: {
154
+ id: z.string().describe("Note ID (x-coredata:// format)"),
155
+ },
156
+ annotations: {
157
+ readOnlyHint: false,
158
+ destructiveHint: true,
159
+ idempotentHint: true,
160
+ openWorldHint: false,
161
+ },
162
+ }, async ({ id }) => {
163
+ try {
164
+ const blocked = await guardShared(id, includeShared);
165
+ if (blocked)
166
+ return err(blocked);
167
+ const result = await runJxa(deleteNoteScript(id));
168
+ return ok(result);
169
+ }
170
+ catch (e) {
171
+ return err(`Failed to delete note: ${e instanceof Error ? e.message : String(e)}`);
172
+ }
173
+ });
174
+ server.registerTool("list_folders", {
175
+ title: "List Folders",
176
+ description: "List all folders across all accounts with note counts.",
177
+ inputSchema: {},
178
+ annotations: {
179
+ readOnlyHint: true,
180
+ destructiveHint: false,
181
+ idempotentHint: true,
182
+ openWorldHint: false,
183
+ },
184
+ }, async () => {
185
+ try {
186
+ const result = await runJxa(listFoldersScript());
187
+ return ok(filterShared(result, includeShared));
188
+ }
189
+ catch (e) {
190
+ return err(`Failed to list folders: ${e instanceof Error ? e.message : String(e)}`);
191
+ }
192
+ });
193
+ server.registerTool("create_folder", {
194
+ title: "Create Folder",
195
+ description: "Create a new folder. Optionally specify which account to create it in.",
196
+ inputSchema: {
197
+ name: z.string().describe("Folder name"),
198
+ account: z.string().optional().describe("Account name (e.g. 'iCloud'). Defaults to primary account."),
199
+ },
200
+ annotations: {
201
+ readOnlyHint: false,
202
+ destructiveHint: false,
203
+ idempotentHint: true,
204
+ openWorldHint: false,
205
+ },
206
+ }, async ({ name, account }) => {
207
+ try {
208
+ const result = await runJxa(createFolderScript(name, account));
209
+ return ok(result);
210
+ }
211
+ catch (e) {
212
+ return err(`Failed to create folder: ${e instanceof Error ? e.message : String(e)}`);
213
+ }
214
+ });
215
+ server.registerTool("move_note", {
216
+ title: "Move Note",
217
+ description: "Move a note to a different folder. NOTE: Apple Notes has no native move command, so this copies the note body to the target folder and deletes the original. The note will get a new ID and creation date. Attachments (images) will be lost.",
218
+ inputSchema: {
219
+ id: z.string().describe("Note ID to move"),
220
+ folder: z.string().describe("Target folder name"),
221
+ },
222
+ annotations: {
223
+ readOnlyHint: false,
224
+ destructiveHint: true,
225
+ idempotentHint: false,
226
+ openWorldHint: false,
227
+ },
228
+ }, async ({ id, folder }) => {
229
+ try {
230
+ const blocked = await guardShared(id, includeShared);
231
+ if (blocked)
232
+ return err(blocked);
233
+ const result = await runJxa(moveNoteScript(id, folder));
234
+ return ok(result);
235
+ }
236
+ catch (e) {
237
+ return err(`Failed to move note: ${e instanceof Error ? e.message : String(e)}`);
238
+ }
239
+ });
240
+ // --- Layer 2: Bulk ---
241
+ server.registerTool("scan_notes", {
242
+ title: "Scan Notes",
243
+ description: "Bulk scan notes returning metadata and a text preview for each. Supports pagination via offset. Optionally filter by folder. Use this to get an overview before organizing.",
244
+ inputSchema: {
245
+ folder: z.string().optional().describe("Filter by folder name. Omit to scan all notes."),
246
+ limit: z.number().int().min(1).max(10000).optional().default(100).describe("Max number of notes to return (default: 100)"),
247
+ offset: z.number().int().min(0).optional().default(0).describe("Number of notes to skip for pagination (default: 0)"),
248
+ previewLength: z.number().int().min(1).max(5000).optional().default(300).describe("Preview text length in characters (default: 300)"),
249
+ },
250
+ annotations: {
251
+ readOnlyHint: true,
252
+ destructiveHint: false,
253
+ idempotentHint: true,
254
+ openWorldHint: false,
255
+ },
256
+ }, async ({ folder, limit, offset, previewLength }) => {
257
+ try {
258
+ const result = await runJxa(scanNotesScript(limit, previewLength, offset, folder));
259
+ result.notes = filterShared(result.notes, includeShared);
260
+ result.returned = result.notes.length;
261
+ return ok(result);
262
+ }
263
+ catch (e) {
264
+ return err(`Failed to scan notes: ${e instanceof Error ? e.message : String(e)}`);
265
+ }
266
+ });
267
+ server.registerTool("compare_notes", {
268
+ title: "Compare Notes",
269
+ description: "Retrieve full plaintext content of 2-5 notes at once for comparison. Use this after scan_notes to safely compare potentially duplicate or similar notes before deciding what to keep, merge, or delete.",
270
+ inputSchema: {
271
+ ids: z
272
+ .array(z.string())
273
+ .min(2)
274
+ .max(5)
275
+ .describe("Array of 2-5 note IDs to compare"),
276
+ },
277
+ annotations: {
278
+ readOnlyHint: true,
279
+ destructiveHint: false,
280
+ idempotentHint: true,
281
+ openWorldHint: false,
282
+ },
283
+ }, async ({ ids }) => {
284
+ try {
285
+ const result = await runJxa(compareNotesScript(ids));
286
+ if (!includeShared) {
287
+ const shared = result.filter((n) => n.shared);
288
+ if (shared.length > 0)
289
+ return err(`Shared notes found: ${shared.map((n) => n.name).join(", ")}. Set IMCP_INCLUDE_SHARED=true to access.`);
290
+ }
291
+ return ok(result);
292
+ }
293
+ catch (e) {
294
+ return err(`Failed to compare notes: ${e instanceof Error ? e.message : String(e)}`);
295
+ }
296
+ });
297
+ server.registerTool("bulk_move_notes", {
298
+ title: "Bulk Move Notes",
299
+ description: "Move multiple notes to a target folder at once. Same limitations as move_note apply to each note (new ID, date reset, attachments lost). Returns per-note success/failure results.",
300
+ inputSchema: {
301
+ ids: z.array(z.string()).min(1).describe("Array of note IDs to move"),
302
+ folder: z.string().describe("Target folder name"),
303
+ },
304
+ annotations: {
305
+ readOnlyHint: false,
306
+ destructiveHint: true,
307
+ idempotentHint: false,
308
+ openWorldHint: false,
309
+ },
310
+ }, async ({ ids, folder }) => {
311
+ try {
312
+ if (!includeShared) {
313
+ const { sharedIds } = await runJxa(guardSharedBulkScript(ids));
314
+ if (sharedIds.length > 0)
315
+ return err(`Blocked: ${sharedIds.length} shared note(s) found. Set IMCP_INCLUDE_SHARED=true to allow.`);
316
+ }
317
+ const result = await runJxa(bulkMoveNotesScript(ids, folder));
318
+ return ok(result);
319
+ }
320
+ catch (e) {
321
+ return err(`Failed to bulk move notes: ${e instanceof Error ? e.message : String(e)}`);
322
+ }
323
+ });
324
+ }
325
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/notes/tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export interface ImcpConfig {
2
+ /** Include shared notes/folders in results. Default: false */
3
+ includeShared: boolean;
4
+ }
5
+ export declare function parseConfig(): ImcpConfig;
@@ -0,0 +1,6 @@
1
+ export function parseConfig() {
2
+ return {
3
+ includeShared: process.env.IMCP_INCLUDE_SHARED === "true",
4
+ };
5
+ }
6
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,MAAM;KAC1D,CAAC;AACJ,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function runJxa<T>(script: string): Promise<T>;
@@ -0,0 +1,29 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ const TIMEOUT_MS = 30_000;
5
+ const MAX_BUFFER = 10 * 1024 * 1024; // 10 MB
6
+ export async function runJxa(script) {
7
+ let stdout;
8
+ try {
9
+ const result = await execFileAsync("osascript", ["-l", "JavaScript", "-e", script], { timeout: TIMEOUT_MS, maxBuffer: MAX_BUFFER });
10
+ stdout = result.stdout;
11
+ }
12
+ catch (e) {
13
+ const err = e;
14
+ if (err.killed || err.signal === "SIGTERM") {
15
+ throw new Error(`osascript timed out after ${TIMEOUT_MS / 1000}s`);
16
+ }
17
+ throw e;
18
+ }
19
+ const trimmed = stdout.trim();
20
+ if (!trimmed)
21
+ throw new Error("osascript returned empty output");
22
+ try {
23
+ return JSON.parse(trimmed);
24
+ }
25
+ catch {
26
+ throw new Error(`osascript returned invalid JSON: ${trimmed.slice(0, 200)}`);
27
+ }
28
+ }
29
+ //# sourceMappingURL=jxa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jxa.js","sourceRoot":"","sources":["../../src/shared/jxa.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,UAAU,GAAG,MAAM,CAAC;AAC1B,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ;AAE7C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAI,MAAc;IAC5C,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAChC,WAAW,EACX,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,EAClC,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAC/C,CAAC;QACF,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IACzB,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,CAA2D,CAAC;QACxE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,6BAA6B,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAM,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@heznpc/imcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Apple Notes — full CRUD, bulk scan, and smart comparison. Connect any AI to your Apple ecosystem.",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "apple",
9
+ "notes",
10
+ "apple-notes",
11
+ "claude",
12
+ "llm",
13
+ "ai",
14
+ "applescript",
15
+ "macos"
16
+ ],
17
+ "homepage": "https://github.com/heznpc/iMcp",
18
+ "bugs": {
19
+ "url": "https://github.com/heznpc/iMcp/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/heznpc/iMcp.git"
24
+ },
25
+ "license": "MIT",
26
+ "author": "heznpc",
27
+ "type": "module",
28
+ "main": "dist/index.js",
29
+ "types": "dist/index.d.ts",
30
+ "bin": {
31
+ "imcp": "dist/index.js"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "clean": "rm -rf dist",
39
+ "dev": "tsc --watch",
40
+ "prepublishOnly": "npm run clean && npm run build",
41
+ "start": "node dist/index.js",
42
+ "typecheck": "tsc --noEmit"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.27.1",
46
+ "zod": "^3.24.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.0.0",
50
+ "typescript": "^5.7.0"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "os": [
56
+ "darwin"
57
+ ]
58
+ }