add-record-to-list
Add companies or people to CRM lists like sales pipelines and lead lists to organize customer data and track business relationships.
Instructions
Add a company or person to a CRM list (sales pipeline, lead list, etc.)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| initialValues | No | Initial values for the list entry (e.g., {"stage": "Prospect"}) | |
| listId | Yes | ID of the list to add the record to | |
| objectType | Yes | Type of record (e.g., "companies", "people") | |
| recordId | Yes | ID of the record to add to the list |
Implementation Reference
- src/objects/lists/entries.ts:42-171 (handler)Core handler that implements the logic to add a record to a list via Attio API, including input validation, generic API attempt with fallback to direct POST /lists/{listId}/entries, extensive logging, and validation error handling.export async function addRecordToList( listId: string, recordId: string, objectType: string, initialValues?: ListEntryValues ): Promise<AttioListEntry> { if (!listId || typeof listId !== 'string') { throw new Error('Invalid list ID: Must be a non-empty string'); } if (!recordId || typeof recordId !== 'string') { throw new Error('Invalid record ID: Must be a non-empty string'); } if (!objectType || typeof objectType !== 'string') { throw new Error( 'Object type is required: Must be a non-empty string (e.g., "companies", "people")' ); } if (!Object.values(ResourceType).includes(objectType as ResourceType)) { const validTypes = Object.values(ResourceType).join(', '); throw new Error( `Invalid object type: "${objectType}". Must be one of: ${validTypes}` ); } const resourceType = objectType as ResourceType; try { return await addGenericRecordToList( listId, recordId, resourceType, initialValues ); } catch (error: unknown) { if (process.env.NODE_ENV === 'development') { const log = createScopedLogger('objects.lists', 'addRecordToList'); log.warn( 'Generic addRecordToList failed; falling back to direct implementation', { listId, recordId, message: error instanceof Error ? error.message : 'Unknown error', } ); } const api = getLazyAttioClient(); const path = `/lists/${listId}/entries`; const payload: ListEntryCreatePayload = { data: { parent_record_id: recordId, parent_object: resourceType, }, }; if (initialValues && Object.keys(initialValues).length > 0) { payload.data.entry_values = initialValues; } if (process.env.NODE_ENV === 'development') { const log = createScopedLogger('objects.lists', 'addRecordToList'); log.info('Fallback request payload', { path, payload }); log.debug('Object Type', { objectType }); if (initialValues) { log.debug('Initial Values', { initialValues }); } } try { const response = await api.post(path, payload); if (process.env.NODE_ENV === 'development') { const log = createScopedLogger('objects.lists', 'addRecordToList'); log.info('Fallback success response', { data: response.data || {}, }); } return extract<AttioListEntry>(response); } catch (fallbackError: unknown) { if (process.env.NODE_ENV === 'development') { const log = createScopedLogger('objects.lists', 'addRecordToList'); log.warn('Error adding record to list (fallback path)', { listId, recordId, message: fallbackError instanceof Error ? fallbackError.message : 'Unknown error', status: hasErrorResponse(fallbackError) ? fallbackError.response?.status : undefined, data: hasErrorResponse(fallbackError) ? fallbackError.response?.data || {} : undefined, validationErrors: hasErrorResponse(fallbackError) ? fallbackError.response?.data?.validation_errors : undefined, }); } if ( hasErrorResponse(fallbackError) && fallbackError.response?.status === 400 ) { const validationErrors = fallbackError.response?.data?.validation_errors || []; const errorDetails = validationErrors .map((validationError) => { return `${validationError.path?.join('.') || 'unknown'}: ${ validationError.message || 'unknown' }`; }) .join('; '); throw new Error( `Validation error adding record to list: ${ errorDetails || getErrorMessage(fallbackError) || 'Unknown error' }` ); } throw fallbackError; } } }
- src/handlers/tool-configs/lists.ts:105-140 (registration)Tool registration configuration including wrapper handler with UUID validation, ID params for approval, and result formatter that JSON.stringifies the AttioListEntry.addRecordToList: { name: 'add-record-to-list', handler: async ( listId: string, recordId: string, objectType: string, values?: Record<string, unknown> ) => { // UUID validation - hard fail for invalid list IDs if (!isValidUUID(listId)) { return { isError: true, content: [ { type: 'text', text: `Invalid list_id: must be a UUID. Got: ${listId}`, }, ], }; } return await addRecordToList(listId, recordId, objectType, values); }, idParams: ['listId', 'recordId'], formatResult: ( result: | AttioListEntry | { isError: boolean; content: Array<Record<string, unknown>> } ) => { // Handle validation error response if (result && typeof result === 'object' && 'isError' in result) { return 'Error: Invalid list ID'; } // Return JSON string return JSON.stringify(result); }, } as ToolConfig,
- Tool schema definition including inputSchema with properties for listId, recordId, objectType (enum companies/people), optional initialValues, required fields, and formatted description.{ name: 'add-record-to-list', description: formatToolDescription({ capability: 'Add company or person to list with optional initial values.', boundaries: 'create records; record must exist first.', requiresApproval: true, constraints: 'Requires list UUID, record UUID, object type.', recoveryHint: 'If not found, create record first with create-record.', }), inputSchema: { type: 'object', properties: { listId: { type: 'string', description: 'UUID of the list to add the record to', example: '550e8400-e29b-41d4-a716-446655440000', }, recordId: { type: 'string', description: 'UUID of the record to add to the list', example: '660e8400-e29b-41d4-a716-446655440001', }, objectType: { type: 'string', description: 'Type of record (e.g., "companies", "people")', enum: ['companies', 'people'], example: 'companies', }, initialValues: { type: 'object', description: 'Initial values for the list entry (e.g., {"stage": "Prospect"})', example: { stage: 'Qualified' }, }, }, required: ['listId', 'recordId', 'objectType'], additionalProperties: false, }, },
- src/handlers/tools/registry.ts:70-91 (registration)Global tool registry where listsToolConfigs (containing add-record-to-list) is registered under ResourceType.LISTS in both universal and legacy TOOL_CONFIGS modes.export const TOOL_CONFIGS = USE_UNIVERSAL_TOOLS_ONLY ? { // Universal tools for consolidated operations (Issue #352) UNIVERSAL: universalToolConfigs, // Lists are relationship containers - always expose them (Issue #470) [ResourceType.LISTS]: listsToolConfigs, // Workspace members for user discovery (Issue #684) [ResourceType.WORKSPACE_MEMBERS]: workspaceMembersToolConfigs, } : { // Legacy resource-specific tools (deprecated, use DISABLE_UNIVERSAL_TOOLS=true to enable) [ResourceType.COMPANIES]: companyToolConfigs, [ResourceType.PEOPLE]: peopleToolConfigs, [ResourceType.DEALS]: dealToolConfigs, [ResourceType.LISTS]: listsToolConfigs, [ResourceType.TASKS]: tasksToolConfigs, [ResourceType.RECORDS]: recordToolConfigs, [ResourceType.WORKSPACE_MEMBERS]: workspaceMembersToolConfigs, GENERAL: generalToolConfigs, // Add other resource types as needed };