update-record
Modify existing records in Attio CRM for companies, people, lists, tasks, deals, notes, or records by providing updated data and record ID.
Instructions
Update an existing record of any supported type
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| record_data | Yes | Updated data | |
| record_id | Yes | Record ID to update | |
| resource_type | Yes | Type of resource to operate on (companies, people, lists, records, tasks) | |
| return_details | No | Return full details |
Implementation Reference
- Core implementation of the 'update-record' tool handler. Transforms input record_data (normalizing phones, special fields), PATCHes to Attio API `/v2/objects/{slug}/records/{id}`, handles errors (esp. phone validation), returns structured JSON + human-readable summary of updated record.export async function handleUpdateRecord( client: HttpClient, params: { resource_type: ResourceType; record_id: string; record_data: Record<string, unknown>; return_details?: boolean; }, config?: ToolHandlerConfig ): Promise<ToolResult> { try { const { resource_type, record_id, record_data, return_details = true, } = params; const objectSlug = getObjectSlug(resource_type); // Transform record_data to Attio's expected format with values wrapper // Phone numbers are validated and normalized to E.164 format const data = transformRecordData(record_data, config); const response = await client.patch<AttioApiResponse<AttioRecord>>( `/v2/objects/${objectSlug}/records/${record_id}`, { data } ); const record = response.data.data; if (return_details) { return structuredResult( record, `Updated ${resource_type} record:\n${formatRecordDetails(record, resource_type)}` ); } return structuredResult( record, `Updated ${resource_type} record with ID: ${record_id}` ); } catch (error) { // Handle phone validation errors with helpful messages if (error instanceof PhoneValidationError) { return errorResult(error.message, { code: error.code, input: error.input, country: error.country, hint: 'Provide phone numbers in E.164 format (e.g., +1 555 123 4567) or configure defaultCountry.', }); } const { message, details } = extractErrorInfo(error); return errorResult(message || 'Failed to update record', details); } }
- Schema definition for 'update-record' tool, including input parameters (resource_type, record_id, record_data, optional return_details), description with usage boundaries, and no specific annotations.export const updateRecordDefinition: ToolDefinition = { name: 'update-record', description: formatDescription({ capability: 'Update existing Attio record fields across all supported resource types', boundaries: 'create new records, delete data, or manage list memberships', constraints: 'Requires record_id and attributes payload; supports partial updates', recoveryHint: 'Call records_get_details first to inspect the latest values before editing', }), inputSchema: { type: 'object', properties: { resource_type: RESOURCE_TYPE_SCHEMA, record_id: { type: 'string', description: 'Record ID to update', }, record_data: { type: 'object', description: 'Updated data for the record', additionalProperties: true, }, return_details: { type: 'boolean', description: 'Return full record details after update', default: true, }, }, required: ['resource_type', 'record_id', 'record_data'], }, };
- packages/core/src/tools/handlers.ts:988-1042 (registration)Dispatch registration mapping 'update-record' tool name to its handleUpdateRecord implementation via getToolHandler function, used by the tool registry for execution.export function getToolHandler( toolName: string ): | (( client: HttpClient, params: Record<string, unknown> ) => Promise<ToolResult>) | undefined { const handlers: Record< string, (client: HttpClient, params: Record<string, unknown>) => Promise<ToolResult> > = { 'aaa-health-check': async (_client, params) => handleHealthCheck(params as { echo?: string }), records_search: async (client, params) => handleSearchRecords( client, params as Parameters<typeof handleSearchRecords>[1] ), records_get_details: async (client, params) => handleGetRecordDetails( client, params as Parameters<typeof handleGetRecordDetails>[1] ), 'create-record': async (client, params) => handleCreateRecord( client, params as Parameters<typeof handleCreateRecord>[1] ), 'update-record': async (client, params) => handleUpdateRecord( client, params as Parameters<typeof handleUpdateRecord>[1] ), 'delete-record': async (client, params) => handleDeleteRecord( client, params as Parameters<typeof handleDeleteRecord>[1] ), records_discover_attributes: async (client, params) => handleDiscoverAttributes( client, params as Parameters<typeof handleDiscoverAttributes>[1] ), 'create-note': async (client, params) => handleCreateNote( client, params as Parameters<typeof handleCreateNote>[1] ), 'list-notes': async (client, params) => handleListNotes(client, params as Parameters<typeof handleListNotes>[1]), }; return handlers[toolName]; }
- packages/core/src/tools/definitions.ts:417-427 (registration)Registration of the updateRecordDefinition in the coreToolDefinitions object, exported for use in MCP ListTools response and registry.export const coreToolDefinitions: Record<string, ToolDefinition> = { 'aaa-health-check': healthCheckDefinition, records_search: searchRecordsDefinition, records_get_details: getRecordDetailsDefinition, 'create-record': createRecordDefinition, 'update-record': updateRecordDefinition, 'delete-record': deleteRecordDefinition, records_discover_attributes: discoverAttributesDefinition, 'create-note': createNoteDefinition, 'list-notes': listNotesDefinition, };
- Key helper function used by update-record (and create-record) to transform user input into Attio API format: wraps fields in {values: [{value: ...}]}, normalizes phones to E.164, handles array/special fields.function transformRecordData( recordData: Record<string, unknown>, config?: ToolHandlerConfig ): Record<string, unknown> { const values: Record<string, unknown> = {}; for (const [key, value] of Object.entries(recordData)) { if (value === undefined || value === null) continue; // Special handling for phone_numbers with validation and E.164 normalization if (key === 'phone_numbers') { const phoneArray = Array.isArray(value) ? value : [value]; const normalizedPhones: Record<string, unknown>[] = []; for (const phone of phoneArray) { if (phone === undefined || phone === null) continue; // Skip if already in Attio format with original_phone_number if ( typeof phone === 'object' && phone !== null && 'original_phone_number' in phone && !('phone_number' in phone) && !('phone' in phone) ) { normalizedPhones.push(phone as Record<string, unknown>); continue; } // Normalize and validate phone number const normalized = normalizePhoneForAttio( phone as string | Record<string, unknown>, { defaultCountry: config?.phone?.defaultCountry } ); normalizedPhones.push(normalized); } if (normalizedPhones.length > 0) { values[key] = normalizedPhones; } continue; } // If value is already in Attio array format [{...}], pass through if ( Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' ) { values[key] = value; continue; } // Check if this is a special field type (domains, email_addresses) const specialKey = SPECIAL_FIELD_FORMATS[key]; if (specialKey) { // Handle array of simple values (e.g., multiple domains) if (Array.isArray(value)) { values[key] = value.map((v) => ({ [specialKey]: String(v) })); } else { values[key] = [{ [specialKey]: String(value) }]; } continue; } // Standard field: wrap in [{value: ...}] if (Array.isArray(value)) { // Array of simple values values[key] = value.map((v) => ({ value: v })); } else { values[key] = [{ value }]; } } return { values }; }