Skip to main content
Glama
tool-prompts.test.js24 kB
/** * 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. `) }

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/sfls1397/Apple-Tools-MCP'

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