Skip to content

Addie billing tools: surface did_you_mean in tool response + tighten lookup_key tool descriptions #2550

@EmmaLouise2018

Description

@EmmaLouise2018

Background

Spun out of #2481 review (thanks @bokelley). The hotfix in #2481 makes getPriceByLookupKey resolve unambiguous aliases like explorer_annualaao_membership_explorer_50, and refuse ambiguous ones. That's a band-aid — root cause is that Addie's LLM invents lookup keys from tier name + interval instead of passing the canonical lookup_key returned by find_membership_products.

Every silent server-side remap is a missed chance to teach the model. With the refusal-on-ambiguity guard in #2481, the resolver only helps in the unambiguous case (today: just explorer_50). As soon as anyone adds a monthly Explorer SKU, explorer_annual will collide → undefined → "No price found" error. Safe behavior, but will look like a regression to whoever adds that SKU.

What to do

1. Surface did_you_mean in the tool response

Mirror the pattern from PR #2482. When create_payment_link / send_invoice / confirm_send_invoice (in server/src/addie/mcp/billing-tools.ts) get a non-canonical lookup_key:

  • If getPriceByLookupKey resolved an alias, include the canonical key in the response so the LLM sees "you asked for explorer_annual; canonical is aao_membership_explorer_50 — use that next time."
  • If it didn't resolve (ambiguous or unknown), the existing "No price found. Available: ..." error already gives the LLM the canonical options.

The point: make the canonical key visible to the model so subsequent calls in the same conversation use it verbatim.

2. Tighten tool descriptions

In server/src/addie/mcp/billing-tools.ts, update the description fields on create_payment_link, send_invoice, confirm_send_invoice so the model is explicitly told:

Pass lookup_key verbatim from the find_membership_products response. Do not construct it from the tier name and billing interval (e.g. do not pass explorer_annual — pass aao_membership_explorer_50).

Today's descriptions just say "The product lookup key from find_membership_products" — that's what the LLM is already (mis-)interpreting.

3. Preserve interval signal as a tiebreaker (optional)

When/if the catalog gains annual+monthly pairs for a tier, resolveLookupKeyAlias could keep the stripped suffix (annual / monthly) and prefer a price whose recurring.interval matches. Today this is theoretical, but worth wiring up before the catalog change happens.

Acceptance

  • LLM receives canonical lookup_key in create_payment_link response when it passed an alias.
  • Tool descriptions explicitly forbid constructing the key.
  • The TODO in resolveLookupKeyAlias (server/src/billing/stripe-client.ts) pointing at this issue can be removed.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions