check_new_messages
Check for new iMessage notifications since your last check. First call establishes a baseline, then subsequent calls report only new arrivals, with options to filter by contact or include text previews.
Instructions
Check for new messages since your last check. First call sets a baseline. Subsequent calls report what arrived since.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| reset | No | Reset baseline to current latest message | |
| include_text | No | Include message text previews (default false) | |
| limit | No | Max messages to return in detail (default 50) | |
| contact | No | Filter to a specific contact |
Implementation Reference
- src/tools/sync.ts:22-155 (handler)The async handler function that executes check_new_messages logic: sets baseline on first call, queries delta messages since lastSeenRowId, filters by contact optionally, returns summary with sender breakdowns and optional message details.
async (params) => { const db = getDb(); const limit = clamp(params.limit ?? 50, 1, MAX_LIMIT); // Get current max ROWID const currentMax: number = (db.prepare("SELECT MAX(ROWID) as max_rowid FROM message").get() as any) ?.max_rowid ?? 0; // First call or explicit reset: set baseline if (params.reset || lastSeenRowId === null) { lastSeenRowId = currentMax; return { content: [ { type: "text", text: JSON.stringify({ status: "baseline_set", baseline_rowid: currentMax }, null, 2), }, ], }; } // Delta query: messages since lastSeenRowId const conditions = baseMessageConditions(); const bindings: Record<string, any> = { lastSeen: lastSeenRowId }; conditions.push("m.ROWID > @lastSeen"); if (params.contact) { const isHandle = /^[+\d]|@/.test(params.contact.trim()); if (isHandle) { conditions.push("h.id LIKE @contact"); bindings.contact = `%${params.contact}%`; } else { const nameKeys = resolveByName(params.contact); if (nameKeys.length > 0) { const orClauses = nameKeys.map((_, i) => `h.id LIKE @nk${i}`); conditions.push(`(${orClauses.join(" OR ")})`); nameKeys.forEach((key, i) => { bindings[`nk${i}`] = `%${key}%`; }); } else { conditions.push("h.id LIKE @contact"); bindings.contact = `%${params.contact}%`; } } } const where = conditions.join(" AND "); const fromJoins = ` FROM message m JOIN chat_message_join cmj ON m.ROWID = cmj.message_id JOIN chat c ON cmj.chat_id = c.ROWID LEFT JOIN handle h ON m.handle_id = h.ROWID`; // Total count const totalRow = db .prepare(`SELECT COUNT(*) as cnt ${fromJoins} WHERE ${where}`) .get(bindings) as any; const totalCount: number = totalRow?.cnt ?? 0; // Per-sender summary (top 10) const senderRows = db .prepare( `SELECT h.id as handle, COUNT(*) as count ${fromJoins} WHERE ${where} GROUP BY h.id ORDER BY count DESC LIMIT 10`, ) .all(bindings) as any[]; const senders = senderRows.map((r: any) => ({ handle: r.handle, name: r.handle ? lookupContact(r.handle).name : "(unknown)", count: r.count, })); // Optional: detailed messages let messages: any[] | undefined; if (params.include_text) { const detailRows = db .prepare( `SELECT m.ROWID as rowid, m.text, m.attributedBody, m.is_from_me, ${DATE_EXPR} as date, h.id as handle, c.display_name as group_name ${fromJoins} WHERE ${where} ORDER BY m.date ASC LIMIT @limit`, ) .all({ ...bindings, limit }) as any[]; messages = detailRows.map((row: any) => { const text = safeText(getMessageText(row)); return { rowid: row.rowid, text, is_from_me: row.is_from_me, date: row.date, handle: row.handle, contact_name: row.handle ? lookupContact(row.handle).name : undefined, group_name: row.group_name, }; }); } // Advance watermark (only when unfiltered — filtered calls shouldn't // skip messages from other contacts) const previousRowId = lastSeenRowId; if (!params.contact) { lastSeenRowId = currentMax; } const result = { status: "delta", new_message_count: totalCount, since_rowid: previousRowId, current_rowid: currentMax, senders, ...(messages ? { messages } : {}), cursor: { after_rowid: previousRowId }, }; return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, - src/tools/sync.ts:15-20 (schema)Zod schema defining input parameters for check_new_messages: reset (boolean), include_text (boolean), limit (number), and contact (string filter).
{ reset: z.boolean().optional().describe("Reset baseline to current latest message"), include_text: z.boolean().optional().describe("Include message text previews (default false)"), limit: z.number().optional().describe("Max messages to return in detail (default 50)"), contact: z.string().optional().describe("Filter to a specific contact"), }, - src/tools/sync.ts:11-157 (registration)The registerSyncTools function that registers the check_new_messages tool with the MCP server, including its name, description, schema, and handler.
export function registerSyncTools(server: McpServer) { server.tool( "check_new_messages", "Check for new messages since your last check. First call sets a baseline. Subsequent calls report what arrived since.", { reset: z.boolean().optional().describe("Reset baseline to current latest message"), include_text: z.boolean().optional().describe("Include message text previews (default false)"), limit: z.number().optional().describe("Max messages to return in detail (default 50)"), contact: z.string().optional().describe("Filter to a specific contact"), }, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (params) => { const db = getDb(); const limit = clamp(params.limit ?? 50, 1, MAX_LIMIT); // Get current max ROWID const currentMax: number = (db.prepare("SELECT MAX(ROWID) as max_rowid FROM message").get() as any) ?.max_rowid ?? 0; // First call or explicit reset: set baseline if (params.reset || lastSeenRowId === null) { lastSeenRowId = currentMax; return { content: [ { type: "text", text: JSON.stringify({ status: "baseline_set", baseline_rowid: currentMax }, null, 2), }, ], }; } // Delta query: messages since lastSeenRowId const conditions = baseMessageConditions(); const bindings: Record<string, any> = { lastSeen: lastSeenRowId }; conditions.push("m.ROWID > @lastSeen"); if (params.contact) { const isHandle = /^[+\d]|@/.test(params.contact.trim()); if (isHandle) { conditions.push("h.id LIKE @contact"); bindings.contact = `%${params.contact}%`; } else { const nameKeys = resolveByName(params.contact); if (nameKeys.length > 0) { const orClauses = nameKeys.map((_, i) => `h.id LIKE @nk${i}`); conditions.push(`(${orClauses.join(" OR ")})`); nameKeys.forEach((key, i) => { bindings[`nk${i}`] = `%${key}%`; }); } else { conditions.push("h.id LIKE @contact"); bindings.contact = `%${params.contact}%`; } } } const where = conditions.join(" AND "); const fromJoins = ` FROM message m JOIN chat_message_join cmj ON m.ROWID = cmj.message_id JOIN chat c ON cmj.chat_id = c.ROWID LEFT JOIN handle h ON m.handle_id = h.ROWID`; // Total count const totalRow = db .prepare(`SELECT COUNT(*) as cnt ${fromJoins} WHERE ${where}`) .get(bindings) as any; const totalCount: number = totalRow?.cnt ?? 0; // Per-sender summary (top 10) const senderRows = db .prepare( `SELECT h.id as handle, COUNT(*) as count ${fromJoins} WHERE ${where} GROUP BY h.id ORDER BY count DESC LIMIT 10`, ) .all(bindings) as any[]; const senders = senderRows.map((r: any) => ({ handle: r.handle, name: r.handle ? lookupContact(r.handle).name : "(unknown)", count: r.count, })); // Optional: detailed messages let messages: any[] | undefined; if (params.include_text) { const detailRows = db .prepare( `SELECT m.ROWID as rowid, m.text, m.attributedBody, m.is_from_me, ${DATE_EXPR} as date, h.id as handle, c.display_name as group_name ${fromJoins} WHERE ${where} ORDER BY m.date ASC LIMIT @limit`, ) .all({ ...bindings, limit }) as any[]; messages = detailRows.map((row: any) => { const text = safeText(getMessageText(row)); return { rowid: row.rowid, text, is_from_me: row.is_from_me, date: row.date, handle: row.handle, contact_name: row.handle ? lookupContact(row.handle).name : undefined, group_name: row.group_name, }; }); } // Advance watermark (only when unfiltered — filtered calls shouldn't // skip messages from other contacts) const previousRowId = lastSeenRowId; if (!params.contact) { lastSeenRowId = currentMax; } const result = { status: "delta", new_message_count: totalCount, since_rowid: previousRowId, current_rowid: currentMax, senders, ...(messages ? { messages } : {}), cursor: { after_rowid: previousRowId }, }; return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); } - src/index.ts:24-24 (registration)Import statement for registerSyncTools from the sync module.
import { registerSyncTools } from "./tools/sync.js"; - src/index.ts:64-64 (registration)Registration call in createServer that invokes registerSyncTools to register the check_new_messages tool with the MCP server.
registerSyncTools(server);