monica_manage_group
Organize contacts into named collections like Family or Travel buddies by listing, creating, updating, or deleting contact groups in Monica CRM.
Instructions
List, inspect, create, update, or delete contact groups. Use this to organize contacts into named collections (e.g., "Family", "Travel buddies").
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| groupId | No | ||
| limit | No | ||
| page | No | ||
| payload | No |
Implementation Reference
- src/tools/modules/groups.ts:29-216 (handler)Handler function implementing the core logic for 'monica_manage_group' tool: a switch statement handling 'list', 'get', 'create', 'update', 'delete' actions on Monica contact groups using the client API.async ({ action, groupId, limit, page, payload }) => { switch (action) { case 'list': { const response = await client.listGroups({ limit, page }); const groups = response.data.map(normalizeGroup); const summaryLines = groups.map((group) => { const contactLabel = group.contactCount === 1 ? 'contact' : 'contacts'; return `• ID ${group.id}: ${group.name} (${group.contactCount} ${contactLabel})`; }); const text = groups.length ? `Found ${groups.length} group${groups.length === 1 ? '' : 's'}:\n\n${summaryLines.join('\n')}` : 'No groups found.'; return { content: [ { type: 'text' as const, text } ], structuredContent: { action, groups, pagination: { currentPage: response.meta.current_page, lastPage: response.meta.last_page, perPage: response.meta.per_page, total: response.meta.total } } }; } case 'get': { if (!groupId) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide groupId when retrieving a group.' } ] }; } const response = await client.getGroup(groupId); const group = normalizeGroup(response.data); const contactNames = group.contacts.map((contact) => contact.name || `Contact ${contact.id}`); const contactsSummary = contactNames.length ? `Members: ${contactNames.join(', ')}` : 'Members: none yet.'; return { content: [ { type: 'text' as const, text: `Group ${group.name} (ID ${group.id}). ${contactsSummary}` } ], structuredContent: { action, groupId, group } }; } case 'create': { if (!payload) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide a group payload when creating a group (name).' } ] }; } const response = await client.createGroup(toGroupCreatePayload(payload)); const group = normalizeGroup(response.data); logger.info({ groupId: group.id }, 'Created Monica group'); return { content: [ { type: 'text' as const, text: `Created group ${group.name} (ID ${group.id}).` } ], structuredContent: { action, group } }; } case 'update': { if (!groupId) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide groupId when updating a group.' } ] }; } if (!payload) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide a group payload when updating a group (name).' } ] }; } const response = await client.updateGroup(groupId, toGroupUpdatePayload(payload)); const group = normalizeGroup(response.data); logger.info({ groupId }, 'Updated Monica group'); return { content: [ { type: 'text' as const, text: `Updated group ${group.name} (ID ${group.id}).` } ], structuredContent: { action, groupId, group } }; } case 'delete': { if (!groupId) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide groupId when deleting a group.' } ] }; } const result = await client.deleteGroup(groupId); logger.info({ groupId }, 'Deleted Monica group'); return { content: [ { type: 'text' as const, text: `Deleted group ID ${groupId}.` } ], structuredContent: { action, groupId, result } }; } default: return { isError: true as const, content: [ { type: 'text' as const, text: `Unsupported action: ${action}.` } ] }; } }
- src/tools/modules/groups.ts:15-217 (registration)Registration of the 'monica_manage_group' tool via server.registerTool, including title, description, and inputSchema definition.server.registerTool( 'monica_manage_group', { title: 'Manage Monica groups', description: 'List, inspect, create, update, or delete contact groups. Use this to organize contacts into named collections (e.g., "Family", "Travel buddies").', inputSchema: { action: z.enum(['list', 'get', 'create', 'update', 'delete']), groupId: z.number().int().positive().optional(), limit: z.number().int().min(1).max(100).optional(), page: z.number().int().min(1).optional(), payload: groupPayloadSchema.optional() } }, async ({ action, groupId, limit, page, payload }) => { switch (action) { case 'list': { const response = await client.listGroups({ limit, page }); const groups = response.data.map(normalizeGroup); const summaryLines = groups.map((group) => { const contactLabel = group.contactCount === 1 ? 'contact' : 'contacts'; return `• ID ${group.id}: ${group.name} (${group.contactCount} ${contactLabel})`; }); const text = groups.length ? `Found ${groups.length} group${groups.length === 1 ? '' : 's'}:\n\n${summaryLines.join('\n')}` : 'No groups found.'; return { content: [ { type: 'text' as const, text } ], structuredContent: { action, groups, pagination: { currentPage: response.meta.current_page, lastPage: response.meta.last_page, perPage: response.meta.per_page, total: response.meta.total } } }; } case 'get': { if (!groupId) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide groupId when retrieving a group.' } ] }; } const response = await client.getGroup(groupId); const group = normalizeGroup(response.data); const contactNames = group.contacts.map((contact) => contact.name || `Contact ${contact.id}`); const contactsSummary = contactNames.length ? `Members: ${contactNames.join(', ')}` : 'Members: none yet.'; return { content: [ { type: 'text' as const, text: `Group ${group.name} (ID ${group.id}). ${contactsSummary}` } ], structuredContent: { action, groupId, group } }; } case 'create': { if (!payload) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide a group payload when creating a group (name).' } ] }; } const response = await client.createGroup(toGroupCreatePayload(payload)); const group = normalizeGroup(response.data); logger.info({ groupId: group.id }, 'Created Monica group'); return { content: [ { type: 'text' as const, text: `Created group ${group.name} (ID ${group.id}).` } ], structuredContent: { action, group } }; } case 'update': { if (!groupId) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide groupId when updating a group.' } ] }; } if (!payload) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide a group payload when updating a group (name).' } ] }; } const response = await client.updateGroup(groupId, toGroupUpdatePayload(payload)); const group = normalizeGroup(response.data); logger.info({ groupId }, 'Updated Monica group'); return { content: [ { type: 'text' as const, text: `Updated group ${group.name} (ID ${group.id}).` } ], structuredContent: { action, groupId, group } }; } case 'delete': { if (!groupId) { return { isError: true as const, content: [ { type: 'text' as const, text: 'Provide groupId when deleting a group.' } ] }; } const result = await client.deleteGroup(groupId); logger.info({ groupId }, 'Deleted Monica group'); return { content: [ { type: 'text' as const, text: `Deleted group ID ${groupId}.` } ], structuredContent: { action, groupId, result } }; } default: return { isError: true as const, content: [ { type: 'text' as const, text: `Unsupported action: ${action}.` } ] }; } } );
- src/tools/modules/groups.ts:21-27 (schema)Input schema for the tool, specifying parameters: action (enum), optional groupId, limit, page, and payload.inputSchema: { action: z.enum(['list', 'get', 'create', 'update', 'delete']), groupId: z.number().int().positive().optional(), limit: z.number().int().min(1).max(100).optional(), page: z.number().int().min(1).optional(), payload: groupPayloadSchema.optional() }
- src/tools/modules/groups.ts:6-8 (schema)groupPayloadSchema for create/update payloads: { name: string }const groupPayloadSchema = z.object({ name: z.string().min(1).max(255) });
- src/tools/modules/groups.ts:220-230 (helper)Helper functions toGroupCreatePayload and toGroupUpdatePayload converting GroupPayloadForm to Monica API payloads.function toGroupCreatePayload(payload: GroupPayloadForm): CreateGroupPayload { return { name: payload.name }; } function toGroupUpdatePayload(payload: GroupPayloadForm): UpdateGroupPayload { return { name: payload.name }; }
- src/tools/registerTools.ts:34-34 (registration)High-level registration invocation: registerGroupTools(context) called within registerTools.registerGroupTools(context);
- src/utils/formatters.ts:34-267 (helper)normalizeGroup utility function used to format group data returned from API.nickname: contact.nickname ?? undefined, gender: contact.gender ?? undefined, emails: contact.information?.emails?.map((email) => email.value) ?? [], phones: contact.information?.phones?.map((phone) => phone.value) ?? [], isPartial: contact.is_partial }; } export function normalizeContactDetail(contact: MonicaContact) { const rawContactFields = getContactFields(contact); return { ...normalizeContactSummary(contact), description: contact.information?.description ?? undefined, dates: contact.information?.dates ?? [], createdAt: contact.created_at, updatedAt: contact.updated_at, customFields: rawContactFields.map(normalizeContactField), tags: contact.tags ? contact.tags.map(normalizeTag) : [] }; } export function buildContactSummary(contact: MonicaContact): string { const name = [contact.first_name, contact.last_name].filter(Boolean).join(' ').trim(); const emails = contact.information?.emails?.map((email) => email.value) ?? []; const phones = contact.information?.phones?.map((phone) => phone.value) ?? []; const rawContactFields = getContactFields(contact); const parts = [name || `Contact #${contact.id}`]; if (emails.length) { parts.push(`Emails: ${emails.join(', ')}`); } if (phones.length) { parts.push(`Phones: ${phones.join(', ')}`); } // Include contact fields if available if (rawContactFields.length > 0) { const fieldLines = rawContactFields.map((field) => { const typeName = field.contact_field_type?.name || 'Unknown'; const value = field.data ?? ''; return `${typeName}: ${value}`; }); parts.push(`Contact Fields:\n ${fieldLines.join('\n ')}`); } if (contact.information?.description) { parts.push(`Notes: ${contact.information.description}`); } if (contact.tags && contact.tags.length) { const tagNames = contact.tags.map((tag) => tag.name).join(', '); parts.push(`Tags: ${tagNames}`); } return parts.join('\n'); } function getContactFields(contact: MonicaContact): MonicaContactField[] { const candidate = (contact as MonicaContact & { contactfields?: MonicaContactField[]; }).contactFields; if (candidate && Array.isArray(candidate)) { return candidate; } const legacy = (contact as { contactfields?: MonicaContactField[] }).contactfields; if (legacy && Array.isArray(legacy)) { return legacy; } return []; } export function normalizeTask(task: MonicaTask) { return { id: task.id, title: task.title, description: task.description ?? undefined, completed: task.completed, status: task.completed ? 'completed' : 'open', completedAt: task.completed_at ?? undefined, dueAt: task.due_at ?? undefined, contactId: task.contact?.id, contact: task.contact ? normalizeContactSummary(task.contact) : undefined, createdAt: task.created_at, updatedAt: task.updated_at }; } export function normalizeNote(note: MonicaNote) { return { id: note.id, body: note.body, isFavorited: note.is_favorited, favoritedAt: note.favorited_at ?? undefined, contactId: note.contact.id, contact: normalizeContactSummary(note.contact), createdAt: note.created_at, updatedAt: note.updated_at }; } export function normalizeActivity(activity: MonicaActivity) { return { id: activity.id, summary: activity.summary, description: activity.description ?? undefined, happenedAt: activity.happened_at, activityType: activity.activity_type ? { id: activity.activity_type.id, name: activity.activity_type.name, locationType: activity.activity_type.location_type, category: activity.activity_type.activity_type_category?.name ?? null } : null, attendees: activity.attendees.contacts.map((contact) => normalizeContactSummary(contact)), attendeeCount: activity.attendees.total, createdAt: activity.created_at, updatedAt: activity.updated_at }; } export function normalizeAddress(address: MonicaAddress) { return { id: address.id, name: address.name, street: address.street ?? undefined, city: address.city ?? undefined, province: address.province ?? undefined, postalCode: address.postal_code ?? undefined, country: address.country ? { id: address.country.id, name: address.country.name, iso: address.country.iso } : null, contact: normalizeContactSummary(address.contact), createdAt: address.created_at ?? undefined, updatedAt: address.updated_at ?? undefined }; } export function normalizeContactFieldType(type: MonicaContactFieldType) { return { id: type.id, name: type.name, icon: type.fontawesome_icon ?? undefined, protocol: type.protocol ?? undefined, delible: typeof type.delible === 'boolean' ? type.delible : type.delible === 1, kind: type.type ?? undefined, createdAt: type.created_at ?? undefined, updatedAt: type.updated_at ?? undefined }; } export function normalizeContactField(field: MonicaContactField) { return { id: field.id, value: field.data, type: normalizeContactFieldType(field.contact_field_type), contactId: field.contact.id, createdAt: field.created_at ?? undefined, updatedAt: field.updated_at ?? undefined }; } export function normalizeActivityType(activityType: MonicaActivityType) { return { id: activityType.id, name: activityType.name, locationType: activityType.location_type ?? undefined, category: activityType.activity_type_category ? normalizeActivityTypeCategory(activityType.activity_type_category) : null, createdAt: activityType.created_at ?? undefined, updatedAt: activityType.updated_at ?? undefined }; } export function normalizeActivityTypeCategory(category: MonicaActivityTypeCategory) { return { id: category.id, name: category.name, createdAt: category.created_at ?? undefined, updatedAt: category.updated_at ?? undefined }; } export function normalizeCountry(country: MonicaCountry) { return { id: country.id, name: country.name, iso: country.iso }; } export function normalizeGender(gender: MonicaGender) { return { id: gender.id, name: gender.name, createdAt: gender.created_at ?? undefined, updatedAt: gender.updated_at ?? undefined }; } export function normalizeRelationshipType(type: MonicaRelationshipType) { return { id: type.id, name: type.name, reverseName: type.name_reverse_relationship, groupId: type.relationship_type_group_id ?? undefined, isDeletable: Boolean(type.delible), createdAt: type.created_at ?? undefined, updatedAt: type.updated_at ?? undefined }; } export function normalizeRelationship(relationship: MonicaRelationship) { return { id: relationship.id, contactId: relationship.contact_is.id, contact: normalizeContactSummary(relationship.contact_is), relatedContactId: relationship.of_contact.id, relatedContact: normalizeContactSummary(relationship.of_contact), relationshipType: normalizeRelationshipType(relationship.relationship_type), createdAt: relationship.created_at, updatedAt: relationship.updated_at }; } export function normalizeGroup(group: MonicaGroup) {