Skip to main content
Glama
akutishevsky

LunchMoney MCP Server

update_manual_crypto

Idempotent

Update a manual crypto asset's balance and optional details in Lunch Money. Requires the asset ID from a manual crypto list.

Instructions

Update a manually-managed crypto asset via the v1 crypto endpoint. The id must be from a get_all_crypto result with source=manual.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
crypto_idYesID of the manual crypto asset to update. Synced crypto assets cannot be updated.
balanceYesUpdated balance of the crypto account.
nameNoOptional official or full name of the account.
display_nameNoOptional display name for the account.
institution_nameNoOptional provider that holds the asset.
currencyNoOptional supported cryptocurrency code.

Implementation Reference

  • Handler function for update_manual_crypto tool. Calls PUT /crypto/manual/{crypto_id} with balance (required), and optional name, display_name, institution_name, and currency fields.
        async ({
            crypto_id,
            balance,
            name,
            display_name,
            institution_name,
            currency,
        }) => {
            try {
                const body: Record<string, unknown> = {
                    balance: balance.toString(),
                };
    
                if (name !== undefined) body.name = name;
                if (display_name !== undefined)
                    body.display_name = display_name;
                if (institution_name !== undefined)
                    body.institution_name = institution_name;
                if (currency !== undefined) body.currency = currency;
    
                const response = await apiV1.put(
                    `/crypto/manual/${crypto_id}`,
                    body,
                );
    
                if (!response.ok) {
                    return handleApiError(
                        response,
                        "Failed to update crypto asset",
                    );
                }
    
                return dataResponse(await response.json());
            } catch (error) {
                return catchError(error, "Failed to update crypto asset");
            }
        },
    );
  • Input schema for update_manual_crypto. Defines required crypto_id (number) and balance (number), plus optional name (max 45), display_name (max 25), institution_name (max 50), and currency (string).
    inputSchema: {
        crypto_id: z.coerce
            .number()
            .describe(
                "ID of the manual crypto asset to update. Synced crypto assets cannot be updated.",
            ),
        balance: z.coerce
            .number()
            .describe("Updated balance of the crypto account."),
        name: z
            .string()
            .max(45)
            .optional()
            .describe("Optional official or full name of the account."),
        display_name: z
            .string()
            .max(25)
            .optional()
            .describe("Optional display name for the account."),
        institution_name: z
            .string()
            .max(50)
            .optional()
            .describe("Optional provider that holds the asset."),
        currency: z
            .string()
            .optional()
            .describe("Optional supported cryptocurrency code."),
    },
  • Registration of update_manual_crypto tool via server.registerTool(), called by registerCryptoTools() which is invoked from src/index.ts line 33.
    server.registerTool(
        "update_manual_crypto",
        {
            description:
                "Update a manually-managed crypto asset via the v1 crypto endpoint. The id must be from a get_all_crypto result with source=manual.",
            inputSchema: {
                crypto_id: z.coerce
                    .number()
                    .describe(
                        "ID of the manual crypto asset to update. Synced crypto assets cannot be updated.",
                    ),
                balance: z.coerce
                    .number()
                    .describe("Updated balance of the crypto account."),
                name: z
                    .string()
                    .max(45)
                    .optional()
                    .describe("Optional official or full name of the account."),
                display_name: z
                    .string()
                    .max(25)
                    .optional()
                    .describe("Optional display name for the account."),
                institution_name: z
                    .string()
                    .max(50)
                    .optional()
                    .describe("Optional provider that holds the asset."),
                currency: z
                    .string()
                    .optional()
                    .describe("Optional supported cryptocurrency code."),
            },
            annotations: {
                idempotentHint: true,
            },
        },
        async ({
            crypto_id,
            balance,
            name,
            display_name,
            institution_name,
            currency,
        }) => {
            try {
                const body: Record<string, unknown> = {
                    balance: balance.toString(),
                };
    
                if (name !== undefined) body.name = name;
                if (display_name !== undefined)
                    body.display_name = display_name;
                if (institution_name !== undefined)
                    body.institution_name = institution_name;
                if (currency !== undefined) body.currency = currency;
    
                const response = await apiV1.put(
                    `/crypto/manual/${crypto_id}`,
                    body,
                );
    
                if (!response.ok) {
                    return handleApiError(
                        response,
                        "Failed to update crypto asset",
                    );
                }
    
                return dataResponse(await response.json());
            } catch (error) {
                return catchError(error, "Failed to update crypto asset");
            }
        },
    );
  • apiV1 helper used by the handler to make the PUT request to the v1 dev endpoint (https://dev.lunchmoney.app/v1).
    export const apiV1 = {
        get: (path: string) =>
            apiRequest("GET", path, undefined, "https://dev.lunchmoney.app/v1"),
        put: (path: string, body: unknown) =>
            apiRequest("PUT", path, body, "https://dev.lunchmoney.app/v1"),
    };
  • CryptoAsset type definition used by the get_all_crypto tool (referenced in tool description for update_manual_crypto).
    export interface CryptoAsset {
        id?: number | null;
        zabo_account_id?: number | null;
        source: "manual" | "synced";
        name: string;
        display_name: string | null;
        balance: string;
        balance_as_of?: string;
        currency: string;
        status: string;
        institution_name: string | null;
        created_at: string;
        to_base?: number | null;
    }
Behavior2/5

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

Annotations only provide idempotentHint=true. The description adds no details about side effects, partial updates, or response format, which is insufficient for a mutation tool with minimal annotation coverage.

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?

The description is two concise sentences, front-loaded with the essential action and constraint, with no redundant information.

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

Completeness2/5

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

Given the tool has 6 parameters, no output schema, and minimal annotations, the description lacks return value information and does not explain permission requirements or other behavioral context.

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. The description does not add meaningful information beyond what the schema already provides for each parameter.

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?

The description clearly states the tool updates a manually-managed crypto asset via a specific endpoint, distinguishing it from siblings like update_manual_account. It provides a verb-resource pair with precise scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description specifies that the id must come from get_all_crypto with source=manual, giving a clear precondition. It implicitly excludes synced assets. However, it does not elaborate on other contextual prerequisites.

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/akutishevsky/lunchmoney-mcp'

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