monica_manage_contact
Create, update, or delete contact profiles in Monica CRM. Manage basic info like name, gender, description, and birthdate to organize and maintain contact details effectively.
Instructions
Create, update, or delete contacts in Monica CRM. Use this for basic profile info (name, gender, description, birthdate). For phone numbers and emails, use contact field management instead.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| contactId | No | ||
| profile | No |
Implementation Reference
- src/tools/modules/contacts.ts:207-577 (handler)The primary handler function that executes all the tool's logic for different sections ('summary', 'profile', 'field', 'address') and actions ('create', 'update', 'delete', 'list', 'get'). Called by the tool's registration lambda and wrapper tools.export async function handleContactOperation( input: ContactToolInput, context: ToolRegistrationContext ): Promise<any> { const { client, logger } = context; switch (input.section) { case 'summary': { const response = await client.getContact(input.contactId!, input.includeContactFields); const contact = normalizeContactDetail(response.data); const summary = buildContactSummary(response.data); return { content: [ { type: 'text' as const, text: summary } ], structuredContent: { section: input.section, contact } }; } case 'profile': { if (input.action === 'delete') { await client.deleteContact(input.contactId!); logger.info({ contactId: input.contactId }, 'Deleted Monica contact'); return { content: [ { type: 'text' as const, text: `Deleted contact ID ${input.contactId}.` } ], structuredContent: { section: input.section, action: input.action, contactId: input.contactId } }; } const profile = input.profile!; const contactId = input.contactId; const genderId = await resolveGenderId(client, profile.genderId, profile.genderName); const payload = toContactProfileInput({ ...profile, genderId }); if (input.action === 'create') { const response = await client.createContact(payload); const contact = normalizeContactDetail(response.data); logger.info({ contactId: contact.id }, 'Created Monica contact'); return { content: [ { type: 'text' as const, text: `Created contact ${contact.name || `#${contact.id}`} (ID ${contact.id}).` } ], structuredContent: { section: input.section, action: input.action, contact } }; } const response = await client.updateContact(contactId!, payload); const contact = normalizeContactDetail(response.data); logger.info({ contactId }, 'Updated Monica contact'); return { content: [ { type: 'text' as const, text: `Updated contact ${contact.name || `#${contact.id}`} (ID ${contact.id}).` } ], structuredContent: { section: input.section, action: input.action, contactId, contact } }; } case 'field': { switch (input.action) { case 'list': { const response = await client.listContactFields({ contactId: input.contactId!, limit: input.limit, page: input.page }); const fields = response.data.map(normalizeContactField); return { content: [ { type: 'text' as const, text: fields.length ? `Fetched ${fields.length} contact field${fields.length === 1 ? '' : 's'} for contact ${input.contactId}.` : `No contact fields found for contact ${input.contactId}.` } ], structuredContent: { section: input.section, action: input.action, contactId: input.contactId, fields, pagination: { currentPage: response.meta.current_page, lastPage: response.meta.last_page, perPage: response.meta.per_page, total: response.meta.total } } }; } case 'get': { const response = await client.getContactField(input.contactFieldId!); const field = normalizeContactField(response.data); return { content: [ { type: 'text' as const, text: `Retrieved contact field ${field.type.name} (ID ${field.id}).` } ], structuredContent: { section: input.section, action: input.action, field } }; } case 'create': { const payload = input.fieldPayload!; const contactFieldTypeId = await resolveContactFieldTypeId(client, { contactFieldTypeId: payload.contactFieldTypeId, contactFieldTypeName: payload.contactFieldTypeName }); const result = await client.createContactField( toContactFieldPayloadInput({ ...payload, contactFieldTypeId }) ); const field = normalizeContactField(result.data); logger.info({ contactFieldId: field.id, contactId: field.contactId }, 'Created Monica contact field'); return { content: [ { type: 'text' as const, text: `Created contact field ${field.type.name} (ID ${field.id}) for contact ${field.contactId}.` } ], structuredContent: { section: input.section, action: input.action, field } }; } case 'update': { const payload = input.fieldPayload!; const contactFieldTypeId = await resolveContactFieldTypeId(client, { contactFieldTypeId: payload.contactFieldTypeId, contactFieldTypeName: payload.contactFieldTypeName }); const result = await client.updateContactField( input.contactFieldId!, toContactFieldPayloadInput({ ...payload, contactFieldTypeId }) ); const field = normalizeContactField(result.data); logger.info({ contactFieldId: input.contactFieldId }, 'Updated Monica contact field'); return { content: [ { type: 'text' as const, text: `Updated contact field ${field.type.name} (ID ${field.id}).` } ], structuredContent: { section: input.section, action: input.action, contactFieldId: input.contactFieldId, field } }; } case 'delete': { const result = await client.deleteContactField(input.contactFieldId!); logger.info({ contactFieldId: input.contactFieldId }, 'Deleted Monica contact field'); return { content: [ { type: 'text' as const, text: `Deleted contact field ID ${input.contactFieldId}.` } ], structuredContent: { section: input.section, action: input.action, contactFieldId: input.contactFieldId, result } }; } default: return unreachable(input.action as never); } } case 'address': { switch (input.action) { case 'list': { const response = await client.listAddresses({ contactId: input.contactId!, limit: input.limit, page: input.page }); const addresses = response.data.map(normalizeAddress); const summary = addresses.length ? `Fetched ${addresses.length} address${addresses.length === 1 ? '' : 'es'} for contact ${input.contactId}.` : `No addresses found for contact ${input.contactId}.`; return { content: [ { type: 'text' as const, text: summary } ], structuredContent: { section: input.section, action: input.action, contactId: input.contactId, addresses, pagination: { currentPage: response.meta.current_page, lastPage: response.meta.last_page, perPage: response.meta.per_page, total: response.meta.total } } }; } case 'get': { const response = await client.getAddress(input.addressId!); const address = normalizeAddress(response.data); return { content: [ { type: 'text' as const, text: `Retrieved address "${address.name}" (ID ${address.id}).` } ], structuredContent: { section: input.section, action: input.action, address } }; } case 'create': { const payload = input.addressPayload!; const countryId = await resolveCountryId(client, { countryId: payload.countryId, countryIso: payload.countryIso, countryName: payload.countryName }); const result = await client.createAddress( toAddressPayloadInput({ ...payload, countryId }) ); const address = normalizeAddress(result.data); logger.info({ addressId: address.id, contactId: address.contact.id }, 'Created Monica address'); return { content: [ { type: 'text' as const, text: `Created address "${address.name}" (ID ${address.id}) for contact ${address.contact.id}.` } ], structuredContent: { section: input.section, action: input.action, address } }; } case 'update': { const payload = input.addressPayload!; const countryId = await resolveCountryId(client, { countryId: payload.countryId, countryIso: payload.countryIso, countryName: payload.countryName }); const result = await client.updateAddress( input.addressId!, toAddressPayloadInput({ ...payload, countryId }) ); const address = normalizeAddress(result.data); logger.info({ addressId: input.addressId }, 'Updated Monica address'); return { content: [ { type: 'text' as const, text: `Updated address "${address.name}" (ID ${address.id}).` } ], structuredContent: { section: input.section, action: input.action, addressId: input.addressId, address } }; } case 'delete': { const result = await client.deleteAddress(input.addressId!); logger.info({ addressId: input.addressId }, 'Deleted Monica address'); return { content: [ { type: 'text' as const, text: `Deleted address ID ${input.addressId}.` } ], structuredContent: { section: input.section, action: input.action, addressId: input.addressId, result } }; } default: return unreachable(input.action as never); } } default: return unreachable(input.section as never); } }
- Zod schema definition (contactToolInputShape) and full validation schema (contactToolInputSchema) with refinements for the tool input.const contactToolInputShape = { section: z.enum(['summary', 'profile', 'field', 'address']), action: z.enum(['create', 'update', 'delete', 'list', 'get']).optional(), contactId: z.number().int().positive().optional(), contactFieldId: z.number().int().positive().optional(), addressId: z.number().int().positive().optional(), includeContactFields: z.boolean().optional(), limit: z.number().int().min(1).max(100).optional(), page: z.number().int().min(1).optional(), profile: contactProfileSchema.optional(), fieldPayload: contactFieldPayloadSchema.optional(), addressPayload: addressPayloadSchema.optional() } as const; const contactToolInputSchema = z.object(contactToolInputShape).superRefine((data, ctx) => { switch (data.section) { case 'summary': if (typeof data.contactId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactId when retrieving a summary.' }); } break; case 'profile': { if (!data.action || !['create', 'update', 'delete'].includes(data.action)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Use action create, update, or delete for profile operations.' }); break; } if (data.action === 'create' && !data.profile) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide profile details when creating a contact.' }); } if (data.action === 'update') { if (typeof data.contactId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactId when updating a contact.' }); } if (!data.profile) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide profile details when updating a contact.' }); } } if (data.action === 'delete' && typeof data.contactId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactId when deleting a contact.' }); } break; } case 'field': { if (!data.action || !['list', 'get', 'create', 'update', 'delete'].includes(data.action)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Use action list/get/create/update/delete for contact fields.' }); break; } if (data.action === 'list' && typeof data.contactId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactId when listing contact fields.' }); } if (data.action === 'get' && typeof data.contactFieldId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactFieldId when retrieving a contact field.' }); } if (['create', 'update'].includes(data.action) && !data.fieldPayload) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contact field details for this action.' }); } if (['update', 'delete'].includes(data.action) && typeof data.contactFieldId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactFieldId when updating or deleting a contact field.' }); } break; } case 'address': { if (!data.action || !['list', 'get', 'create', 'update', 'delete'].includes(data.action)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Use action list/get/create/update/delete for addresses.' }); break; } if (data.action === 'list' && typeof data.contactId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide contactId when listing addresses.' }); } if (data.action === 'get' && typeof data.addressId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide addressId when retrieving an address.' }); } if (['create', 'update'].includes(data.action) && !data.addressPayload) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide address details for this action.' }); } if (['update', 'delete'].includes(data.action) && typeof data.addressId !== 'number') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Provide addressId when updating or deleting an address.' }); } break; } default: break; } });
- src/tools/modules/contacts.ts:579-595 (registration)Direct registration of the 'monica_manage_contact' tool using server.registerTool, providing title, description, inputSchema, and a thin handler that parses input and delegates to handleContactOperation.export function registerContactTools(context: ToolRegistrationContext): void { const { server } = context; server.registerTool( 'monica_manage_contact', { title: 'Manage Monica contact', description: 'Advanced contact management tool with section-based operations. Set section to "summary" to retrieve full contact details, "profile" for contact creation/updates, "field" for managing contact fields (email/phone/etc), or "address" for address management. For simpler operations, prefer the dedicated wrapper tools: monica_manage_contact_profile, monica_manage_contact_field, or monica_manage_contact_address.', inputSchema: contactToolInputShape }, async (rawInput) => { const input = contactToolInputSchema.parse(rawInput); return handleContactOperation(input, context); } ); }
- src/tools/registerTools.ts:24-26 (registration)Top-level tool registration entrypoint that invokes registerContactTools(context), including the monica_manage_contact tool.export function registerTools(context: ToolRegistrationContext): void { registerSearchTools(context); registerContactTools(context);
- Helper functions for converting Zod-validated form inputs to Monica API payloads and unreachable case handler.function toContactProfileInput(profile: ContactProfileForm & { genderId?: number }): ContactProfileInput { return { firstName: profile.firstName, lastName: profile.lastName ?? null, nickname: profile.nickname ?? null, description: profile.description ?? null, genderId: profile.genderId, isPartial: profile.isPartial, isDeceased: profile.isDeceased, birthdate: profile.birthdate as ContactProfileInput['birthdate'], deceasedDate: profile.deceasedDate as ContactProfileInput['deceasedDate'], remindOnDeceasedDate: profile.remindOnDeceasedDate }; } function toContactFieldPayloadInput( payload: ContactFieldPayloadForm & { contactFieldTypeId: number } ): CreateContactFieldPayload & UpdateContactFieldPayload { return { contactId: payload.contactId, contactFieldTypeId: payload.contactFieldTypeId, data: payload.data }; } function toAddressPayloadInput( payload: AddressPayloadForm & { countryId: string | null } ): CreateAddressPayload & UpdateAddressPayload { return { contactId: payload.contactId, name: payload.name, street: payload.street ?? null, city: payload.city ?? null, province: payload.province ?? null, postalCode: payload.postalCode ?? null, countryId: payload.countryId }; } function unreachable(value: never): never { throw new Error(`Unhandled case: ${JSON.stringify(value)}`);