Litelead-mcp

getContacts Tool

Overview

The getContacts tool retrieves contact records for a specific customer from the Firebase/Firestore database with support for pagination and sorting.

Location

tools/getContacts.js

Description

Retrieves contacts for a specific customer with cursor-based pagination support. Contacts are retrieved from the Firestore path: /Accounts/{accountId}/Customers/{customerId}/Contacts.

Input Schema

{
  customerId: string;                // REQUIRED: Customer ID to fetch contacts for
  limit?: number;                    // Number of records to retrieve (1-100, default: 50)
  cursor?: string;                   // Document ID for cursor-based pagination
  orderBy?: string;                  // Field to order by (default: "contactId")
  orderDirection?: "asc" | "desc";   // Sort direction (default: "asc")
  cursorDirection?: "next" | "previous";  // Direction for pagination (default: "next")
  includeTotal?: boolean;            // Include total count of all contacts (default: false)
}

Parameters

Parameter Type Required Default Description
customerId string Yes - ID of the customer to fetch contacts for
limit number No 50 Maximum number of contacts to return (1-100)
cursor string No - Document ID to start pagination from
orderBy string No “contactId” Field name to sort by
orderDirection enum No “asc” Sort direction: “asc” or “desc”
cursorDirection enum No “next” Pagination direction: “next” or “previous”
includeTotal boolean No false Whether to include total count (expensive)

Output Schema

{
  contacts: Contact[];
  pagination: PaginationMetadata;
}

Contact Object

{
  id: string;                        // Firestore document ID
  path: string;                      // Full Firestore path
  contactId: number;                 // Unique contact ID
  createdAt: string | Date;          // Creation timestamp
  createdBy: string;                 // Creator user ID
  customer: string;                  // Customer reference path
  email: string;                     // Contact email address
  firstName: string;                 // First name
  greeting: string;                  // Greeting/salutation
  imported: boolean;                 // Whether contact was imported
  lastName: string;                  // Last name
  mailchimp: {
    id: string | null;               // Mailchimp ID if synced
  };
  metadata: Record<string, any>;     // Additional metadata
  mobilePhone: string;               // Mobile phone number
  opt_out_email?: boolean;           // Email opt-out status
  opt_out_letter?: boolean;          // Letter opt-out status
  ownedBy: string;                   // Owner user ID
  tags: any[];                       // Contact tags
  telePhone: string;                 // Telephone number
}

Pagination Metadata

{
  limit: number;                 // Applied limit
  hasMore: boolean;              // Whether more results exist
  nextCursor: string | null;     // Cursor for next page
  previousCursor: string | null; // Cursor for previous page
  total?: number | null;         // Total count (if includeTotal was true)
}

Usage Examples

Basic Usage

// Get first 10 contacts for a customer
const result = await getContacts(context, {
  customerId: "customer123",
  limit: 10
});

console.log(result.contacts);      // Array of 10 contacts
console.log(result.pagination);    // Pagination metadata

Paginated Retrieval

// First page
const page1 = await getContacts(context, {
  customerId: "customer123",
  limit: 20,
  orderBy: "contactId",
  orderDirection: "asc"
});

// Next page
const page2 = await getContacts(context, {
  customerId: "customer123",
  limit: 20,
  cursor: page1.pagination.nextCursor,
  cursorDirection: "next"
});

Sort by Email

// Get contacts sorted by email
const result = await getContacts(context, {
  customerId: "customer123",
  limit: 10,
  orderBy: "email",
  orderDirection: "asc"
});

With Total Count

const result = await getContacts(context, {
  customerId: "customer123",
  limit: 10,
  includeTotal: true
});

console.log(`Customer has ${result.pagination.total} total contacts`);

Implementation Details

Data Transformation

The tool transforms Firestore documents by:

  1. Adding the document id and full path
  2. Converting createdAt timestamp to Date object
  3. Preserving all other fields as-is
const transformContact = (doc) => {
  const data = doc.data();
  return {
    id: doc.id,
    path: doc.ref.path,
    ...data,
    createdAt: safeToDate(data.createdAt) || data.createdAt,
  };
};

Validation

The tool validates that customerId is provided:

if (!customerId) {
  throw new Error("The 'customerId' parameter is required.");
}

Default Ordering

By default, contacts are ordered by contactId in ascending order.

Error Handling

Common Errors

Error Cause Solution
“The ‘customerId’ parameter is required.” Missing customerId Provide customerId in params
“Error fetching contacts for customer [id]: [message]” Firestore query failed Check Firebase connection and customer exists
“Failed to fetch contacts for customer [id].” Query error Verify customer ID is valid

Error Response

Errors are logged with the customer ID and re-thrown:

catch (error) {
  console.error(`Error fetching contacts for customer ${customerId}:`, error);
  throw new Error(`Failed to fetch contacts for customer ${customerId}.`);
}

Performance Considerations

  1. Customer Scope: Queries are scoped to a single customer, which improves performance
  2. Limit Size: Keep limit between 1-100 for optimal performance
  3. Total Count: Setting includeTotal: true requires an additional query
  4. Subcollection Queries: Contacts are in a subcollection, which is efficient for per-customer queries

Firestore Collection Structure

/Accounts/{accountId}/Customers/{customerId}/Contacts/{contactId}
  - contactId: number
  - createdAt: timestamp
  - createdBy: string
  - customer: string (reference)
  - email: string
  - firstName: string
  - greeting: string
  - imported: boolean
  - lastName: string
  - mailchimp: { id: string | null }
  - metadata: object
  - mobilePhone: string
  - opt_out_email: boolean
  - opt_out_letter: boolean
  - ownedBy: string
  - tags: array
  - telePhone: string

See Also