Skip to main content
Glama

monica_manage_contact

Manage contact details in Monica CRM by creating, updating, deleting, or retrieving profiles, fields, addresses, and summaries through section-based operations.

Instructions

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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sectionYes
actionNo
contactIdNo
contactFieldIdNo
addressIdNo
includeContactFieldsNo
limitNo
pageNo
profileNo
fieldPayloadNo
addressPayloadNo

Implementation Reference

  • Main exported handler function that implements the core logic for the monica_manage_contact tool. Handles operations on contact summary, profile, fields, and addresses with actions like create, update, delete, list, get.
    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);
          }
    }
  • Input schema definition for the tool, including contactToolInputShape used in registration and contactToolInputSchema for parsing with Zod refinements validating section-action combinations.
    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;
      }
    });
  • Registration function for the monica_manage_contact tool, which is called from src/tools/registerTools.ts. Registers the tool name, metadata, schema, and handler.
    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);
        }
      );
    }
  • Top-level tool registration entrypoint that invokes registerContactTools to register the monica_manage_contact tool among others.
    export function registerTools(context: ToolRegistrationContext): void {
      registerSearchTools(context);
      registerContactTools(context);
  • Helper function to convert profile form data to API payload input.
    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
      };

Latest Blog Posts

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/Jacob-Stokes/monica-mcp'

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