monica_manage_financial_record
Manage financial records in Monica CRM by creating, updating, deleting, listing, or viewing gifts and debts associated with contacts.
Instructions
List, inspect, create, update, or delete gifts and debts. Use recordType="gift" for presents, recordType="debt" for money owed.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| recordType | Yes | ||
| action | Yes | ||
| recordId | No | ||
| contactId | No | ||
| limit | No | ||
| page | No | ||
| giftPayload | No | ||
| debtPayload | No |
Implementation Reference
- src/tools/modules/financial.ts:87-226 (handler)Main handler function for 'gift' recordType operations: list, get, create, update, delete gifts using MonicaClient API calls, normalization, and logging.async function handleGift({ input, client, logger }: { input: GiftInput; client: ToolRegistrationContext['client']; logger: ToolRegistrationContext['logger']; }) { const { action } = input; switch (action) { case 'list': { const response = await client.listGifts({ contactId: input.contactId, limit: input.limit, page: input.page }); const gifts = response.data.map(normalizeGift); const scope = input.contactId ? `contact ${input.contactId}` : 'your account'; const summary = gifts.length ? `Found ${gifts.length} gift${gifts.length === 1 ? '' : 's'} for ${scope}.` : `No gifts found for ${scope}.`; return { content: [ { type: 'text' as const, text: summary } ], structuredContent: { recordType: input.recordType, action, contactId: input.contactId, gifts, pagination: { currentPage: response.meta.current_page, lastPage: response.meta.last_page, perPage: response.meta.per_page, total: response.meta.total } } }; } case 'get': { if (input.recordId == null) { return missingIdError('gift'); } const response = await client.getGift(input.recordId); const gift = normalizeGift(response.data); return { content: [ { type: 'text' as const, text: `Gift ${gift.title} (ID ${gift.id}).` } ], structuredContent: { recordType: input.recordType, action, recordId: input.recordId, gift } }; } case 'create': { if (!input.giftPayload) { return missingPayloadError('gift'); } const response = await client.createGift(toGiftCreatePayload(input.giftPayload)); const gift = normalizeGift(response.data); logger.info({ giftId: gift.id, contactId: gift.contact.id }, 'Created Monica gift'); return { content: [ { type: 'text' as const, text: `Created gift ${gift.title} (ID ${gift.id}).` } ], structuredContent: { recordType: input.recordType, action, gift } }; } case 'update': { if (input.recordId == null) { return missingIdError('gift'); } if (!input.giftPayload) { return missingPayloadError('gift'); } const response = await client.updateGift(input.recordId, toGiftUpdatePayload(input.giftPayload)); const gift = normalizeGift(response.data); logger.info({ giftId: gift.id }, 'Updated Monica gift'); return { content: [ { type: 'text' as const, text: `Updated gift ${gift.title} (ID ${gift.id}).` } ], structuredContent: { recordType: input.recordType, action, recordId: input.recordId, gift } }; } case 'delete': { if (input.recordId == null) { return missingIdError('gift'); } const result = await client.deleteGift(input.recordId); logger.info({ giftId: input.recordId }, 'Deleted Monica gift'); return { content: [ { type: 'text' as const, text: `Deleted gift ID ${input.recordId}.` } ], structuredContent: { recordType: input.recordType, action, recordId: input.recordId, result } }; } default: return unsupportedAction(action); } }
- src/tools/modules/financial.ts:228-373 (handler)Main handler function for 'debt' recordType operations: list, get, create, update, delete debts using MonicaClient API calls, normalization, and logging.async function handleDebt({ input, client, logger }: { input: DebtInput; client: ToolRegistrationContext['client']; logger: ToolRegistrationContext['logger']; }) { const { action } = input; switch (action) { case 'list': { const response = await client.listDebts({ contactId: input.contactId, limit: input.limit, page: input.page }); const debts = response.data.map(normalizeDebt); const scope = input.contactId ? `contact ${input.contactId}` : 'your account'; const summary = debts.length ? `Found ${debts.length} debt${debts.length === 1 ? '' : 's'} for ${scope}.` : `No debts found for ${scope}.`; return { content: [ { type: 'text' as const, text: summary } ], structuredContent: { recordType: input.recordType, action, contactId: input.contactId, debts, pagination: { currentPage: response.meta.current_page, lastPage: response.meta.last_page, perPage: response.meta.per_page, total: response.meta.total } } }; } case 'get': { if (input.recordId == null) { return missingIdError('debt'); } const response = await client.getDebt(input.recordId); const debt = normalizeDebt(response.data); return { content: [ { type: 'text' as const, text: `Debt ID ${debt.id} (${debt.amount} ${debt.currency?.iso ?? ''}).` } ], structuredContent: { recordType: input.recordType, action, recordId: input.recordId, debt } }; } case 'create': { if (!input.debtPayload) { return missingPayloadError('debt'); } const response = await client.createDebt(toDebtCreatePayload(input.debtPayload)); const debt = normalizeDebt(response.data); logger.info({ debtId: debt.id, contactId: debt.contact.id }, 'Created Monica debt'); return { content: [ { type: 'text' as const, text: `Created debt ID ${debt.id} (${debt.amount} ${debt.currency?.iso ?? ''}).` } ], structuredContent: { recordType: input.recordType, action, debt } }; } case 'update': { if (input.recordId == null) { return missingIdError('debt'); } if (!input.debtPayload) { return missingPayloadError('debt'); } const response = await client.updateDebt(input.recordId, toDebtUpdatePayload(input.debtPayload)); const debt = normalizeDebt(response.data); logger.info({ debtId: debt.id }, 'Updated Monica debt'); return { content: [ { type: 'text' as const, text: `Updated debt ID ${debt.id} (${debt.amount} ${debt.currency?.iso ?? ''}).` } ], structuredContent: { recordType: input.recordType, action, recordId: input.recordId, debt } }; } case 'delete': { if (input.recordId == null) { return missingIdError('debt'); } const result = await client.deleteDebt(input.recordId); logger.info({ debtId: input.recordId }, 'Deleted Monica debt'); return { content: [ { type: 'text' as const, text: `Deleted debt ID ${input.recordId}.` } ], structuredContent: { recordType: input.recordType, action, recordId: input.recordId, result } }; } default: return unsupportedAction(action); } }
- src/tools/modules/financial.ts:14-62 (schema)Zod schemas for validating tool inputs: giftPayloadSchema, debtPayloadSchema, financialInputSchema (combines recordType, action, payloads), and derived types.const giftPayloadSchema = z.object({ contactId: z.number().int().positive(), receivedOn: z .string() .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/u, 'receivedOn must use YYYY-MM-DD format.'), title: z.string().min(1).max(255), description: z.string().max(1_000_000).optional().nullable(), wasGivenByMe: z.boolean().optional(), amount: z.number().optional(), currencyId: z.number().int().positive().optional() }); type GiftPayloadForm = z.infer<typeof giftPayloadSchema>; const debtPayloadSchema = z.object({ contactId: z.number().int().positive(), description: z.string().max(1_000_000).optional().nullable(), amount: z.number().positive(), currencyId: z.number().int().positive().optional(), happenedAt: z .string() .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/u, 'happenedAt must use YYYY-MM-DD format.'), isSettled: z.boolean().optional(), settledAt: z .string() .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/u, 'settledAt must use YYYY-MM-DD format.') .optional() .nullable() }); type DebtPayloadForm = z.infer<typeof debtPayloadSchema>; const financialInputShape = { recordType: z.enum(['gift', 'debt']), action: z.enum(['list', 'get', 'create', 'update', 'delete']), recordId: z.number().int().positive().optional(), contactId: z.number().int().positive().optional(), limit: z.number().int().min(1).max(100).optional(), page: z.number().int().min(1).optional(), giftPayload: giftPayloadSchema.optional(), debtPayload: debtPayloadSchema.optional() } as const; const financialInputSchema = z.object(financialInputShape); type FinancialInput = z.infer<typeof financialInputSchema>; type GiftInput = FinancialInput & { recordType: 'gift' }; type DebtInput = FinancialInput & { recordType: 'debt' };
- src/tools/modules/financial.ts:67-84 (registration)Registers the 'monica_manage_financial_record' tool with server.registerTool, providing title, description, inputSchema, and dispatcher handler that routes to handleGift or handleDebt.server.registerTool( 'monica_manage_financial_record', { title: 'Manage Monica gifts and debts', description: 'List, inspect, create, update, or delete gifts and debts. Use recordType="gift" for presents, recordType="debt" for money owed.', inputSchema: financialInputShape }, async (rawInput) => { const input = financialInputSchema.parse(rawInput); if (input.recordType === 'gift') { return handleGift({ input: input as GiftInput, client, logger }); } return handleDebt({ input: input as DebtInput, client, logger }); } );
- src/tools/registerTools.ts:39-39 (registration)Calls registerFinancialTools as part of the overall registerTools function that registers all MCP tools.registerFinancialTools(context);
- src/tools/modules/financial.ts:64-85 (helper)Module-level registration function for financial tools, invoked from registerTools.ts.export function registerFinancialTools(context: ToolRegistrationContext): void { const { server, client, logger } = context; server.registerTool( 'monica_manage_financial_record', { title: 'Manage Monica gifts and debts', description: 'List, inspect, create, update, or delete gifts and debts. Use recordType="gift" for presents, recordType="debt" for money owed.', inputSchema: financialInputShape }, async (rawInput) => { const input = financialInputSchema.parse(rawInput); if (input.recordType === 'gift') { return handleGift({ input: input as GiftInput, client, logger }); } return handleDebt({ input: input as DebtInput, client, logger }); } ); }