Skip to main content
Glama
wolfiesch

iMessage MCP Server

by wolfiesch
contacts_manager.py6.37 kB
""" Contact management for iMessage MCP server. Sprint 1: Basic contact lookup from JSON config Sprint 2: macOS Contacts sync, fuzzy matching, DB integration """ import json import logging from pathlib import Path from typing import Optional, List, Dict logger = logging.getLogger(__name__) class Contact: """Represents a contact with messaging information.""" def __init__( self, name: str, phone: str, relationship_type: str = "other", notes: str = "" ): self.name = name self.phone = phone self.relationship_type = relationship_type self.notes = notes def __repr__(self): return f"Contact(name='{self.name}', phone='{self.phone}')" def to_dict(self) -> dict: return { "name": self.name, "phone": self.phone, "relationship_type": self.relationship_type, "notes": self.notes } class ContactsManager: """ Manages contact lookup and resolution. Sprint 1: Load from JSON config file Sprint 2: Sync with macOS Contacts and Life Planner database """ def __init__(self, config_path: str = "config/contacts.json"): """ Initialize contacts manager. Args: config_path: Path to contacts configuration file """ self.config_path = Path(config_path) self.contacts: List[Contact] = [] self._load_contacts() def _load_contacts(self): """Load contacts from configuration file.""" if not self.config_path.exists(): logger.warning(f"Contacts config not found: {self.config_path}") logger.warning("Creating empty contacts.json - please add your contacts") self._create_default_config() return try: with open(self.config_path) as f: data = json.load(f) contacts_data = data.get("contacts", []) self.contacts = [ Contact( name=c["name"], phone=c["phone"], relationship_type=c.get("relationship_type", "other"), notes=c.get("notes", "") ) for c in contacts_data ] logger.info(f"Loaded {len(self.contacts)} contacts from config") except Exception as e: logger.error(f"Error loading contacts: {e}") self.contacts = [] def _create_default_config(self): """Create default contacts configuration file.""" default_config = { "_comment": "Manual contact configuration for Sprint 1", "_instructions": "Add your contacts here. Format: +1XXXXXXXXXX", "contacts": [] } self.config_path.parent.mkdir(parents=True, exist_ok=True) with open(self.config_path, 'w') as f: json.dump(default_config, f, indent=2) logger.info(f"Created default config at {self.config_path}") def get_contact_by_name(self, name: str) -> Optional[Contact]: """ Get contact by exact name match. Args: name: Contact name to search for Returns: Contact object if found, None otherwise Note: Sprint 1: Exact match only Sprint 2: Will add fuzzy matching """ # Exact match (case-insensitive) for contact in self.contacts: if contact.name.lower() == name.lower(): logger.info(f"Found contact: {contact.name} -> {contact.phone}") return contact # Try partial match (contains) for contact in self.contacts: if name.lower() in contact.name.lower(): logger.info(f"Partial match: {contact.name} -> {contact.phone}") return contact logger.warning(f"Contact not found: {name}") return None def get_contact_by_phone(self, phone: str) -> Optional[Contact]: """ Get contact by phone number. Args: phone: Phone number to search for Returns: Contact object if found, None otherwise """ # Normalize phone for comparison (remove non-digits) normalized_search = ''.join(c for c in phone if c.isdigit()) for contact in self.contacts: normalized_contact = ''.join(c for c in contact.phone if c.isdigit()) # Match if search phone is suffix of contact phone # (handles +1 country code differences) if (normalized_contact.endswith(normalized_search) or normalized_search.endswith(normalized_contact)): logger.info(f"Found contact by phone: {contact.name}") return contact logger.warning(f"No contact found for phone: {phone}") return None def list_contacts(self) -> List[Contact]: """ Get all contacts. Returns: List of all Contact objects """ return self.contacts def add_contact( self, name: str, phone: str, relationship_type: str = "other", notes: str = "" ) -> Contact: """ Add a new contact (Sprint 1: manual only). Args: name: Contact name phone: Phone number relationship_type: Type of relationship notes: Optional notes Returns: Created Contact object Note: Sprint 1: Only updates JSON config Sprint 2: Will also update Life Planner database """ contact = Contact(name, phone, relationship_type, notes) self.contacts.append(contact) # Save to config self._save_contacts() logger.info(f"Added contact: {name}") return contact def _save_contacts(self): """Save contacts back to configuration file.""" try: with open(self.config_path) as f: data = json.load(f) data["contacts"] = [c.to_dict() for c in self.contacts] with open(self.config_path, 'w') as f: json.dump(data, f, indent=2) logger.info("Saved contacts to config") except Exception as e: logger.error(f"Error saving contacts: {e}")

Latest Blog Posts

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/wolfiesch/imessage-mcp'

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