@zyx1121/apple-contacts-mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zyx1121
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,47 @@
1
+ # @zyx1121/apple-mail-mcp
2
+
3
+ MCP server for Apple Mail — read, search, and manage emails via Claude Code.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ claude mcp add apple-mail -- npx @zyx1121/apple-mail-mcp
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ - macOS with Apple Mail configured
14
+ - Node.js >= 18
15
+ - First run will prompt for Automation permission (System Settings > Privacy & Security > Automation)
16
+
17
+ ## Tools
18
+
19
+ | Tool | Description |
20
+ |------|-------------|
21
+ | `mail_get_accounts` | List all accounts and their mailboxes |
22
+ | `mail_count_unread` | Count unread messages per account/mailbox |
23
+ | `mail_list_messages` | List messages with filters (account, mailbox, date range, unread) |
24
+ | `mail_read_message` | Read full content of a message by ID |
25
+ | `mail_search` | Search by subject, sender, or both |
26
+ | `mail_mark_read` | Mark a message as read |
27
+
28
+ ## Examples
29
+
30
+ ```
31
+ "List my mail accounts" → mail_get_accounts
32
+ "Show unread count" → mail_count_unread
33
+ "Yesterday's emails" → mail_list_messages { date_from: "2026-03-26" }
34
+ "Search for GitHub emails" → mail_search { query: "GitHub" }
35
+ "Read message 12345" → mail_read_message { message_id: 12345 }
36
+ ```
37
+
38
+ ## Limitations
39
+
40
+ - macOS only (uses AppleScript via `osascript`)
41
+ - Subject search is case-sensitive (AppleScript limitation)
42
+ - Sender/body search fetches recent messages and filters in JS (last 30 days, max 500)
43
+ - Mail.app must be running
44
+
45
+ ## License
46
+
47
+ MIT
@@ -0,0 +1,5 @@
1
+ export declare class AppleMailError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare function runAppleScript(script: string): Promise<string>;
5
+ export declare function escapeForAppleScript(str: string): string;
@@ -0,0 +1,29 @@
1
+ import { execFile } from "node:child_process";
2
+ const TIMEOUT_MS = 60_000;
3
+ export class AppleMailError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "AppleMailError";
7
+ }
8
+ }
9
+ export async function runAppleScript(script) {
10
+ return new Promise((resolve, reject) => {
11
+ execFile("osascript", ["-e", script], { timeout: TIMEOUT_MS }, (err, stdout, stderr) => {
12
+ if (err) {
13
+ const msg = stderr || err.message;
14
+ if (msg.includes("not running") || msg.includes("-600"))
15
+ return reject(new AppleMailError("Apple Mail is not running. Please open Mail.app and try again."));
16
+ if (msg.includes("not allowed") || msg.includes("not permitted"))
17
+ return reject(new AppleMailError("Permission denied. Grant automation access in System Settings > Privacy & Security > Automation."));
18
+ if (msg.includes("timed out") || err.code === "ETIMEDOUT")
19
+ return reject(new AppleMailError("AppleScript execution timed out."));
20
+ return reject(new AppleMailError(msg.trim()));
21
+ }
22
+ resolve(stdout.trimEnd());
23
+ });
24
+ });
25
+ }
26
+ export function escapeForAppleScript(str) {
27
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
28
+ }
29
+ //# sourceMappingURL=applescript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applescript.js","sourceRoot":"","sources":["../src/applescript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,UAAU,GAAG,MAAM,CAAC;AAE1B,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACrF,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC;gBAClC,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACrD,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,gEAAgE,CAAC,CAAC,CAAC;gBACtG,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC;oBAC9D,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,kGAAkG,CAAC,CAAC,CAAC;gBACxI,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAK,GAA6B,CAAC,IAAI,KAAK,WAAW;oBAClF,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,kCAAkC,CAAC,CAAC,CAAC;gBACxE,OAAO,MAAM,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAChD,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,19 @@
1
+ export declare function success(data: unknown): {
2
+ content: {
3
+ type: "text";
4
+ text: string;
5
+ }[];
6
+ };
7
+ export declare function error(message: string): {
8
+ content: {
9
+ type: "text";
10
+ text: string;
11
+ }[];
12
+ isError: true;
13
+ };
14
+ export declare function withErrorHandling<T extends Record<string, unknown>>(fn: (args: T) => Promise<ReturnType<typeof success | typeof error>>): (args: T) => Promise<{
15
+ content: {
16
+ type: "text";
17
+ text: string;
18
+ }[];
19
+ }>;
@@ -0,0 +1,25 @@
1
+ import { AppleMailError } from "./applescript.js";
2
+ export function success(data) {
3
+ return {
4
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
5
+ };
6
+ }
7
+ export function error(message) {
8
+ return {
9
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
10
+ isError: true,
11
+ };
12
+ }
13
+ export function withErrorHandling(fn) {
14
+ return async (args) => {
15
+ try {
16
+ return await fn(args);
17
+ }
18
+ catch (e) {
19
+ if (e instanceof AppleMailError)
20
+ return error(e.message);
21
+ throw e;
22
+ }
23
+ };
24
+ }
25
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,UAAU,OAAO,CAAC,IAAa;IACnC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,OAAe;IACnC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAC9E,OAAO,EAAE,IAAa;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,EAAmE;IAEnE,OAAO,KAAK,EAAE,IAAO,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,cAAc;gBAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACzD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { createServer } from "./server.js";
4
+ if (process.platform !== "darwin") {
5
+ console.error("apple-contacts-mcp requires macOS with Apple Contacts.");
6
+ process.exit(1);
7
+ }
8
+ const server = createServer();
9
+ const transport = new StdioServerTransport();
10
+ await server.connect(transport);
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAClC,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,11 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { registerContactTools } from "./tools/contacts.js";
3
+ export function createServer() {
4
+ const server = new McpServer({
5
+ name: "apple-contacts",
6
+ version: "0.1.0",
7
+ });
8
+ registerContactTools(server);
9
+ return server;
10
+ }
11
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE7B,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerContactTools(server: McpServer): void;
@@ -0,0 +1,239 @@
1
+ import { z } from "zod";
2
+ import { runAppleScript, escapeForAppleScript } from "../applescript.js";
3
+ import { success, error, withErrorHandling } from "../helpers.js";
4
+ const RS = "\u001e"; // ASCII 30 Record Separator
5
+ function parseContact(line) {
6
+ const [id, name, firstName, lastName, org, phones, emails] = line.split("\t");
7
+ return {
8
+ id,
9
+ name,
10
+ firstName: firstName || null,
11
+ lastName: lastName || null,
12
+ organization: org || null,
13
+ phones: phones ? phones.split(",").filter(Boolean).map((p) => {
14
+ const [label, ...rest] = p.split(":");
15
+ return { label, value: rest.join(":") };
16
+ }) : [],
17
+ emails: emails ? emails.split(",").filter(Boolean).map((e) => {
18
+ const [label, ...rest] = e.split(":");
19
+ return { label, value: rest.join(":") };
20
+ }) : [],
21
+ };
22
+ }
23
+ function parseContacts(raw) {
24
+ if (!raw)
25
+ return [];
26
+ return raw.split(RS).filter(Boolean).map(parseContact);
27
+ }
28
+ const CONTACT_FIELDS = `
29
+ set phoneList to ""
30
+ repeat with ph in phones of p
31
+ set phoneList to phoneList & (label of ph) & ":" & (value of ph) & ","
32
+ end repeat
33
+ set emailList to ""
34
+ repeat with em in emails of p
35
+ set emailList to emailList & (label of em) & ":" & (value of em) & ","
36
+ end repeat
37
+ set fn to ""
38
+ try
39
+ if first name of p is not missing value then set fn to first name of p
40
+ end try
41
+ set ln to ""
42
+ try
43
+ if last name of p is not missing value then set ln to last name of p
44
+ end try
45
+ set org to ""
46
+ try
47
+ if organization of p is not missing value then set org to organization of p
48
+ end try`;
49
+ export function registerContactTools(server) {
50
+ // List all contacts
51
+ server.registerTool("contacts_list", {
52
+ description: "List all contacts",
53
+ inputSchema: z.object({
54
+ limit: z.coerce.number().default(50).describe("Max contacts to return (default 50)"),
55
+ }),
56
+ }, withErrorHandling(async ({ limit }) => {
57
+ const raw = await runAppleScript(`
58
+ tell application "Contacts"
59
+ set output to ""
60
+ set cnt to 0
61
+ repeat with p in every person
62
+ if cnt >= ${limit} then exit repeat
63
+ ${CONTACT_FIELDS}
64
+ set output to output & (id of p) & "\\t" & (name of p) & "\\t" & fn & "\\t" & ln & "\\t" & org & "\\t" & phoneList & "\\t" & emailList & (ASCII character 30)
65
+ set cnt to cnt + 1
66
+ end repeat
67
+ return output
68
+ end tell`);
69
+ return success(parseContacts(raw));
70
+ }));
71
+ // Search contacts
72
+ server.registerTool("contacts_search", {
73
+ description: "Search contacts by name, phone, or email",
74
+ inputSchema: z.object({
75
+ query: z.string().min(1).describe("Search keyword"),
76
+ }),
77
+ }, withErrorHandling(async ({ query }) => {
78
+ const esc = escapeForAppleScript(query);
79
+ const raw = await runAppleScript(`
80
+ tell application "Contacts"
81
+ set output to ""
82
+ set nameMatches to every person whose name contains "${esc}"
83
+ set emailMatches to every person whose emails contains "${esc}"
84
+ set phoneMatches to every person whose phones contains "${esc}"
85
+ set seen to {}
86
+ repeat with grp in {nameMatches, emailMatches, phoneMatches}
87
+ repeat with p in grp
88
+ set pid to id of p
89
+ if pid is not in seen then
90
+ set end of seen to pid
91
+ ${CONTACT_FIELDS}
92
+ set output to output & pid & "\\t" & (name of p) & "\\t" & fn & "\\t" & ln & "\\t" & org & "\\t" & phoneList & "\\t" & emailList & (ASCII character 30)
93
+ end if
94
+ end repeat
95
+ end repeat
96
+ return output
97
+ end tell`);
98
+ return success(parseContacts(raw));
99
+ }));
100
+ // Get contact by ID
101
+ server.registerTool("contacts_get", {
102
+ description: "Get full details of a contact by ID",
103
+ inputSchema: z.object({
104
+ id: z.string().describe("Contact ID"),
105
+ }),
106
+ }, withErrorHandling(async ({ id }) => {
107
+ const esc = escapeForAppleScript(id);
108
+ const raw = await runAppleScript(`
109
+ tell application "Contacts"
110
+ set p to person id "${esc}"
111
+ ${CONTACT_FIELDS}
112
+ set addrList to ""
113
+ repeat with a in addresses of p
114
+ set addrList to addrList & (label of a) & ":" & (street of a) & ";"
115
+ end repeat
116
+ set urlList to ""
117
+ repeat with u in urls of p
118
+ set urlList to urlList & (label of u) & ":" & (value of u) & ","
119
+ end repeat
120
+ set bd to ""
121
+ try
122
+ if birth date of p is not missing value then set bd to (birth date of p as string)
123
+ end try
124
+ return (id of p) & "\\t" & (name of p) & "\\t" & fn & "\\t" & ln & "\\t" & org & "\\t" & phoneList & "\\t" & emailList & "\\t" & addrList & "\\t" & urlList & "\\t" & bd
125
+ end tell`);
126
+ if (!raw)
127
+ return error("Contact not found");
128
+ const parts = raw.split("\t");
129
+ const [cid, name, firstName, lastName, organization, phones, emails, addresses, urls, birthDate] = parts;
130
+ return success({
131
+ id: cid,
132
+ name,
133
+ firstName: firstName || null,
134
+ lastName: lastName || null,
135
+ organization: organization || null,
136
+ phones: phones ? phones.split(",").filter(Boolean).map((p) => {
137
+ const [label, ...rest] = p.split(":");
138
+ return { label, value: rest.join(":") };
139
+ }) : [],
140
+ emails: emails ? emails.split(",").filter(Boolean).map((e) => {
141
+ const [label, ...rest] = e.split(":");
142
+ return { label, value: rest.join(":") };
143
+ }) : [],
144
+ addresses: addresses ? addresses.split(";").filter(Boolean).map((a) => {
145
+ const [label, ...rest] = a.split(":");
146
+ return { label, street: rest.join(":") };
147
+ }) : [],
148
+ urls: urls ? urls.split(",").filter(Boolean).map((u) => {
149
+ const [label, ...rest] = u.split(":");
150
+ return { label, value: rest.join(":") };
151
+ }) : [],
152
+ birthDate: birthDate || null,
153
+ });
154
+ }));
155
+ // Create contact
156
+ server.registerTool("contacts_create", {
157
+ description: "Create a new contact",
158
+ inputSchema: z.object({
159
+ first_name: z.string().optional().describe("First name"),
160
+ last_name: z.string().optional().describe("Last name"),
161
+ organization: z.string().optional().describe("Company/organization"),
162
+ phone: z.string().optional().describe("Phone number"),
163
+ phone_label: z.string().default("mobile").describe("Phone label (mobile, home, work, etc.)"),
164
+ email: z.string().optional().describe("Email address"),
165
+ email_label: z.string().default("work").describe("Email label (work, home, etc.)"),
166
+ }),
167
+ }, withErrorHandling(async ({ first_name, last_name, organization, phone, phone_label, email, email_label }) => {
168
+ if (!first_name && !last_name && !organization)
169
+ return error("Provide at least first_name, last_name, or organization");
170
+ const props = [];
171
+ if (first_name)
172
+ props.push(`first name:"${escapeForAppleScript(first_name)}"`);
173
+ if (last_name)
174
+ props.push(`last name:"${escapeForAppleScript(last_name)}"`);
175
+ if (organization)
176
+ props.push(`organization:"${escapeForAppleScript(organization)}"`);
177
+ const phoneCmd = phone
178
+ ? `make new phone at end of phones of newPerson with properties {label:"${escapeForAppleScript(phone_label)}", value:"${escapeForAppleScript(phone)}"}`
179
+ : "";
180
+ const emailCmd = email
181
+ ? `make new email at end of emails of newPerson with properties {label:"${escapeForAppleScript(email_label)}", value:"${escapeForAppleScript(email)}"}`
182
+ : "";
183
+ const raw = await runAppleScript(`
184
+ tell application "Contacts"
185
+ set newPerson to make new person with properties {${props.join(", ")}}
186
+ ${phoneCmd}
187
+ ${emailCmd}
188
+ save
189
+ return (id of newPerson) & "\\t" & (name of newPerson)
190
+ end tell`);
191
+ const [id, name] = raw.split("\t");
192
+ return success({ id, name, created: true });
193
+ }));
194
+ // Update contact
195
+ server.registerTool("contacts_update", {
196
+ description: "Update an existing contact",
197
+ inputSchema: z.object({
198
+ id: z.string().describe("Contact ID"),
199
+ first_name: z.string().optional().describe("New first name"),
200
+ last_name: z.string().optional().describe("New last name"),
201
+ organization: z.string().optional().describe("New organization"),
202
+ }),
203
+ }, withErrorHandling(async ({ id, first_name, last_name, organization }) => {
204
+ const esc = escapeForAppleScript(id);
205
+ const updates = [];
206
+ if (first_name !== undefined)
207
+ updates.push(`set first name of p to "${escapeForAppleScript(first_name)}"`);
208
+ if (last_name !== undefined)
209
+ updates.push(`set last name of p to "${escapeForAppleScript(last_name)}"`);
210
+ if (organization !== undefined)
211
+ updates.push(`set organization of p to "${escapeForAppleScript(organization)}"`);
212
+ if (!updates.length)
213
+ return error("No fields to update");
214
+ await runAppleScript(`
215
+ tell application "Contacts"
216
+ set p to person id "${esc}"
217
+ ${updates.join("\n ")}
218
+ save
219
+ end tell`);
220
+ return success({ id, updated: true });
221
+ }));
222
+ // Delete contact
223
+ server.registerTool("contacts_delete", {
224
+ description: "Delete a contact",
225
+ inputSchema: z.object({
226
+ id: z.string().describe("Contact ID"),
227
+ }),
228
+ }, withErrorHandling(async ({ id }) => {
229
+ const esc = escapeForAppleScript(id);
230
+ await runAppleScript(`
231
+ tell application "Contacts"
232
+ set p to person id "${esc}"
233
+ delete p
234
+ save
235
+ end tell`);
236
+ return success({ id, deleted: true });
237
+ }));
238
+ }
239
+ //# sourceMappingURL=contacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contacts.js","sourceRoot":"","sources":["../../src/tools/contacts.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@zyx1121/apple-contacts-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Apple Contacts — search, read, create, and manage contacts via Claude Code",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "apple-contacts-mcp": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "apple-contacts",
21
+ "claude",
22
+ "contacts",
23
+ "macos"
24
+ ],
25
+ "author": "zyx1121",
26
+ "license": "MIT",
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.12.1",
32
+ "zod": "^3.25.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "typescript": "^5.0.0"
37
+ }
38
+ }