Skip to main content
Glama
anipotts

imessage-mcp

by anipotts

get_contact

Retrieve detailed iMessage contact analytics including message statistics, conversation patterns, and recent interactions from local macOS databases.

Instructions

Deep info on a specific contact: tier, message stats, yearly breakdown, and recent messages.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
contactYesContact handle (phone/email) or name fragment

Implementation Reference

  • Complete implementation of get_contact tool: registration with MCP server, schema definition (contact parameter), and handler logic that retrieves contact info, message stats, yearly breakdown, and recent messages from the iMessage database.
    server.tool(
      "get_contact",
      "Deep info on a specific contact: tier, message stats, yearly breakdown, and recent messages.",
      {
        contact: z.string().describe("Contact handle (phone/email) or name fragment"),
      },
      { readOnlyHint: true, destructiveHint: false, openWorldHint: false },
      async (params) => {
        const db = getDb();
        const handle = params.contact;
    
        // Find matching handles
        const handles = db.prepare(`
          SELECT DISTINCT h.id FROM handle h WHERE h.id LIKE @pattern
        `).all({ pattern: `%${handle}%` }) as any[];
    
        if (handles.length === 0) {
          return { content: [{ type: "text", text: `No contact found matching "${handle}"` }] };
        }
    
        const results: any[] = [];
        for (const { id } of handles) {
          const contact = lookupContact(id);
    
          // Overall stats
          const stats = db.prepare(`
            SELECT
              COUNT(*) as total,
              SUM(CASE WHEN m.is_from_me = 1 THEN 1 ELSE 0 END) as sent,
              SUM(CASE WHEN m.is_from_me = 0 THEN 1 ELSE 0 END) as received,
              AVG(LENGTH(m.text)) as avg_length,
              MIN(${DATE_EXPR}) as first_message,
              MAX(${DATE_EXPR}) as last_message
            FROM message m
            JOIN handle h ON m.handle_id = h.ROWID
            WHERE h.id = @id AND (m.text IS NOT NULL OR m.attributedBody IS NOT NULL) ${MSG_FILTER}
          `).get({ id }) as any;
    
          // Yearly breakdown
          const yearly = db.prepare(`
            SELECT
              strftime('%Y', ${DATE_EXPR}) as year,
              COUNT(*) as messages,
              SUM(CASE WHEN m.is_from_me = 1 THEN 1 ELSE 0 END) as sent,
              SUM(CASE WHEN m.is_from_me = 0 THEN 1 ELSE 0 END) as received
            FROM message m
            JOIN handle h ON m.handle_id = h.ROWID
            WHERE h.id = @id AND (m.text IS NOT NULL OR m.attributedBody IS NOT NULL) ${MSG_FILTER}
            GROUP BY year
            ORDER BY year
          `).all({ id });
    
          results.push({
            handle: id,
            name: contact.name,
            tier: contact.tier,
            stats,
            yearly_breakdown: yearly,
          });
        }
    
        return {
          content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
        };
      },
    );
  • lookupContact helper function that resolves contact information from macOS AddressBook, returning name and tier (known/unknown) for a given handle (phone number or email).
    export function lookupContact(handle: string): Contact {
      if (!handle) return { id: "", name: "(unknown)", tier: "unknown" };
      const cleaned = handle.trim();
    
      // Resolve from macOS AddressBook
      const name = resolveFromAddressBook(cleaned);
      if (name) {
        return { id: cleaned, name, tier: "known" };
      }
    
      return { id: cleaned, name: cleaned, tier: "unknown" };
    }
  • src/index.ts:52-52 (registration)
    Registration call that activates all contact tools including get_contact by invoking registerContactTools from the tools module.
    registerContactTools(server);
  • Database constants DATE_EXPR and MSG_FILTER used in get_contact SQL queries for timestamp conversion and message filtering.
    export const DATE_EXPR = `datetime(m.date/1000000000 + ${APPLE_EPOCH_OFFSET}, 'unixepoch', 'localtime')`;
    
    // Filter out tapbacks and object-replacement-character-only messages
    export const MSG_FILTER = `AND m.associated_message_type = 0 AND (m.text IS NOT NULL OR m.attributedBody IS NOT NULL) AND COALESCE(m.text, '') <> '\ufffc' AND COALESCE(m.text, '') NOT LIKE '\ufffc\ufffc%'`;

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

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