@goforgeit/mcp-apple-notes 0.5.2
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 +50 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +249 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Forge
|
|
2
|
+
Copyright (c) 2025 Prometheus Group LLC. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
PROPRIETARY LICENSE
|
|
5
|
+
|
|
6
|
+
This software and associated documentation files (the "Software") are the
|
|
7
|
+
exclusive property of Prometheus Group LLC. The Software is protected by
|
|
8
|
+
copyright laws and international treaty provisions.
|
|
9
|
+
|
|
10
|
+
GRANT OF ACCESS
|
|
11
|
+
|
|
12
|
+
Access to this Software is granted solely for the purpose of evaluation,
|
|
13
|
+
testing, and providing feedback to Prometheus Group LLC. This access does
|
|
14
|
+
not constitute a transfer of ownership, license to use commercially, or
|
|
15
|
+
any other rights not expressly stated herein.
|
|
16
|
+
|
|
17
|
+
RESTRICTIONS
|
|
18
|
+
|
|
19
|
+
You may NOT:
|
|
20
|
+
- Use the Software for commercial purposes without explicit written permission
|
|
21
|
+
- Copy, modify, merge, publish, distribute, sublicense, or sell copies of the Software
|
|
22
|
+
- Reverse engineer, decompile, or disassemble the Software
|
|
23
|
+
- Remove or alter any proprietary notices, labels, or marks on the Software
|
|
24
|
+
- Claim ownership or authorship of the Software or any derivative works
|
|
25
|
+
|
|
26
|
+
FEEDBACK
|
|
27
|
+
|
|
28
|
+
Any feedback, suggestions, ideas, or improvements you provide regarding the
|
|
29
|
+
Software shall become the exclusive property of Prometheus Group LLC. You
|
|
30
|
+
hereby assign all rights, title, and interest in such feedback to Prometheus
|
|
31
|
+
Group LLC without any obligation of compensation or attribution.
|
|
32
|
+
|
|
33
|
+
NO WARRANTY
|
|
34
|
+
|
|
35
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
36
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
37
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
38
|
+
PROMETHEUS GROUP LLC BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
39
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
40
|
+
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
41
|
+
|
|
42
|
+
TERMINATION
|
|
43
|
+
|
|
44
|
+
This access may be terminated at any time by Prometheus Group LLC without
|
|
45
|
+
prior notice. Upon termination, you must destroy all copies of the Software
|
|
46
|
+
in your possession.
|
|
47
|
+
|
|
48
|
+
CONTACT
|
|
49
|
+
|
|
50
|
+
For licensing inquiries, please visit: https://goforgeit.com
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @goforgeit/mcp-apple-notes
|
|
5
|
+
*
|
|
6
|
+
* Forge MCP server for Apple Notes on macOS.
|
|
7
|
+
* Lists, searches, reads, and creates notes via AppleScript.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface AppleNotesMCPServer {
|
|
11
|
+
server: McpServer;
|
|
12
|
+
getRegisteredTools(): string[];
|
|
13
|
+
start(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
declare function runAppleScript(script: string): Promise<string>;
|
|
16
|
+
declare function createAppleNotesMCPServer(): AppleNotesMCPServer;
|
|
17
|
+
|
|
18
|
+
export { type AppleNotesMCPServer, createAppleNotesMCPServer, runAppleScript };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { execFile } from "child_process";
|
|
8
|
+
import { promisify } from "util";
|
|
9
|
+
var execFileAsync = promisify(execFile);
|
|
10
|
+
async function runAppleScript(script) {
|
|
11
|
+
const { stdout } = await execFileAsync("osascript", ["-e", script]);
|
|
12
|
+
return stdout.trim();
|
|
13
|
+
}
|
|
14
|
+
function createAppleNotesMCPServer() {
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "@goforgeit/mcp-apple-notes",
|
|
17
|
+
version: "0.1.0"
|
|
18
|
+
});
|
|
19
|
+
const registeredTools = [];
|
|
20
|
+
server.tool(
|
|
21
|
+
"list_notes",
|
|
22
|
+
"List notes from Apple Notes app with optional folder filtering",
|
|
23
|
+
{
|
|
24
|
+
folder: z.string().optional().describe('Folder name to filter notes (e.g., "Notes", "Work")'),
|
|
25
|
+
limit: z.number().optional().default(20).describe("Maximum number of notes to return")
|
|
26
|
+
},
|
|
27
|
+
async ({ folder, limit }) => {
|
|
28
|
+
const folderClause = folder ? `notes of folder ${JSON.stringify(folder)}` : "notes";
|
|
29
|
+
const script = `
|
|
30
|
+
tell application "Notes"
|
|
31
|
+
set notesList to {}
|
|
32
|
+
set targetNotes to ${folderClause}
|
|
33
|
+
set noteCount to 0
|
|
34
|
+
repeat with aNote in targetNotes
|
|
35
|
+
if noteCount < ${limit} then
|
|
36
|
+
set noteInfo to "{\\"name\\":\\"" & (name of aNote as string) & "\\",\\"id\\":\\"" & (id of aNote as string) & "\\",\\"creation_date\\":\\"" & (creation date of aNote as string) & "\\",\\"modification_date\\":\\"" & (modification date of aNote as string) & "\\"}"
|
|
37
|
+
set end of notesList to noteInfo
|
|
38
|
+
set noteCount to noteCount + 1
|
|
39
|
+
else
|
|
40
|
+
exit repeat
|
|
41
|
+
end if
|
|
42
|
+
end repeat
|
|
43
|
+
return "[" & my joinList(notesList, ",") & "]"
|
|
44
|
+
end tell
|
|
45
|
+
|
|
46
|
+
on joinList(theList, delimiter)
|
|
47
|
+
set AppleScript's text item delimiters to delimiter
|
|
48
|
+
set theString to theList as string
|
|
49
|
+
set AppleScript's text item delimiters to ""
|
|
50
|
+
return theString
|
|
51
|
+
end joinList
|
|
52
|
+
`;
|
|
53
|
+
try {
|
|
54
|
+
const results = await runAppleScript(script);
|
|
55
|
+
return { content: [{ type: "text", text: results }] };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Failed to list notes: ${error instanceof Error ? error.message : String(error)}`
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
isError: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
registeredTools.push("list_notes");
|
|
70
|
+
server.tool(
|
|
71
|
+
"get_note",
|
|
72
|
+
"Get the full content of a specific note by name",
|
|
73
|
+
{
|
|
74
|
+
note_name: z.string().describe("Name of the note to retrieve"),
|
|
75
|
+
folder: z.string().optional().describe("Folder where the note is located")
|
|
76
|
+
},
|
|
77
|
+
async ({ note_name, folder }) => {
|
|
78
|
+
const targetClause = folder ? `first note of folder ${JSON.stringify(folder)} whose name is ${JSON.stringify(note_name)}` : `first note whose name is ${JSON.stringify(note_name)}`;
|
|
79
|
+
const script = `
|
|
80
|
+
tell application "Notes"
|
|
81
|
+
set targetNote to ${targetClause}
|
|
82
|
+
set noteName to name of targetNote as string
|
|
83
|
+
set noteBody to body of targetNote as string
|
|
84
|
+
set noteId to id of targetNote as string
|
|
85
|
+
set noteCreated to creation date of targetNote as string
|
|
86
|
+
set noteModified to modification date of targetNote as string
|
|
87
|
+
return "{\\"name\\":\\"" & noteName & "\\",\\"id\\":\\"" & noteId & "\\",\\"creation_date\\":\\"" & noteCreated & "\\",\\"modification_date\\":\\"" & noteModified & "\\",\\"body\\":\\"" & noteBody & "\\"}"
|
|
88
|
+
end tell
|
|
89
|
+
`;
|
|
90
|
+
try {
|
|
91
|
+
const result = await runAppleScript(script);
|
|
92
|
+
return { content: [{ type: "text", text: result }] };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Failed to get note: ${error instanceof Error ? error.message : String(error)}`
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
isError: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
registeredTools.push("get_note");
|
|
107
|
+
server.tool(
|
|
108
|
+
"search_notes",
|
|
109
|
+
"Search notes by text query across all folders",
|
|
110
|
+
{
|
|
111
|
+
query: z.string().describe("Text to search for in note names and bodies"),
|
|
112
|
+
limit: z.number().optional().default(10).describe("Maximum number of results to return")
|
|
113
|
+
},
|
|
114
|
+
async ({ query, limit }) => {
|
|
115
|
+
const script = `
|
|
116
|
+
tell application "Notes"
|
|
117
|
+
set notesList to {}
|
|
118
|
+
set noteCount to 0
|
|
119
|
+
repeat with aNote in notes
|
|
120
|
+
if noteCount < ${limit} then
|
|
121
|
+
set noteName to name of aNote as string
|
|
122
|
+
set noteBody to plaintext of aNote as string
|
|
123
|
+
if noteName contains ${JSON.stringify(query)} or noteBody contains ${JSON.stringify(query)} then
|
|
124
|
+
set noteInfo to "{\\"name\\":\\"" & noteName & "\\",\\"id\\":\\"" & (id of aNote as string) & "\\",\\"creation_date\\":\\"" & (creation date of aNote as string) & "\\",\\"modification_date\\":\\"" & (modification date of aNote as string) & "\\"}"
|
|
125
|
+
set end of notesList to noteInfo
|
|
126
|
+
set noteCount to noteCount + 1
|
|
127
|
+
end if
|
|
128
|
+
else
|
|
129
|
+
exit repeat
|
|
130
|
+
end if
|
|
131
|
+
end repeat
|
|
132
|
+
return "[" & my joinList(notesList, ",") & "]"
|
|
133
|
+
end tell
|
|
134
|
+
|
|
135
|
+
on joinList(theList, delimiter)
|
|
136
|
+
set AppleScript's text item delimiters to delimiter
|
|
137
|
+
set theString to theList as string
|
|
138
|
+
set AppleScript's text item delimiters to ""
|
|
139
|
+
return theString
|
|
140
|
+
end joinList
|
|
141
|
+
`;
|
|
142
|
+
try {
|
|
143
|
+
const results = await runAppleScript(script);
|
|
144
|
+
return { content: [{ type: "text", text: results }] };
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: `Failed to search notes: ${error instanceof Error ? error.message : String(error)}`
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
isError: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
registeredTools.push("search_notes");
|
|
159
|
+
server.tool(
|
|
160
|
+
"create_note",
|
|
161
|
+
"Create a new note in Apple Notes",
|
|
162
|
+
{
|
|
163
|
+
title: z.string().describe("Title of the new note"),
|
|
164
|
+
body: z.string().describe("Content/body of the new note"),
|
|
165
|
+
folder: z.string().optional().default("Notes").describe("Folder to create the note in")
|
|
166
|
+
},
|
|
167
|
+
async ({ title, body, folder }) => {
|
|
168
|
+
const script = `
|
|
169
|
+
tell application "Notes"
|
|
170
|
+
set targetFolder to folder ${JSON.stringify(folder)}
|
|
171
|
+
set newNote to make new note at targetFolder with properties {name:${JSON.stringify(title)}, body:${JSON.stringify(body)}}
|
|
172
|
+
return "Note created successfully: " & (name of newNote as string)
|
|
173
|
+
end tell
|
|
174
|
+
`;
|
|
175
|
+
try {
|
|
176
|
+
const result = await runAppleScript(script);
|
|
177
|
+
return { content: [{ type: "text", text: result }] };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return {
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: "text",
|
|
183
|
+
text: `Failed to create note: ${error instanceof Error ? error.message : String(error)}`
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
isError: true
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
registeredTools.push("create_note");
|
|
192
|
+
server.tool("list_folders", "List all folders in Apple Notes", {}, async () => {
|
|
193
|
+
const script = `
|
|
194
|
+
tell application "Notes"
|
|
195
|
+
set foldersList to {}
|
|
196
|
+
repeat with aFolder in folders
|
|
197
|
+
set folderInfo to "{\\"name\\":\\"" & (name of aFolder as string) & "\\",\\"id\\":\\"" & (id of aFolder as string) & "\\",\\"note_count\\":" & (count of notes of aFolder) & "}"
|
|
198
|
+
set end of foldersList to folderInfo
|
|
199
|
+
end repeat
|
|
200
|
+
return "[" & my joinList(foldersList, ",") & "]"
|
|
201
|
+
end tell
|
|
202
|
+
|
|
203
|
+
on joinList(theList, delimiter)
|
|
204
|
+
set AppleScript's text item delimiters to delimiter
|
|
205
|
+
set theString to theList as string
|
|
206
|
+
set AppleScript's text item delimiters to ""
|
|
207
|
+
return theString
|
|
208
|
+
end joinList
|
|
209
|
+
`;
|
|
210
|
+
try {
|
|
211
|
+
const results = await runAppleScript(script);
|
|
212
|
+
return { content: [{ type: "text", text: results }] };
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: "text",
|
|
218
|
+
text: `Failed to list folders: ${error instanceof Error ? error.message : String(error)}`
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
isError: true
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
registeredTools.push("list_folders");
|
|
226
|
+
return {
|
|
227
|
+
server,
|
|
228
|
+
getRegisteredTools() {
|
|
229
|
+
return [...registeredTools];
|
|
230
|
+
},
|
|
231
|
+
async start() {
|
|
232
|
+
const transport = new StdioServerTransport();
|
|
233
|
+
await server.connect(transport);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
var isMainModule = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
|
|
238
|
+
if (isMainModule || process.env.FORGE_MCP_START === "true") {
|
|
239
|
+
const mcpServer = createAppleNotesMCPServer();
|
|
240
|
+
mcpServer.start().catch((err) => {
|
|
241
|
+
console.error("Failed to start Apple Notes MCP server:", err);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
createAppleNotesMCPServer,
|
|
247
|
+
runAppleScript
|
|
248
|
+
};
|
|
249
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @goforgeit/mcp-apple-notes\n *\n * Forge MCP server for Apple Notes on macOS.\n * Lists, searches, reads, and creates notes via AppleScript.\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface AppleNotesMCPServer {\n server: McpServer;\n getRegisteredTools(): string[];\n start(): Promise<void>;\n}\n\nexport async function runAppleScript(script: string): Promise<string> {\n const { stdout } = await execFileAsync('osascript', ['-e', script]);\n return stdout.trim();\n}\n\nexport function createAppleNotesMCPServer(): AppleNotesMCPServer {\n const server = new McpServer({\n name: '@goforgeit/mcp-apple-notes',\n version: '0.1.0',\n });\n\n const registeredTools: string[] = [];\n\n // --- list_notes ---\n server.tool(\n 'list_notes',\n 'List notes from Apple Notes app with optional folder filtering',\n {\n folder: z.string().optional().describe('Folder name to filter notes (e.g., \"Notes\", \"Work\")'),\n limit: z.number().optional().default(20).describe('Maximum number of notes to return'),\n },\n async ({ folder, limit }) => {\n const folderClause = folder ? `notes of folder ${JSON.stringify(folder)}` : 'notes';\n\n const script = `\n tell application \"Notes\"\n set notesList to {}\n set targetNotes to ${folderClause}\n set noteCount to 0\n repeat with aNote in targetNotes\n if noteCount < ${limit} then\n set noteInfo to \"{\\\\\"name\\\\\":\\\\\"\" & (name of aNote as string) & \"\\\\\",\\\\\"id\\\\\":\\\\\"\" & (id of aNote as string) & \"\\\\\",\\\\\"creation_date\\\\\":\\\\\"\" & (creation date of aNote as string) & \"\\\\\",\\\\\"modification_date\\\\\":\\\\\"\" & (modification date of aNote as string) & \"\\\\\"}\"\n set end of notesList to noteInfo\n set noteCount to noteCount + 1\n else\n exit repeat\n end if\n end repeat\n return \"[\" & my joinList(notesList, \",\") & \"]\"\n end tell\n\n on joinList(theList, delimiter)\n set AppleScript's text item delimiters to delimiter\n set theString to theList as string\n set AppleScript's text item delimiters to \"\"\n return theString\n end joinList\n `;\n\n try {\n const results = await runAppleScript(script);\n return { content: [{ type: 'text' as const, text: results }] };\n } catch (error) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to list notes: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n registeredTools.push('list_notes');\n\n // --- get_note ---\n server.tool(\n 'get_note',\n 'Get the full content of a specific note by name',\n {\n note_name: z.string().describe('Name of the note to retrieve'),\n folder: z.string().optional().describe('Folder where the note is located'),\n },\n async ({ note_name, folder }) => {\n const targetClause = folder\n ? `first note of folder ${JSON.stringify(folder)} whose name is ${JSON.stringify(note_name)}`\n : `first note whose name is ${JSON.stringify(note_name)}`;\n\n const script = `\n tell application \"Notes\"\n set targetNote to ${targetClause}\n set noteName to name of targetNote as string\n set noteBody to body of targetNote as string\n set noteId to id of targetNote as string\n set noteCreated to creation date of targetNote as string\n set noteModified to modification date of targetNote as string\n return \"{\\\\\"name\\\\\":\\\\\"\" & noteName & \"\\\\\",\\\\\"id\\\\\":\\\\\"\" & noteId & \"\\\\\",\\\\\"creation_date\\\\\":\\\\\"\" & noteCreated & \"\\\\\",\\\\\"modification_date\\\\\":\\\\\"\" & noteModified & \"\\\\\",\\\\\"body\\\\\":\\\\\"\" & noteBody & \"\\\\\"}\"\n end tell\n `;\n\n try {\n const result = await runAppleScript(script);\n return { content: [{ type: 'text' as const, text: result }] };\n } catch (error) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to get note: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n registeredTools.push('get_note');\n\n // --- search_notes ---\n server.tool(\n 'search_notes',\n 'Search notes by text query across all folders',\n {\n query: z.string().describe('Text to search for in note names and bodies'),\n limit: z.number().optional().default(10).describe('Maximum number of results to return'),\n },\n async ({ query, limit }) => {\n const script = `\n tell application \"Notes\"\n set notesList to {}\n set noteCount to 0\n repeat with aNote in notes\n if noteCount < ${limit} then\n set noteName to name of aNote as string\n set noteBody to plaintext of aNote as string\n if noteName contains ${JSON.stringify(query)} or noteBody contains ${JSON.stringify(query)} then\n set noteInfo to \"{\\\\\"name\\\\\":\\\\\"\" & noteName & \"\\\\\",\\\\\"id\\\\\":\\\\\"\" & (id of aNote as string) & \"\\\\\",\\\\\"creation_date\\\\\":\\\\\"\" & (creation date of aNote as string) & \"\\\\\",\\\\\"modification_date\\\\\":\\\\\"\" & (modification date of aNote as string) & \"\\\\\"}\"\n set end of notesList to noteInfo\n set noteCount to noteCount + 1\n end if\n else\n exit repeat\n end if\n end repeat\n return \"[\" & my joinList(notesList, \",\") & \"]\"\n end tell\n\n on joinList(theList, delimiter)\n set AppleScript's text item delimiters to delimiter\n set theString to theList as string\n set AppleScript's text item delimiters to \"\"\n return theString\n end joinList\n `;\n\n try {\n const results = await runAppleScript(script);\n return { content: [{ type: 'text' as const, text: results }] };\n } catch (error) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to search notes: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n registeredTools.push('search_notes');\n\n // --- create_note ---\n server.tool(\n 'create_note',\n 'Create a new note in Apple Notes',\n {\n title: z.string().describe('Title of the new note'),\n body: z.string().describe('Content/body of the new note'),\n folder: z.string().optional().default('Notes').describe('Folder to create the note in'),\n },\n async ({ title, body, folder }) => {\n const script = `\n tell application \"Notes\"\n set targetFolder to folder ${JSON.stringify(folder)}\n set newNote to make new note at targetFolder with properties {name:${JSON.stringify(title)}, body:${JSON.stringify(body)}}\n return \"Note created successfully: \" & (name of newNote as string)\n end tell\n `;\n\n try {\n const result = await runAppleScript(script);\n return { content: [{ type: 'text' as const, text: result }] };\n } catch (error) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to create note: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n registeredTools.push('create_note');\n\n // --- list_folders ---\n server.tool('list_folders', 'List all folders in Apple Notes', {}, async () => {\n const script = `\n tell application \"Notes\"\n set foldersList to {}\n repeat with aFolder in folders\n set folderInfo to \"{\\\\\"name\\\\\":\\\\\"\" & (name of aFolder as string) & \"\\\\\",\\\\\"id\\\\\":\\\\\"\" & (id of aFolder as string) & \"\\\\\",\\\\\"note_count\\\\\":\" & (count of notes of aFolder) & \"}\"\n set end of foldersList to folderInfo\n end repeat\n return \"[\" & my joinList(foldersList, \",\") & \"]\"\n end tell\n\n on joinList(theList, delimiter)\n set AppleScript's text item delimiters to delimiter\n set theString to theList as string\n set AppleScript's text item delimiters to \"\"\n return theString\n end joinList\n `;\n\n try {\n const results = await runAppleScript(script);\n return { content: [{ type: 'text' as const, text: results }] };\n } catch (error) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to list folders: ${error instanceof Error ? error.message : String(error)}`,\n },\n ],\n isError: true,\n };\n }\n });\n registeredTools.push('list_folders');\n\n return {\n server,\n getRegisteredTools() {\n return [...registeredTools];\n },\n async start() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n },\n };\n}\n\n// CLI entry point\nconst isMainModule =\n process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\\\/g, '/'));\n\nif (isMainModule || process.env.FORGE_MCP_START === 'true') {\n const mcpServer = createAppleNotesMCPServer();\n mcpServer.start().catch((err) => {\n console.error('Failed to start Apple Notes MCP server:', err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@goforgeit/mcp-apple-notes",
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Forge MCP server for Apple Notes — list, search, read, and create notes via macOS Notes app",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-apple-notes": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
20
|
+
"zod": "^4.2.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.10.2",
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"vitest": "^3.2.4"
|
|
26
|
+
},
|
|
27
|
+
"os": [
|
|
28
|
+
"darwin"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run"
|
|
37
|
+
}
|
|
38
|
+
}
|