/**
* Tool: update_person
* Update an existing person in Attio CRM
*/
import type { Tool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { createAttioClient } from '../attio-client.js';
import {
handleToolError,
createSuccessResponse,
} from '../utils/error-handler.js';
import { ConfigurationError, ValidationError } from '../utils/errors.js';
interface AttioPersonUpdateResponse {
data: {
id: {
workspace_id: string;
object_id: string;
record_id: string;
};
created_at: string;
web_url: string;
values: {
name?: Array<{
first_name?: string;
last_name?: string;
full_name?: string;
}>;
email_addresses?: Array<{ email_address: string }>;
description?: Array<{ value: string }>;
linkedin?: Array<{ value: string }>;
[key: string]: unknown;
};
};
}
/**
* Tool definition for MCP
*/
export const updatePersonTool: Tool = {
name: 'update_person',
description:
'Update an existing person in Attio CRM by record ID. You can update name fields (first_name, last_name, full_name), description, and/or LinkedIn URL. Only provided fields will be updated; omitted fields remain unchanged. To manage email addresses, use add_person_email and remove_person_email tools instead. Returns the updated person record.',
inputSchema: {
type: 'object',
properties: {
record_id: {
type: 'string',
description:
'The unique record ID of the person to update (e.g., from search_people or get_person)',
},
first_name: {
type: 'string',
description: 'Updated first name',
},
last_name: {
type: 'string',
description: 'Updated last name',
},
full_name: {
type: 'string',
description: 'Updated full name',
},
description: {
type: 'string',
description: 'Updated person description or notes',
},
linkedin: {
type: 'string',
description:
'Updated LinkedIn URL (e.g., "https://linkedin.com/in/johndoe")',
},
},
required: ['record_id'],
},
};
/**
* Handler function for update_person tool
*/
export async function handleUpdatePerson(args: {
record_id: string;
first_name?: string;
last_name?: string;
full_name?: string;
description?: string;
linkedin?: string;
}): Promise<CallToolResult> {
try {
const apiKey = process.env['ATTIO_API_KEY'];
if (!apiKey) {
throw new ConfigurationError('ATTIO_API_KEY not configured');
}
const {
record_id,
first_name,
last_name,
full_name,
description,
linkedin,
} = args;
// Validate required fields
if (!record_id || record_id.trim().length === 0) {
throw new ValidationError(
'record_id parameter is required and cannot be empty',
'record_id'
);
}
// Check that at least one field is being updated
if (!first_name && !last_name && !full_name && !description && !linkedin) {
throw new ValidationError(
'At least one field must be provided to update (first_name, last_name, full_name, description, or linkedin). To manage email addresses, use add_person_email and remove_person_email tools.'
);
}
const client = createAttioClient(apiKey);
// Build request body with only provided fields
const values: Record<string, unknown[]> = {};
// Handle name fields - only update if at least one name field is provided
if (
first_name !== undefined ||
last_name !== undefined ||
full_name !== undefined
) {
const nameObj: {
first_name?: string;
last_name?: string;
full_name?: string;
} = {};
if (first_name !== undefined) {
if (first_name.trim().length > 0) {
nameObj.first_name = first_name.trim();
}
}
if (last_name !== undefined) {
if (last_name.trim().length > 0) {
nameObj.last_name = last_name.trim();
}
}
if (full_name !== undefined) {
if (full_name.trim().length > 0) {
nameObj.full_name = full_name.trim();
}
}
values['name'] = [nameObj];
}
if (description !== undefined) {
// Allow empty string to clear description
if (description.trim().length > 0) {
values['description'] = [{ value: description.trim() }];
} else {
values['description'] = [];
}
}
if (linkedin !== undefined) {
// Allow empty string to clear linkedin
if (linkedin.trim().length > 0) {
values['linkedin'] = [{ value: linkedin.trim() }];
} else {
values['linkedin'] = [];
}
}
// Update person via Attio API
// Use PUT to replace fields (PATCH would add to arrays instead of replacing)
const response = await client.put<AttioPersonUpdateResponse>(
`/objects/people/records/${record_id}`,
{
data: {
values,
},
}
);
const person = response.data;
const nameData = person.values.name?.[0];
// Transform to clean format
const result = {
record_id: person.id.record_id,
workspace_id: person.id.workspace_id,
object_id: person.id.object_id,
created_at: person.created_at,
web_url: person.web_url,
name: nameData?.full_name || null,
first_name: nameData?.first_name || null,
last_name: nameData?.last_name || null,
email_addresses:
person.values.email_addresses
?.map((e) => e.email_address)
.filter(Boolean) || [],
description: person.values.description?.[0]?.value || null,
linkedin: person.values.linkedin?.[0]?.value || null,
};
return createSuccessResponse(result);
} catch (error) {
return handleToolError(error, 'update_person');
}
}