@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 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,7 @@
1
+ export declare function getMyCard(): Promise<{
2
+ id: string;
3
+ name: string;
4
+ organization: string;
5
+ email: string;
6
+ phone: string;
7
+ }>;
@@ -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,3 @@
1
+ export declare function getVcard(contactId: string): Promise<{
2
+ vcard: string;
3
+ } | null>;
@@ -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,5 @@
1
+ export declare function searchByModificationDate(since: string, limit: number): Promise<{
2
+ id: string;
3
+ name: string;
4
+ modificationDate: string;
5
+ }[]>;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aernoud/contactsmcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for macOS Contacts.app — contact management via AppleScript",
5
5
  "mcpName": "io.github.aernouddekker/contactsmcp",
6
6
  "type": "module",