create-manual-journal
Create manual journal entries in Xero by specifying narration and balanced debit/credit lines with account codes, ensuring proper accounting records.
Instructions
Create a manual journal in Xero. Retrieve a list of account codes in Xero to use for the journal lines. Journal lines must contain at least two individual journal lines with account codes, use basic accounting account types pairing when not specified, and make sure journal line pairs have credit and debit balanced.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| narration | Yes | Description of manual journal being posted | |
| manualJournalLines | Yes | The manualJournalLines element must contain at least two individual manualJournalLine sub-elements | |
| date | No | Optional date in YYYY-MM-DD format | |
| lineAmountTypes | No | Optional line amount types (EXCLUSIVE, INCLUSIVE, NO_TAX), NO_TAX by default | |
| status | No | Optional status of the manual journal (DRAFT, POSTED, DELETED, VOID, ARCHIVED), DRAFT by default | |
| url | No | Optional URL link to a source document | |
| showOnCashBasisReports | No | Optional boolean to show on cash basis reports, default is true |
Implementation Reference
- MCP tool handler: processes arguments, invokes Xero handler, formats success/error responses with deep link.async (args) => { try { const response = await createXeroManualJournal( args.narration, args.manualJournalLines, args.date, args.lineAmountTypes as LineAmountTypes | undefined, args.status as ManualJournal.StatusEnum | undefined, args.url, args.showOnCashBasisReports, ); if (response.isError) { return { content: [ { type: "text" as const, text: `Error creating manual journal: ${response.error}`, }, ], }; } const manualJournal = response.result; const deepLink = manualJournal.manualJournalID ? await getDeepLink( DeepLinkType.MANUAL_JOURNAL, manualJournal.manualJournalID, ) : null; return { content: [ { type: "text" as const, text: [ `Manual journal created: ${manualJournal.narration} (ID: ${manualJournal.manualJournalID})`, manualJournal.date ? `Date: ${manualJournal.date}` : null, manualJournal.status ? `Status: ${manualJournal.status}` : "No status", manualJournal.journalLines ? manualJournal.journalLines.map((line) => ({ type: "text" as const, text: [ `Line Amount: ${line.lineAmount}`, line.accountCode ? `Account Code: ${line.accountCode}` : "No account code", line.description ? `Description: ${line.description}` : "No description", line.taxType ? `Tax Type: ${line.taxType}` : "No tax type", `Tax Amount: ${line.taxAmount}`, ] .filter(Boolean) .join("\n"), })) : [{ type: "text" as const, text: "No journal lines" }], `Show on Cash Basis Reports: ${manualJournal.showOnCashBasisReports}`, deepLink ? `Link to view: ${deepLink}` : null, ] .filter(Boolean) .join("\n"), }, ], }; } catch (error) { const err = ensureError(error); return { content: [ { type: "text" as const, text: `Error creating manual journal: ${err.message}`, }, ], }; } },
- Zod input schema defining parameters for the create-manual-journal tool.{ narration: z .string() .describe("Description of manual journal being posted"), manualJournalLines: z .array( z.object({ lineAmount: z .number() .describe( "Total for manual journal line. Debits are positive, credits are negative value", ), accountCode: z.string().describe("Account code for the journal line"), description: z .string() .optional() .describe("Optional description for manual journal line"), taxType: z .string() .optional() .describe("Optional tax type for the manual journal line"), // TODO: TODO: tracking can be added here }), ) .describe( "The manualJournalLines element must contain at least two individual manualJournalLine sub-elements", ), date: z.string().optional().describe("Optional date in YYYY-MM-DD format"), lineAmountTypes: z .enum(["EXCLUSIVE", "INCLUSIVE", "NO_TAX"]) .optional() .describe( "Optional line amount types (EXCLUSIVE, INCLUSIVE, NO_TAX), NO_TAX by default", ), status: z .enum(["DRAFT", "POSTED", "DELETED", "VOID", "ARCHIVED"]) .optional() .describe( "Optional status of the manual journal (DRAFT, POSTED, DELETED, VOID, ARCHIVED), DRAFT by default", ), url: z .string() .optional() .describe("Optional URL link to a source document"), showOnCashBasisReports: z .boolean() .optional() .describe( "Optional boolean to show on cash basis reports, default is true", ), },
- src/tools/tool-factory.ts:17-19 (registration)Registers all create tools (including create-manual-journal) on the MCP server via ToolFactory.CreateTools.map((tool) => tool()).forEach((tool) => server.tool(tool.name, tool.description, tool.schema, tool.handler), );
- src/tools/create/index.ts:13-25 (registration)Groups and exports CreateManualJournalTool in CreateTools array for registration.export const CreateTools = [ CreateContactTool, CreateCreditNoteTool, CreateManualJournalTool, CreateInvoiceTool, CreateQuoteTool, CreatePaymentTool, CreateItemTool, CreateBankTransactionTool, CreatePayrollTimesheetTool, CreateTrackingCategoryTool, CreateTrackingOptionsTool ];
- Helper handler that wraps Xero API call for creating manual journal with error handling.export async function createXeroManualJournal( narration: string, manualJournalLines: ManualJournalLine[], date?: string, lineAmountTypes?: LineAmountTypes, status?: ManualJournal.StatusEnum, url?: string, showOnCashBasisReports?: boolean, ): Promise<XeroClientResponse<ManualJournal>> { try { const createdManualJournal = await createManualJournal( narration, manualJournalLines, date, lineAmountTypes, status, url, showOnCashBasisReports, ); if (!createdManualJournal) { throw new Error("Manual journal creation failed."); } return { result: createdManualJournal, isError: false, error: null, }; } catch (error) { return { result: null, isError: true, error: formatError(error), }; } }