Skip to main content
Glama

Apple MCP Tools

by wearesage
contacts.ts5.02 kB
import { run } from '@jxa/run'; import { runAppleScript } from 'run-applescript'; // Removed unused import: import { CallToolResponse } from '@modelcontextprotocol/sdk/types.js'; // Define the argument type for the contacts tool (though not used in this file anymore) interface ContactsArgs { name?: string; } async function checkContactsAccess(): Promise<boolean> { try { // Try to get the count of contacts as a simple test await runAppleScript(` tell application "Contacts" count every person end tell`); return true; } catch (error) { throw new Error("Cannot access Contacts app. Please grant access in System Preferences > Security & Privacy > Privacy > Contacts."); } } async function getAllNumbers() { try { if (!await checkContactsAccess()) { return {}; } const nums: { [key: string]: string[] } = await run(() => { const Contacts = Application('Contacts'); const people = Contacts.people(); const phoneNumbers: { [key: string]: string[] } = {}; for (const person of people) { try { const name = person.name(); const phones = person.phones().map((phone: unknown) => (phone as { value: string }).value); if (!phoneNumbers[name]) { phoneNumbers[name] = []; } phoneNumbers[name] = [...phoneNumbers[name], ...phones]; } catch (error) { // Skip contacts that can't be processed } } return phoneNumbers; }); return nums; } catch (error) { throw new Error(`Error accessing contacts: ${error instanceof Error ? error.message : String(error)}`); } } async function findNumber(name: string) { try { if (!await checkContactsAccess()) { return []; } const nums: string[] = await run((name: string) => { const Contacts = Application('Contacts'); const people = Contacts.people.whose({name: {_contains: name}})(); // Ensure it's executed let phoneValues: string[] = []; if (people.length > 0) { const person = people[0]; // Check if phones property exists and is callable if (typeof person.phones === 'function') { const phones = person.phones(); if (Array.isArray(phones)) { phoneValues = phones.map((phone: any) => { // Check if phone object and value property exist if (phone && typeof phone.value === 'function') { const val = phone.value(); return typeof val === 'string' ? val : null; // Return null if not a string } return null; // Return null if phone or value is invalid }).filter((value): value is string => value !== null && value !== ''); // Filter out nulls and empty strings } } } return phoneValues; }, name); // If no numbers found, run getAllNumbers() to find the closest match (changed from getNumbers) if (nums.length === 0) { const allNumbers = await getAllNumbers(); const closestMatch = Object.keys(allNumbers).find(personName => personName.toLowerCase().includes(name.toLowerCase()) ); return closestMatch ? allNumbers[closestMatch] : []; } return nums; } catch (error) { throw new Error(`Error finding contact: ${error instanceof Error ? error.message : String(error)}`); } } async function findContactByPhone(phoneNumber: string): Promise<string | null> { try { if (!await checkContactsAccess()) { return null; } // Normalize the phone number for comparison const searchNumber = phoneNumber.replace(/[^0-9+]/g, ''); // Get all contacts and their numbers const allContacts = await getAllNumbers(); // Look for a match for (const [name, numbers] of Object.entries(allContacts)) { const normalizedNumbers = numbers.map(num => num.replace(/[^0-9+]/g, '')); if (normalizedNumbers.some(num => num === searchNumber || num === `+${searchNumber}` || num === `+1${searchNumber}` || `+1${num}` === searchNumber )) { return name; } } return null; } catch (error) { // Return null instead of throwing to handle gracefully return null; } } // Removed unused handleContactsRequest function export default { getAllNumbers, findNumber, findContactByPhone };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wearesage/mcp-apple'

If you have feedback or need assistance with the MCP directory API, please join our Discord server