diff --git a/imessage-mcp/src/tools/send-message.ts b/imessage-mcp/src/tools/send-message.ts index c9f7919..83f2543 100644 --- a/imessage-mcp/src/tools/send-message.ts +++ b/imessage-mcp/src/tools/send-message.ts @@ -14,7 +14,8 @@ interface SendMessageParams { delay_seconds?: number; } -const MAX_HOURLY = 15; +const MAX_PER_MINUTE = 2; +const MAX_HOURLY = 120; const MAX_DAILY = 150; // Schema: macsync.send_queue (uuid id, device_id uuid, to_handle, body, status, @@ -52,14 +53,21 @@ export async function sendMessage(params: SendMessageParams): Promise { } // Rate limits — count rows queued/sent in the windows. Cancelled doesn't - // burn quota. + // burn quota. Three tiers: per-minute (burst cap), hourly, daily. + const minuteAgo = new Date(Date.now() - 60_000).toISOString(); const hourAgo = new Date(Date.now() - 3600_000).toISOString(); const dayStart = new Date(); dayStart.setHours(0, 0, 0, 0); + let minuteCount: number; let hourlyCount: number; let dailyCount: number; try { + const minuteResult = await query( + `SELECT COUNT(*)::int AS count FROM macsync.send_queue + WHERE created_at > $1 AND status <> 'cancelled'`, + [minuteAgo], + ); const hourlyResult = await query( `SELECT COUNT(*)::int AS count FROM macsync.send_queue WHERE created_at > $1 AND status <> 'cancelled'`, @@ -70,6 +78,7 @@ export async function sendMessage(params: SendMessageParams): Promise { WHERE created_at > $1 AND status <> 'cancelled'`, [dayStart.toISOString()], ); + minuteCount = (minuteResult.rows[0]?.count as number) ?? 0; hourlyCount = (hourlyResult.rows[0]?.count as number) ?? 0; dailyCount = (dailyResult.rows[0]?.count as number) ?? 0; } catch (err) { @@ -77,9 +86,14 @@ export async function sendMessage(params: SendMessageParams): Promise { throw new Error(`send_message: rate-limit query failed (${msg})`); } + const minuteRemaining = Math.max(0, MAX_PER_MINUTE - minuteCount); const hourlyRemaining = Math.max(0, MAX_HOURLY - hourlyCount); const dailyRemaining = Math.max(0, MAX_DAILY - dailyCount); + if (minuteRemaining === 0) { + return `Rate limit exceeded: ${MAX_PER_MINUTE} messages per minute (burst cap). Try again in ~30s.`; + } + if (hourlyRemaining === 0) { return `Rate limit exceeded: ${MAX_HOURLY} messages per hour. Try again later.`; } @@ -97,7 +111,7 @@ export async function sendMessage(params: SendMessageParams): Promise { `To: ${displayRecipient}`, `Body: ${body}`, '', - `Rate limits: ${hourlyRemaining}/${MAX_HOURLY} hourly, ${dailyRemaining}/${MAX_DAILY} daily`, + `Rate limits: ${minuteRemaining}/${MAX_PER_MINUTE} per-min, ${hourlyRemaining}/${MAX_HOURLY} hourly, ${dailyRemaining}/${MAX_DAILY} daily`, '', 'To send this message, call send_message again with confirm: true', ].join('\n');