Skip to main content
Glama
klodr

mercury-invoicing-mcp

mercury_create_internal_transfer

Idempotent

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

TableJSON Schema
NameRequiredDescriptionDefault
sourceAccountIdYesSource Mercury account ID
destinationAccountIdYesDestination Mercury account ID
amountYesAmount in USD (>= 0.01)
noteNoOptional note attached to the transfer
idempotencyKeyNoUnique key to prevent duplicate transfers. Auto-generated if omitted; pass an explicit one to make retries safe.

Implementation Reference

  • 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);
    },
  • 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.",
        ),
    },
  • 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,
      },
    );
  • 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,
      );
    }
  • 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",
    };
Behavior5/5

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

Beyond annotations (non-destructive, idempotent), description adds immediate settlement, no approval workflow, permanent ledger changes, and idempotency details.

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?

Well-structured with clear headings (USE WHEN, DO NOT USE, SIDE EFFECTS, RETURNS). Every sentence adds value, no redundancy.

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

Completeness4/5

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

Covers purpose, usage, side effects, and return shape. Lacks explicit error handling but sufficient for a well-annotated read/write tool.

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

Parameters3/5

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

Schema coverage is 100%, so baseline is 3. Description does not add parameter details beyond schema, but is not required to.

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?

Clearly states the tool moves money between two Mercury accounts owned by the same organization, distinguishes from external transfers and approval-gated requests.

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?

Explicitly provides USE WHEN and DO NOT USE scenarios, names alternative tools (mercury_send_money, mercury_request_send_money), and notes workspace requirement.

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/klodr/mercury-invoicing-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server