Skip to main content
Glama

mcp-google-sheets

update-contact.ts22.3 kB
import { createAction, Property } from '@activepieces/pieces-framework'; import { HttpMethod } from '@activepieces/pieces-common'; import { missiveAuth } from '../common/auth'; import { missiveCommon } from '../common/client'; import { contactBookDropdown } from '../common/dynamic-dropdowns'; export const updateContact = createAction({ name: 'update_contact', displayName: 'Update Contact', description: 'Modify fields for an existing contact', auth: missiveAuth, props: { contact_book: contactBookDropdown, contact_id: Property.Dropdown({ displayName: 'Contact', description: 'Select the contact to update', required: true, refreshers: ['contact_book'], options: async ({ auth, contact_book }) => { if (!auth) { return { disabled: true, placeholder: 'Please authenticate first', options: [], }; } if (!contact_book) { return { disabled: true, placeholder: 'Please select a contact book first', options: [], }; } try { const response = await missiveCommon.apiCall({ auth: auth as string, method: HttpMethod.GET, resourceUri: `/contacts?contact_book=${contact_book}`, }); const contacts = response.body?.contacts || []; const options = contacts.map((contact: any) => { let label = ''; if (contact.first_name || contact.last_name) { label = `${contact.first_name || ''} ${contact.last_name || ''}`.trim(); } else { label = 'Unnamed Contact'; } // Add email or phone if available for better identification const primaryEmail = contact.infos?.find((info: any) => info.kind === 'email')?.value; const primaryPhone = contact.infos?.find((info: any) => info.kind === 'phone_number')?.value; if (primaryEmail) { label += ` (${primaryEmail})`; } else if (primaryPhone) { label += ` (${primaryPhone})`; } return { label, value: contact.id, }; }); return { disabled: false, options, }; } catch (error) { return { disabled: true, placeholder: 'Failed to load contacts', options: [], }; } }, }), move_to_contact_book: Property.Dropdown({ displayName: 'Move to Contact Book', description: 'Move contact to a different contact book (optional)', required: false, refreshers: [], options: async ({ auth }) => { if (!auth) { return { disabled: true, placeholder: 'Please authenticate first', options: [], }; } try { const response = await missiveCommon.apiCall({ auth: auth as string, method: HttpMethod.GET, resourceUri: '/contact_books', }); const contactBooks = response.body?.contact_books || []; const options = contactBooks.map((book: any) => ({ label: book.name, value: book.id, })); return { disabled: false, options, }; } catch (error) { return { disabled: true, placeholder: 'Failed to load contact books', options: [], }; } }, }), first_name: Property.ShortText({ displayName: 'First Name', description: 'First name of the contact', required: false, }), last_name: Property.ShortText({ displayName: 'Last Name', description: 'Last name of the contact', required: false, }), middle_name: Property.ShortText({ displayName: 'Middle Name', description: 'Middle name of the contact', required: false, }), phonetic_first_name: Property.ShortText({ displayName: 'Phonetic First Name', description: 'Phonetic spelling of the first name', required: false, }), phonetic_last_name: Property.ShortText({ displayName: 'Phonetic Last Name', description: 'Phonetic spelling of the last name', required: false, }), phonetic_middle_name: Property.ShortText({ displayName: 'Phonetic Middle Name', description: 'Phonetic spelling of the middle name', required: false, }), prefix: Property.ShortText({ displayName: 'Prefix', description: 'Name prefix (e.g., Mr., Mrs., Dr.)', required: false, }), suffix: Property.ShortText({ displayName: 'Suffix', description: 'Name suffix (e.g., Jr., Sr., III)', required: false, }), nickname: Property.ShortText({ displayName: 'Nickname', description: 'Nickname for the contact', required: false, }), file_as: Property.ShortText({ displayName: 'File As', description: 'How the contact should be filed/sorted', required: false, }), notes: Property.LongText({ displayName: 'Notes', description: 'Additional notes about the contact', required: false, }), starred: Property.Checkbox({ displayName: 'Starred', description: 'Whether the contact should be starred', required: false, }), gender: Property.StaticDropdown({ displayName: 'Gender', description: 'Gender of the contact', required: false, options: { options: [ { label: 'Male', value: 'Male' }, { label: 'Female', value: 'Female' }, { label: 'Other', value: 'Other' } ] } }), infos: Property.Array({ displayName: 'Contact Information', description: 'Email addresses, phone numbers, and other contact information. Note: When updating infos, all existing infos will be replaced with the ones provided here.', required: false, properties: { kind: Property.StaticDropdown({ displayName: 'Type', description: 'Type of contact information', required: true, options: { options: [ { label: 'Email', value: 'email' }, { label: 'Phone Number', value: 'phone_number' }, { label: 'Twitter', value: 'twitter' }, { label: 'Facebook', value: 'facebook' }, { label: 'Physical Address', value: 'physical_address' }, { label: 'URL', value: 'url' }, { label: 'Custom', value: 'custom' } ] } }), label: Property.StaticDropdown({ displayName: 'Label', description: 'Label for this contact information', required: true, options: { options: [ { label: 'Home', value: 'home' }, { label: 'Work', value: 'work' }, { label: 'Personal', value: 'personal' }, { label: 'Mobile', value: 'mobile' }, { label: 'Main', value: 'main' }, { label: 'Home Fax', value: 'home_fax' }, { label: 'Work Fax', value: 'work_fax' }, { label: 'Other Fax', value: 'other_fax' }, { label: 'Pager', value: 'pager' }, { label: 'Homepage', value: 'homepage' }, { label: 'Profile', value: 'profile' }, { label: 'Blog', value: 'blog' }, { label: 'Other', value: 'other' } ] } }), value: Property.ShortText({ displayName: 'Value', description: 'The actual contact information value', required: true, }), custom_label: Property.ShortText({ displayName: 'Custom Label', description: 'Custom label value (use only if label is "other")', required: false, }), street: Property.ShortText({ displayName: 'Street', description: 'Street address (for physical address)', required: false, }), extended_address: Property.ShortText({ displayName: 'Extended Address', description: 'Extended address (e.g., apartment, suite)', required: false, }), city: Property.ShortText({ displayName: 'City', description: 'City (for physical address)', required: false, }), region: Property.ShortText({ displayName: 'Region/State', description: 'Region or state (for physical address)', required: false, }), postal_code: Property.ShortText({ displayName: 'Postal Code', description: 'Postal code (for physical address)', required: false, }), po_box: Property.ShortText({ displayName: 'PO Box', description: 'PO Box (for physical address)', required: false, }), country: Property.ShortText({ displayName: 'Country', description: 'Country (for physical address)', required: false, }), name: Property.ShortText({ displayName: 'Facebook Name', description: 'Facebook user name (for facebook type)', required: false, }) } }), memberships: Property.DynamicProperties({ displayName: 'Memberships', description: 'Organizations and groups the contact belongs to. Note: When updating memberships, all existing memberships will be replaced with the ones provided here.', required: false, refreshers: ['contact_book'], props: async ({ auth, contact_book }) => { if (!auth) { return { memberships_array: Property.Array({ displayName: 'Memberships', description: 'Please authenticate first', required: false, properties: { placeholder: Property.ShortText({ displayName: 'Authenticate First', description: 'Please authenticate to access memberships', required: false, }) } }) }; } let organizationOptions: Array<{ label: string; value: string }> = []; let contactGroupOptions: Array<{ label: string; value: string }> = []; try { const orgsResponse = await missiveCommon.apiCall({ auth: auth as unknown as string, method: HttpMethod.GET, resourceUri: '/organizations', }); organizationOptions = orgsResponse.body?.organizations?.map((org: any) => ({ label: org.name, value: org.id })) || []; } catch (error) { console.error('Failed to fetch organizations:', error); } if (contact_book) { try { const groupsResponse = await missiveCommon.apiCall({ auth: auth as unknown as string, method: HttpMethod.GET, resourceUri: `/contact_groups?contact_book=${contact_book}&kind=group`, }); contactGroupOptions = groupsResponse.body?.contact_groups?.map((group: any) => ({ label: group.name, value: group.id })) || []; } catch (error) { console.error('Failed to fetch contact groups:', error); } } return { memberships_array: Property.Array({ displayName: 'Memberships', description: 'Add, remove, and manage contact memberships. All existing memberships will be replaced.', required: false, properties: { type: Property.StaticDropdown({ displayName: 'Type', description: 'Type of membership', required: true, options: { options: [ { label: 'Organization', value: 'organization' }, { label: 'Group', value: 'group' } ] } }), organization: Property.StaticDropdown({ displayName: 'Organization', description: 'Select organization (only for Organization type)', required: false, options: { options: organizationOptions.length > 0 ? organizationOptions : [{ label: 'No organizations found', value: '' }] } }), contact_group: Property.StaticDropdown({ displayName: 'Contact Group', description: contact_book ? 'Select contact group (only for Group type)' : 'Select contact book first to see groups', required: false, options: { options: contactGroupOptions.length > 0 ? contactGroupOptions : [{ label: contact_book ? 'No groups found' : 'Select contact book first', value: '' }] } }), title: Property.ShortText({ displayName: 'Title', description: 'Job title or role', required: false, }), location: Property.ShortText({ displayName: 'Location', description: 'Location or office', required: false, }), department: Property.ShortText({ displayName: 'Department', description: 'Department', required: false, }), description: Property.ShortText({ displayName: 'Description', description: 'Description of role or membership', required: false, }) } }) }; }, }) }, async run(context) { const propsValue = context.propsValue as any; const { contact_id, contact_book, move_to_contact_book, first_name, last_name, middle_name, phonetic_first_name, phonetic_last_name, phonetic_middle_name, prefix, suffix, nickname, file_as, notes, starred, gender, infos } = propsValue; const contactData: Record<string, any> = { id: contact_id, }; if (move_to_contact_book) contactData['contact_book'] = move_to_contact_book; if (first_name) contactData['first_name'] = first_name; if (last_name) contactData['last_name'] = last_name; if (middle_name) contactData['middle_name'] = middle_name; if (phonetic_first_name) contactData['phonetic_first_name'] = phonetic_first_name; if (phonetic_last_name) contactData['phonetic_last_name'] = phonetic_last_name; if (phonetic_middle_name) contactData['phonetic_middle_name'] = phonetic_middle_name; if (prefix) contactData['prefix'] = prefix; if (suffix) contactData['suffix'] = suffix; if (nickname) contactData['nickname'] = nickname; if (file_as) contactData['file_as'] = file_as; if (notes) contactData['notes'] = notes; if (starred !== undefined) contactData['starred'] = starred; if (gender) contactData['gender'] = gender; if (infos && infos.length > 0) { contactData['infos'] = infos.map((info: any) => { const infoObj: any = { kind: info.kind, label: info.label, }; if (info.kind === 'email' || info.kind === 'phone_number' || info.kind === 'twitter' || info.kind === 'url' || info.kind === 'custom') { infoObj.value = info.value; } else if (info.kind === 'facebook') { infoObj.name = info.name; } else if (info.kind === 'physical_address') { if (info.street) infoObj.street = info.street; if (info.extended_address) infoObj.extended_address = info.extended_address; if (info.city) infoObj.city = info.city; if (info.region) infoObj.region = info.region; if (info.postal_code) infoObj.postal_code = info.postal_code; if (info.po_box) infoObj.po_box = info.po_box; if (info.country) infoObj.country = info.country; } if (info.custom_label && info.label === 'other') { infoObj.custom_label = info.custom_label; } return infoObj; }); } const membershipsArray = propsValue.memberships_array; if (membershipsArray && Array.isArray(membershipsArray) && membershipsArray.length > 0) { const memberships: any[] = []; for (const membership of membershipsArray) { if (!membership.type) continue; const membershipObj: any = {}; if (membership.title) membershipObj.title = membership.title; if (membership.location) membershipObj.location = membership.location; if (membership.department) membershipObj.department = membership.department; if (membership.description) membershipObj.description = membership.description; if (membership.type === 'organization' && membership.organization) { membershipObj.group = { kind: 'organization', id: membership.organization }; } else if (membership.type === 'group' && membership.contact_group) { membershipObj.group = { kind: 'group', id: membership.contact_group }; } if (membershipObj.group) { memberships.push(membershipObj); } } if (memberships.length > 0) { contactData['memberships'] = memberships; } } const response = await missiveCommon.apiCall({ auth: context.auth, method: HttpMethod.PATCH, resourceUri: `/contacts/${contact_id}`, body: { contacts: [contactData] }, }); return response.body; }, });

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/activepieces/activepieces'

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