@aernoud/contactsmcp 0.2.0 → 0.3.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/dist/index.js +23 -0
- package/dist/tools/getMyCard.d.ts +7 -0
- package/dist/tools/getMyCard.js +31 -0
- package/dist/tools/getVcard.d.ts +3 -0
- package/dist/tools/getVcard.js +18 -0
- package/dist/tools/searchByModificationDate.d.ts +5 -0
- package/dist/tools/searchByModificationDate.js +37 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,6 +14,9 @@ import { createGroup } from "./tools/createGroup.js";
|
|
|
14
14
|
import { deleteGroup } from "./tools/deleteGroup.js";
|
|
15
15
|
import { renameGroup } from "./tools/renameGroup.js";
|
|
16
16
|
import { listGroupMembers } from "./tools/listGroupMembers.js";
|
|
17
|
+
import { getMyCard } from "./tools/getMyCard.js";
|
|
18
|
+
import { getVcard } from "./tools/getVcard.js";
|
|
19
|
+
import { searchByModificationDate } from "./tools/searchByModificationDate.js";
|
|
17
20
|
const server = new McpServer({
|
|
18
21
|
name: "contactsmcp",
|
|
19
22
|
version: "0.1.0",
|
|
@@ -114,5 +117,25 @@ server.tool("list-group-members", "List all contacts in a group in macOS Contact
|
|
|
114
117
|
const members = await listGroupMembers(groupName, limit);
|
|
115
118
|
return { content: [{ type: "text", text: JSON.stringify(members, null, 2) }] };
|
|
116
119
|
});
|
|
120
|
+
server.tool("get-my-card", "Get the user's own contact card from macOS Contacts.app", {}, async () => {
|
|
121
|
+
const card = await getMyCard();
|
|
122
|
+
return { content: [{ type: "text", text: JSON.stringify(card, null, 2) }] };
|
|
123
|
+
});
|
|
124
|
+
server.tool("get-vcard", "Get the vCard 3.0 text for a contact by their Contacts.app ID", {
|
|
125
|
+
contactId: z.string().describe("Contact ID from Contacts.app (from search results)"),
|
|
126
|
+
}, async ({ contactId }) => {
|
|
127
|
+
const result = await getVcard(contactId);
|
|
128
|
+
if (!result) {
|
|
129
|
+
return { content: [{ type: "text", text: "Contact not found." }] };
|
|
130
|
+
}
|
|
131
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
132
|
+
});
|
|
133
|
+
server.tool("search-by-modification-date", "Find contacts modified after a given date in macOS Contacts.app", {
|
|
134
|
+
since: z.string().describe("Date string, e.g. 'January 1, 2026' or '2026-01-01'"),
|
|
135
|
+
limit: z.number().optional().default(50).describe("Max results to return"),
|
|
136
|
+
}, async ({ since, limit }) => {
|
|
137
|
+
const contacts = await searchByModificationDate(since, limit);
|
|
138
|
+
return { content: [{ type: "text", text: JSON.stringify(contacts, null, 2) }] };
|
|
139
|
+
});
|
|
117
140
|
const transport = new StdioServerTransport();
|
|
118
141
|
await server.connect(transport);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runAppleScript, FIELD_SEP } from "@mailappmcp/shared";
|
|
2
|
+
export async function getMyCard() {
|
|
3
|
+
const script = `
|
|
4
|
+
tell application "Contacts"
|
|
5
|
+
set p to my card
|
|
6
|
+
set pId to id of p
|
|
7
|
+
set pName to name of p
|
|
8
|
+
set pEmail to ""
|
|
9
|
+
try
|
|
10
|
+
set pEmail to value of first email of p
|
|
11
|
+
end try
|
|
12
|
+
set pPhone to ""
|
|
13
|
+
try
|
|
14
|
+
set pPhone to value of first phone of p
|
|
15
|
+
end try
|
|
16
|
+
set pOrg to ""
|
|
17
|
+
try
|
|
18
|
+
set pOrg to organization of p
|
|
19
|
+
end try
|
|
20
|
+
return pId & "${FIELD_SEP}" & pName & "${FIELD_SEP}" & pOrg & "${FIELD_SEP}" & pEmail & "${FIELD_SEP}" & pPhone
|
|
21
|
+
end tell`;
|
|
22
|
+
const raw = await runAppleScript(script);
|
|
23
|
+
const parts = raw.split(FIELD_SEP);
|
|
24
|
+
return {
|
|
25
|
+
id: (parts[0] ?? "").trim(),
|
|
26
|
+
name: (parts[1] ?? "").trim(),
|
|
27
|
+
organization: (parts[2] ?? "").trim(),
|
|
28
|
+
email: (parts[3] ?? "").trim(),
|
|
29
|
+
phone: (parts[4] ?? "").trim(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { runAppleScript, escapeForAppleScript } from "@mailappmcp/shared";
|
|
2
|
+
export async function getVcard(contactId) {
|
|
3
|
+
const cId = escapeForAppleScript(contactId);
|
|
4
|
+
const script = `
|
|
5
|
+
tell application "Contacts"
|
|
6
|
+
try
|
|
7
|
+
set p to first person whose id is "${cId}"
|
|
8
|
+
return vcard of p
|
|
9
|
+
on error
|
|
10
|
+
return "NOT_FOUND"
|
|
11
|
+
end try
|
|
12
|
+
end tell`;
|
|
13
|
+
const raw = await runAppleScript(script);
|
|
14
|
+
if (raw.trim() === "NOT_FOUND") {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return { vcard: raw.trim() };
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { runAppleScript, escapeForAppleScript, FIELD_SEP, RECORD_SEP } from "@mailappmcp/shared";
|
|
2
|
+
export async function searchByModificationDate(since, limit) {
|
|
3
|
+
const escapedDate = escapeForAppleScript(since);
|
|
4
|
+
const script = `
|
|
5
|
+
tell application "Contacts"
|
|
6
|
+
set cutoffDate to date "${escapedDate}"
|
|
7
|
+
set output to ""
|
|
8
|
+
set i to 0
|
|
9
|
+
set maxLimit to ${limit}
|
|
10
|
+
repeat with p in every person
|
|
11
|
+
if i >= maxLimit then exit repeat
|
|
12
|
+
if modification date of p > cutoffDate then
|
|
13
|
+
set pId to id of p
|
|
14
|
+
set pName to name of p
|
|
15
|
+
set modDate to modification date of p as string
|
|
16
|
+
set output to output & pId & "${FIELD_SEP}" & pName & "${FIELD_SEP}" & modDate & "${RECORD_SEP}"
|
|
17
|
+
set i to i + 1
|
|
18
|
+
end if
|
|
19
|
+
end repeat
|
|
20
|
+
return output
|
|
21
|
+
end tell`;
|
|
22
|
+
const raw = await runAppleScript(script);
|
|
23
|
+
if (!raw.trim()) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
return raw
|
|
27
|
+
.split(RECORD_SEP)
|
|
28
|
+
.filter((r) => r.trim())
|
|
29
|
+
.map((record) => {
|
|
30
|
+
const fields = record.split(FIELD_SEP);
|
|
31
|
+
return {
|
|
32
|
+
id: (fields[0] ?? "").trim(),
|
|
33
|
+
name: (fields[1] ?? "").trim(),
|
|
34
|
+
modificationDate: (fields[2] ?? "").trim(),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
package/package.json
CHANGED