Skip to main content
Glama

Manage Monica contact

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
      };
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but provides minimal behavioral context. It mentions 'section-based operations' but doesn't explain what happens during create/update/delete actions, whether operations are destructive, what permissions are required, or how errors are handled. For an 11-parameter tool with complex nested objects, this is insufficient behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is perfectly concise with two sentences: the first explains the tool's section-based nature with clear examples, the second provides usage guidance with specific alternative tools. Every word earns its place with no redundancy or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's high complexity (11 parameters, nested objects, multiple actions), no annotations, and no output schema, the description is incomplete. It explains the section parameter well but leaves most parameters undocumented, provides minimal behavioral context for a multi-action tool, and doesn't address return values or error conditions.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage and 11 parameters (including complex nested objects), the description only explains the 'section' parameter's four enum values. It doesn't clarify when other parameters like contactId, contactFieldId, addressId, or the three payload objects are required, nor their relationships to sections and actions. The description adds minimal value beyond what the bare schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states this is an 'Advanced contact management tool with section-based operations' and specifies the four section types (summary, profile, field, address) with their purposes. It distinguishes this comprehensive tool from its simpler wrapper siblings by explicitly naming them as alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool vs alternatives: 'For simpler operations, prefer the dedicated wrapper tools' and names three specific alternatives (monica_manage_contact_profile, monica_manage_contact_field, monica_manage_contact_address). This gives clear direction on tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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