mercury_create_internal_transfer
Move money between your own Mercury accounts to rebalance cash or fund sub-accounts. Settles immediately with no approval required.
Instructions
Move money between two of your own Mercury accounts (e.g. Checking → Savings). Funds stay inside your organisation.
USE WHEN: rebalancing cash between your own Mercury accounts — sweeping idle deposits to Treasury, funding a sub-account before issuing cards, etc. Both accounts must belong to your workspace.
DO NOT USE: to send money to an external counterparty (use mercury_send_money). To request approval-gated movement, use mercury_request_send_money (different surface, external only).
SIDE EFFECTS: moves real money between two accounts you own. Settles immediately, no approval workflow because no external recipient is involved. Persistent ledger entries on both sides. Idempotent via idempotencyKey — auto-generated if omitted, but pass an explicit one to make retries safe.
RETURNS: { id, amount, status, ... } — the booked transfer.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sourceAccountId | Yes | Source Mercury account ID | |
| destinationAccountId | Yes | Destination Mercury account ID | |
| amount | Yes | Amount in USD (>= 0.01) | |
| note | No | Optional note attached to the transfer | |
| idempotencyKey | No | Unique key to prevent duplicate transfers. Auto-generated if omitted; pass an explicit one to make retries safe. |
Implementation Reference
- src/tools/transactions.ts:169-173 (handler)The handler function for mercury_create_internal_transfer. It sends a POST request to /transfer with sourceAccountId, destinationAccountId, amount, optional note, and an idempotencyKey (auto-generated if omitted).
async ({ idempotencyKey, ...body }) => { const idem = idempotencyKey ?? randomUUID(); const data = await client.post(`/transfer`, { ...body, idempotencyKey: idem }); return textResult(data); }, - src/tools/transactions.ts:157-168 (schema)Zod input schema for mercury_create_internal_transfer: sourceAccountId (UUID), destinationAccountId (UUID), amount (number >= 0.01), optional note, optional idempotencyKey.
{ sourceAccountId: z.uuid().describe("Source Mercury account ID"), destinationAccountId: z.uuid().describe("Destination Mercury account ID"), amount: z.number().min(0.01).describe("Amount in USD (>= 0.01)"), note: z.string().optional().describe("Optional note attached to the transfer"), idempotencyKey: z .string() .optional() .describe( "Unique key to prevent duplicate transfers. Auto-generated if omitted; pass an explicit one to make retries safe.", ), }, - src/tools/transactions.ts:143-180 (registration)Registration of the mercury_create_internal_transfer tool via defineTool inside registerTransactionTools. Registered with idempotentHint: true (safe to retry) and no destructiveHint (no external recipient).
defineTool( server, "mercury_create_internal_transfer", [ "Move money between two of your own Mercury accounts (e.g. Checking → Savings). Funds stay inside your organisation.", "", "USE WHEN: rebalancing cash between your own Mercury accounts — sweeping idle deposits to Treasury, funding a sub-account before issuing cards, etc. Both accounts must belong to your workspace.", "", "DO NOT USE: to send money to an external counterparty (use `mercury_send_money`). To request approval-gated movement, use `mercury_request_send_money` (different surface, external only).", "", "SIDE EFFECTS: **moves real money** between two accounts you own. Settles immediately, no approval workflow because no external recipient is involved. Persistent ledger entries on both sides. **Idempotent via `idempotencyKey`** — auto-generated if omitted, but pass an explicit one to make retries safe.", "", "RETURNS: `{ id, amount, status, ... }` — the booked transfer.", ].join("\n"), { sourceAccountId: z.uuid().describe("Source Mercury account ID"), destinationAccountId: z.uuid().describe("Destination Mercury account ID"), amount: z.number().min(0.01).describe("Amount in USD (>= 0.01)"), note: z.string().optional().describe("Optional note attached to the transfer"), idempotencyKey: z .string() .optional() .describe( "Unique key to prevent duplicate transfers. Auto-generated if omitted; pass an explicit one to make retries safe.", ), }, async ({ idempotencyKey, ...body }) => { const idem = idempotencyKey ?? randomUUID(); const data = await client.post(`/transfer`, { ...body, idempotencyKey: idem }); return textResult(data); }, { title: "Create Internal Transfer", destructiveHint: false, idempotentHint: true, openWorldHint: true, }, ); - src/tools/_shared.ts:28-54 (helper)The defineTool helper: wraps the handler with rate-limit/dry-run/audit middleware via wrapToolHandler, creates a strict Zod schema, and registers the tool on the MCP server via server.registerTool.
export function defineTool<S extends ZodRawShape>( server: McpServer, name: string, description: string, inputSchema: S, handler: (args: z.infer<z.ZodObject<S>>) => Promise<ToolResult>, annotations: ToolAnnotations, ): void { const wrapped = wrapToolHandler(name, handler); const strictSchema = z.object(inputSchema).strict(); // MCP behavioral annotations (readOnlyHint / destructiveHint / // idempotentHint / openWorldHint) — declared machine-readable so // hosts and rubrics (TDQS / Glama Behavior dimension) can detect // tool semantics without scraping the prose description. Required // (not optional) so every new tool ships with explicit semantics — // forgetting the annotation now fails typecheck instead of // silently shipping a tool with no hint set. // The MCP SDK overloads `registerTool` with shape narrowing the runtime // strict-schema and the wrapped callback can't satisfy through generics. // Both casts are runtime-safe — the signatures only diverge at the type // level. Asserted by the existing tool-registration tests. (server.registerTool as unknown as (...a: unknown[]) => unknown)( name, { description, inputSchema: strictSchema, annotations }, wrapped, ); } - src/middleware.ts:46-71 (registration)Tool-to-bucket mapping in middleware. mercury_create_internal_transfer is mapped to the 'internal_transfer' bucket for rate limiting (daily: 2, monthly: 40).
const TOOL_BUCKET: Record<string, string> = { // Money out mercury_send_money: "payments", mercury_request_send_money: "payments", mercury_create_internal_transfer: "internal_transfer", // Invoicing mercury_create_invoice: "invoices_write", mercury_update_invoice: "invoices_write", mercury_cancel_invoice: "invoices_cancel", // Customers (AR) mercury_create_customer: "customers_write", mercury_update_customer: "customers_write", mercury_delete_customer: "customers_write", // Banking writes mercury_add_recipient: "recipients_add", mercury_update_recipient: "recipients_update", mercury_update_transaction: "transactions_update", // Webhooks mercury_create_webhook: "webhooks_create", mercury_update_webhook: "webhooks_update", mercury_delete_webhook: "webhooks_delete", };