/**
* Tool Prompt Tests - Real data tests using actual prompts
*
* These tests invoke actual tool handlers with real queries against your data.
* They verify that tools return expected structures and reasonable results.
*/
import { describe, it, expect, beforeAll } from 'vitest'
import fs from 'fs'
import path from 'path'
import { connect } from '@lancedb/lancedb'
import { pipeline } from '@xenova/transformers'
import { safeSqlite3Json, safeSqlite3 } from '../../lib/shell.js'
import {
loadContacts,
searchContacts,
lookupContact,
resolveByName
} from '../../contacts.js'
// Real paths
const DATA_DIR = path.join(process.env.HOME, '.apple-tools-mcp')
const DB_PATH = path.join(DATA_DIR, 'vector-index')
const MESSAGES_DB = path.join(process.env.HOME, 'Library', 'Messages', 'chat.db')
const CALENDAR_DB = path.join(process.env.HOME, 'Library', 'Group Containers', 'group.com.apple.calendar', 'Calendar.sqlitedb')
const MAIL_DIR = path.join(process.env.HOME, 'Library', 'Mail')
// Check what exists
const indexExists = fs.existsSync(DB_PATH)
const messagesExists = fs.existsSync(MESSAGES_DB)
const calendarExists = fs.existsSync(CALENDAR_DB)
const mailExists = fs.existsSync(MAIL_DIR)
// Embedding model
let embedder = null
let db = null
async function getEmbedding(text) {
if (!embedder) {
embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2')
}
const output = await embedder(text, { pooling: 'mean', normalize: true })
return Array.from(output.data)
}
// Helper to search a table
async function searchTable(tableName, query, limit = 10) {
if (!db) return []
try {
const tables = await db.tableNames()
if (!tables.includes(tableName)) return []
const table = await db.openTable(tableName)
const embedding = await getEmbedding(query)
return await table.search(embedding).limit(limit).toArray()
} catch (e) {
console.error(`Search error for ${tableName}:`, e.message)
return []
}
}
// ============================================================================
// INDIVIDUAL TOOL TESTS
// ============================================================================
describe.skipIf(!indexExists)('Individual Tool Tests', () => {
beforeAll(async () => {
if (indexExists) {
db = await connect(DB_PATH)
}
})
describe('Smart Search', () => {
it('smart_search: "What happened with EdgeCore this month"', async () => {
// Smart search queries multiple sources
const emailResults = await searchTable('emails', 'EdgeCore', 5)
const calendarResults = await searchTable('calendar', 'EdgeCore', 5)
const messageResults = await searchTable('messages', 'EdgeCore', 5)
const totalResults = emailResults.length + calendarResults.length + messageResults.length
console.log(` → Found ${emailResults.length} emails, ${calendarResults.length} events, ${messageResults.length} messages`)
// Should find something across sources
expect(totalResults).toBeGreaterThanOrEqual(0)
})
})
describe('Mail Tools', () => {
it('mail_search: "Find emails from Anthropic about Claude API"', async () => {
const results = await searchTable('emails', 'Anthropic Claude API', 10)
console.log(` → Found ${results.length} emails`)
if (results.length > 0) {
expect(results[0]).toHaveProperty('subject')
expect(results[0]).toHaveProperty('from')
expect(results[0]).toHaveProperty('filePath')
}
})
it('mail_search: "Find emails about Zillow rent payment"', async () => {
const results = await searchTable('emails', 'Zillow rent payment', 10)
console.log(` → Found ${results.length} emails`)
for (const email of results) {
expect(email).toHaveProperty('subject')
expect(email).toHaveProperty('date')
}
})
it('mail_search with sender filter concept: "emails from Anthropic"', async () => {
const results = await searchTable('emails', 'Anthropic', 20)
const anthropicEmails = results.filter(e =>
(e.from || '').toLowerCase().includes('anthropic')
)
console.log(` → Found ${anthropicEmails.length} emails from Anthropic`)
})
it('mail_search with attachment filter concept', async () => {
const results = await searchTable('emails', 'attachment receipt invoice', 10)
const withAttachments = results.filter(e => e.hasAttachment)
console.log(` → Found ${withAttachments.length} emails with attachments`)
})
it('mail_senders concept: "Who emails me the most"', async () => {
const results = await searchTable('emails', 'email', 100)
const senderCounts = new Map()
for (const email of results) {
const sender = email.from || 'unknown'
senderCounts.set(sender, (senderCounts.get(sender) || 0) + 1)
}
const topSenders = [...senderCounts.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
console.log(` → Top senders: ${topSenders.map(([s, c]) => `${s} (${c})`).join(', ')}`)
expect(senderCounts.size).toBeGreaterThan(0)
})
it('mail_thread concept: "email thread about PaperSurvey.io"', async () => {
const results = await searchTable('emails', 'PaperSurvey.io', 10)
console.log(` → Found ${results.length} emails about PaperSurvey.io`)
if (results.length > 0) {
// Group by subject (simplified thread detection)
const subjects = new Set(results.map(e => e.subject))
console.log(` → Unique subjects: ${subjects.size}`)
}
})
})
describe('Messages Tools', () => {
it('messages_search: "Find messages about church"', async () => {
const results = await searchTable('messages', 'church', 10)
console.log(` → Found ${results.length} messages`)
if (results.length > 0) {
expect(results[0]).toHaveProperty('text')
expect(results[0]).toHaveProperty('chatIdentifier')
}
})
it('messages_search: "messages about Thanksgiving"', async () => {
const results = await searchTable('messages', 'Thanksgiving', 10)
console.log(` → Found ${results.length} messages about Thanksgiving`)
})
it('messages_conversation concept: "conversation with Mom"', async () => {
// First find Mom's contact info
loadContacts()
const momContacts = resolveByName('Mom')
if (momContacts.length > 0) {
const mom = momContacts[0]
console.log(` → Found Mom: ${mom.displayName}`)
// Search for messages mentioning Mom or from her identifiers
const results = await searchTable('messages', 'Mom', 20)
console.log(` → Found ${results.length} messages mentioning Mom`)
} else {
console.log(` → Mom contact not found`)
}
})
it('messages_contacts concept: "Who do I message most often"', async () => {
const results = await searchTable('messages', 'message text', 100)
const contactCounts = new Map()
for (const msg of results) {
const contact = msg.chatIdentifier || msg.sender || 'unknown'
contactCounts.set(contact, (contactCounts.get(contact) || 0) + 1)
}
const topContacts = [...contactCounts.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
console.log(` → Top message contacts: ${topContacts.length}`)
})
})
describe('Calendar Tools', () => {
it('calendar_search: "Find events about AI"', async () => {
const results = await searchTable('calendar', 'AI artificial intelligence', 10)
console.log(` → Found ${results.length} AI-related events`)
if (results.length > 0) {
expect(results[0]).toHaveProperty('title')
}
})
it('calendar_search: "Startup Sioux Falls events"', async () => {
const results = await searchTable('calendar', 'Startup Sioux Falls', 10)
console.log(` → Found ${results.length} Startup Sioux Falls events`)
})
it('calendar_search: "EdgeCore Implementation Session"', async () => {
const results = await searchTable('calendar', 'EdgeCore Implementation Session', 5)
console.log(` → Found ${results.length} EdgeCore sessions`)
if (results.length > 0) {
const event = results[0]
console.log(` → First: "${event.title}" on ${event.start}`)
}
})
it('calendar_recurring concept: "recurring events"', async () => {
const results = await searchTable('calendar', 'weekly meeting recurring standup', 20)
console.log(` → Found ${results.length} potentially recurring events`)
})
})
describe('Contacts Tools', () => {
it('contacts_search: "Find contacts named Coates"', () => {
loadContacts()
const results = searchContacts('Coates', 10)
console.log(` → Found ${results.length} contacts named Coates`)
for (const contact of results) {
console.log(` - ${contact.displayName}`)
expect(contact).toHaveProperty('displayName')
}
})
it('contacts_lookup: "Get contact info for Mom"', () => {
loadContacts()
const contact = lookupContact('Mom')
if (contact) {
console.log(` → Found: ${contact.displayName}`)
console.log(` Emails: ${contact.emails.map(e => e.email).join(', ')}`)
console.log(` Phones: ${contact.phones.map(p => p.phone).join(', ')}`)
expect(contact.displayName).toBeTruthy()
} else {
console.log(` → Mom contact not found`)
}
})
it('contacts_search: "Gayle Coates"', () => {
loadContacts()
const results = searchContacts('Gayle Coates', 5)
console.log(` → Found ${results.length} matches for Gayle Coates`)
})
it('contacts_search: "Lindsey Groth"', () => {
loadContacts()
const results = searchContacts('Lindsey Groth', 5)
console.log(` → Found ${results.length} matches for Lindsey Groth`)
})
it('person_search concept: "all communication with Mom"', async () => {
loadContacts()
const momContacts = resolveByName('Mom')
if (momContacts.length > 0) {
const mom = momContacts[0]
const searchTerms = [mom.displayName, ...mom.emails.map(e => e.email)].join(' ')
const emails = await searchTable('emails', searchTerms, 10)
const messages = await searchTable('messages', searchTerms, 10)
const calendar = await searchTable('calendar', searchTerms, 5)
console.log(` → Mom communication: ${emails.length} emails, ${messages.length} messages, ${calendar.length} events`)
}
})
})
})
// ============================================================================
// COMBINED FUNCTIONALITY TESTS
// ============================================================================
describe.skipIf(!indexExists)('Combined Functionality Tests', () => {
beforeAll(async () => {
if (indexExists && !db) {
db = await connect(DB_PATH)
}
})
describe('Workflow - Morning Briefing', () => {
it('Multi-source overview', async () => {
// Simulate morning briefing
const recentEmails = await searchTable('emails', 'today yesterday recent', 10)
const todayEvents = await searchTable('calendar', 'meeting today schedule', 10)
const recentMessages = await searchTable('messages', 'recent message', 10)
console.log(` → Briefing: ${recentEmails.length} emails, ${todayEvents.length} events, ${recentMessages.length} messages`)
expect(recentEmails).toBeDefined()
expect(todayEvents).toBeDefined()
expect(recentMessages).toBeDefined()
})
})
describe('Workflow - Meeting Prep', () => {
it('"EdgeCore Implementation Session Dec 4th - help me prepare"', async () => {
// Find the event
const events = await searchTable('calendar', 'EdgeCore Implementation Session', 5)
// Find related emails
const emails = await searchTable('emails', 'EdgeCore implementation', 10)
console.log(` → Found ${events.length} EdgeCore events and ${emails.length} related emails`)
})
})
describe('Workflow - Contact Research', () => {
it('"Tell me everything about Mom"', async () => {
loadContacts()
const mom = lookupContact('Mom')
if (mom) {
const emails = await searchTable('emails', mom.displayName, 10)
const messages = await searchTable('messages', mom.displayName, 10)
const events = await searchTable('calendar', mom.displayName, 5)
console.log(` → Mom research: Contact found, ${emails.length} emails, ${messages.length} messages, ${events.length} events`)
}
})
})
describe('Cross-Source', () => {
it('"emails about Stuart, read most recent, show full thread"', async () => {
const emails = await searchTable('emails', 'Stuart', 10)
console.log(` → Found ${emails.length} emails about Stuart`)
if (emails.length > 0) {
const mostRecent = emails[0]
console.log(` → Most recent: "${mostRecent.subject}" from ${mostRecent.from}`)
expect(mostRecent.filePath).toBeDefined()
}
})
it('"emails and calendar events related to Jack Henry or Synapsys"', async () => {
const jackHenryEmails = await searchTable('emails', 'Jack Henry Synapsys', 10)
const jackHenryEvents = await searchTable('calendar', 'Jack Henry Synapsys', 10)
console.log(` → Jack Henry/Synapsys: ${jackHenryEmails.length} emails, ${jackHenryEvents.length} events`)
})
it('"Look up Gayle Coates and find messages with her"', async () => {
loadContacts()
const gayle = lookupContact('Gayle Coates')
if (gayle) {
console.log(` → Found Gayle: ${gayle.displayName}`)
const messages = await searchTable('messages', 'Gayle Coates', 10)
console.log(` → Found ${messages.length} messages`)
} else {
console.log(` → Gayle Coates not found in contacts`)
}
})
})
describe('Tool Chaining', () => {
it('"Find email about Ben and licensing - start broad, narrow down"', async () => {
// Broad search
const broad = await searchTable('emails', 'Ben licensing', 20)
console.log(` → Broad search: ${broad.length} results`)
// Narrower search
const narrow = await searchTable('emails', 'Ben license agreement contract', 10)
console.log(` → Narrow search: ${narrow.length} results`)
})
it('"Show upcoming Startup Sioux Falls events"', async () => {
const events = await searchTable('calendar', 'Startup Sioux Falls', 10)
console.log(` → Found ${events.length} Startup Sioux Falls events`)
for (const event of events.slice(0, 3)) {
console.log(` - ${event.title} (${event.start})`)
}
})
})
})
// ============================================================================
// EDGE CASES AND ERROR HANDLING
// ============================================================================
describe.skipIf(!indexExists)('Edge Cases and Error Handling', () => {
beforeAll(async () => {
if (indexExists && !db) {
db = await connect(DB_PATH)
}
})
describe('No Results', () => {
it('"Find emails about quantum computing research"', async () => {
const results = await searchTable('emails', 'quantum computing research', 10)
console.log(` → Found ${results.length} results (may be 0)`)
// This may return 0 results - that's OK
expect(Array.isArray(results)).toBe(true)
})
it('"Look up John Smith 12345" (contact not found)', () => {
loadContacts()
const contact = lookupContact('John Smith 12345')
console.log(` → Contact found: ${contact ? 'yes' : 'no'}`)
// Expected to not find this
})
it('"Events on January 15 2026" (future date with no events)', async () => {
const results = await searchTable('calendar', 'January 2026', 10)
console.log(` → Found ${results.length} results`)
})
})
describe('Ambiguous Query', () => {
it('"Find messages from Peter" (multiple matching contacts)', async () => {
loadContacts()
const peters = searchContacts('Peter', 10)
console.log(` → Found ${peters.length} contacts named Peter`)
const messages = await searchTable('messages', 'Peter', 10)
console.log(` → Found ${messages.length} messages mentioning Peter`)
})
it('"Find that email from last week" (vague request)', async () => {
const results = await searchTable('emails', 'last week recent', 10)
console.log(` → Found ${results.length} recent emails`)
})
it('"Search contacts for Smith" (common name)', () => {
loadContacts()
const results = searchContacts('Smith', 20)
console.log(` → Found ${results.length} contacts with Smith`)
})
})
describe('Large Results', () => {
it('"Show all emails from November"', async () => {
const results = await searchTable('emails', 'November', 50)
console.log(` → Found ${results.length} November-related emails (limited to 50)`)
})
it('"Full conversation history with Mom"', async () => {
const results = await searchTable('messages', 'Mom', 100)
console.log(` → Found ${results.length} messages mentioning Mom`)
})
})
describe('Semantic Understanding', () => {
it('"Find emails about API problems or issues"', async () => {
const results = await searchTable('emails', 'API problems issues errors bugs', 10)
console.log(` → Found ${results.length} emails about API issues`)
})
it('"Find emails about money or payment"', async () => {
const results = await searchTable('emails', 'money payment invoice bill receipt', 10)
console.log(` → Found ${results.length} financial emails`)
})
})
describe('Similar Names', () => {
it('"Find all Coates family members"', () => {
loadContacts()
const results = searchContacts('Coates', 20)
console.log(` → Found ${results.length} Coates contacts:`)
for (const contact of results) {
console.log(` - ${contact.displayName}`)
}
})
})
describe('Filter Edge Cases', () => {
it('"Events from Startup Sioux Falls calendar only"', async () => {
const results = await searchTable('calendar', 'Startup Sioux Falls', 20)
const filtered = results.filter(e =>
(e.calendar || '').toLowerCase().includes('startup')
)
console.log(` → Found ${filtered.length} events from Startup calendar`)
})
it('"Weekend free time" (Saturday schedule)', async () => {
const results = await searchTable('calendar', 'Saturday weekend', 10)
console.log(` → Found ${results.length} weekend events`)
})
})
})
// ============================================================================
// INSTRUCTIONS BEHAVIOR TESTS
// ============================================================================
describe.skipIf(!indexExists)('Instructions Behavior Tests', () => {
beforeAll(async () => {
if (indexExists && !db) {
db = await connect(DB_PATH)
}
})
describe('Tool Selection - Quick Query', () => {
it('Should use calendar for "Whats on my calendar today"', async () => {
const results = await searchTable('calendar', 'today schedule meeting', 10)
console.log(` → Calendar query returned ${results.length} events`)
// This validates the calendar table is queryable
})
it('Should use calendar for "next few appointments"', async () => {
const results = await searchTable('calendar', 'appointment meeting event', 10)
console.log(` → Found ${results.length} upcoming appointments`)
})
})
describe('Tool Selection - Deep Investigation', () => {
it('person_search for "Research Gayle Coates before I call her"', async () => {
loadContacts()
const gayle = lookupContact('Gayle Coates')
if (gayle) {
const emails = await searchTable('emails', 'Gayle Coates', 5)
const messages = await searchTable('messages', 'Gayle Coates', 5)
const events = await searchTable('calendar', 'Gayle Coates', 3)
console.log(` → Gayle research: Contact found, ${emails.length} emails, ${messages.length} messages, ${events.length} events`)
} else {
console.log(` → Gayle not found - would show no results`)
}
})
it('mail_thread for "full email conversation about Off Cycle Release"', async () => {
const results = await searchTable('emails', 'Off Cycle Release', 20)
console.log(` → Found ${results.length} emails about Off Cycle Release`)
// Group by subject to simulate thread
if (results.length > 0) {
const subjects = [...new Set(results.map(e => e.subject))]
console.log(` → Unique subjects (threads): ${subjects.length}`)
}
})
})
describe('Date Handling', () => {
it('"meetings next Tuesday" (natural language)', async () => {
const results = await searchTable('calendar', 'Tuesday meeting', 10)
console.log(` → Found ${results.length} Tuesday meetings`)
})
it('"emails from last Wednesday" (relative date)', async () => {
const results = await searchTable('emails', 'Wednesday', 10)
console.log(` → Found ${results.length} Wednesday-related emails`)
})
})
describe('Filter Combinations', () => {
it('"emails from Anthropic in the last week"', async () => {
const results = await searchTable('emails', 'Anthropic', 20)
const anthropicEmails = results.filter(e =>
(e.from || '').toLowerCase().includes('anthropic')
)
console.log(` → Found ${anthropicEmails.length} Anthropic emails`)
})
it('"emails with attachments about receipts"', async () => {
const results = await searchTable('emails', 'receipt attachment invoice', 20)
const withAttachments = results.filter(e => e.hasAttachment)
console.log(` → Found ${withAttachments.length} receipt emails with attachments`)
})
it('"messages from Mom about Thanksgiving"', async () => {
const results = await searchTable('messages', 'Mom Thanksgiving', 10)
console.log(` → Found ${results.length} Thanksgiving messages`)
})
})
describe('Response Format', () => {
it('"Search emails for Notion and summarize"', async () => {
const results = await searchTable('emails', 'Notion', 10)
console.log(` → Found ${results.length} Notion emails to summarize`)
if (results.length > 0) {
const subjects = results.map(e => e.subject).slice(0, 5)
console.log(` → Topics: ${subjects.join('; ')}`)
}
})
it('"Find all communication about IDEA JAM"', async () => {
const emails = await searchTable('emails', 'IDEA JAM', 10)
const calendar = await searchTable('calendar', 'IDEA JAM', 5)
console.log(` → IDEA JAM: ${emails.length} emails, ${calendar.length} events`)
})
})
describe('Efficient Chaining', () => {
it('"Tell me about Lindsey Groth"', async () => {
loadContacts()
const lindsey = lookupContact('Lindsey Groth')
if (lindsey) {
console.log(` → Contact: ${lindsey.displayName}`)
console.log(` Emails: ${lindsey.emails.length}`)
console.log(` Phones: ${lindsey.phones.length}`)
const emails = await searchTable('emails', 'Lindsey Groth', 5)
const messages = await searchTable('messages', 'Lindsey Groth', 5)
console.log(` → Communication: ${emails.length} emails, ${messages.length} messages`)
} else {
console.log(` → Lindsey Groth not found`)
}
})
})
})
// Print summary if index doesn't exist
if (!indexExists) {
console.log(`
⚠️ Tool prompt tests skipped - LanceDB index not found at ${DB_PATH}
To run these tests, ensure your MCP server has built the index.
`)
}