Skip to content

Add merchant management tools (get_merchant, list_merchants, update_merchant) #4

@krisrowe

Description

@krisrowe

Summary

We have UPDATE_MERCHANT_MUTATION in queries.py but it's only used indirectly through update_recurring, which requires an existing stream_id. There's no way to:

  1. List merchants (with optional name filter)
  2. Get a single merchant by ID
  3. Update a merchant's name or recurring settings directly

This blocks the bill-check workflow when we need to find a merchant, create a recurring stream, or rename a merchant in place.

Proposed Tools

list_merchants(name_contains=None)

List all merchants. Optional name_contains filter narrows results (case-insensitive substring match). Returns canonical merchant representation:

{
  "id": "...",
  "name": "Acme Corp",
  "recurring": {
    "frequency": "monthly",
    "amount": -661.10,
    "starting": "2026-01-09",
    "status": "active"
  }
}

Where recurring is null if the merchant has no recurring stream.

get_merchant(merchant_id)

Get a single merchant by ID. Same output shape as list_merchants items.

update_merchant(merchant_id, name=None, recurring=None)

PATCH semantics — only provided fields are updated.

Example 1: Make a merchant recurring for the first time

The merchant exists but has never been flagged as recurring. This should create a new recurring stream that appears in list_recurring.

update_merchant("merchant_id", recurring={
    "frequency": "monthly",
    "amount": -500.00,
    "starting": "2026-01-09",
    "status": "active"
})

Example 2: Make a merchant non-recurring (remove from recurring entirely)

The merchant currently has a recurring stream. This should remove it from list_recurring permanently. The merchant still exists, it just won't appear as a recurring obligation anymore.

update_merchant("merchant_id", recurring=False)

Example 3: Cancel/deactivate a recurring stream (keep but mark inactive)

The merchant stays in the recurring system but is marked canceled. Useful for obligations that ended but you want to keep the history.

update_merchant("merchant_id", recurring={"status": "canceled"})

Example 4: Update just the amount for existing recurring

The merchant is already recurring. Only the amount changes — frequency, start date, and status are preserved.

update_merchant("merchant_id", recurring={"amount": -700.00})

Example 5: Rename a merchant

update_merchant("merchant_id", name="New Merchant Name")

Example 6: Rename and set up recurring in one call

update_merchant("merchant_id", name="New Name", recurring={
    "frequency": "monthly",
    "amount": -500.00,
    "starting": "2026-01-09",
    "status": "active"
})

Tool Documentation Requirements

update_merchant tool description must:

  • Explicitly state that setting recurring on a merchant creates or updates the recurring stream visible in list_recurring
  • Document each example above (first-time recurring, remove recurring, cancel, update amount, rename)
  • Clarify the difference between removing recurring (recurring=False) vs canceling (recurring={"status": "canceled"})
  • Note PATCH semantics: only provided keys in the recurring object are changed; omitted keys are preserved

list_recurring tool description must:

  • Reference update_merchant as the way to add, modify, or remove recurring streams
  • State: "To add a new recurring obligation, find the merchant via list_merchants, then use update_merchant to flag it as recurring"
  • State: "To deactivate or remove a recurring stream, use update_merchant with the merchant ID (available in each stream's data)"

This cross-referencing is critical for AI agents to discover the right tool for the job.

Testing Required — Verify Before Documenting

IMPORTANT: The crossover behavior between UPDATE_MERCHANT_MUTATION and the recurring streams list has NOT been fully verified via live API testing. Before committing changes — especially tool documentation that makes claims about behavior — the following must be tested against the live Monarch API:

What we know (from existing docs and code)

From RECURRING.md:

  • "Items are transaction-seeded — cannot create from thin air without a transaction" (line 102)
  • "Each merchant can only have one recurring stream" (line 103)
  • Open question #10: "What mutation fires when adding a recurring merchant?" — never answered
  • "No create or delete mutations have been found in any community library" (line 109)

From the memory reference:

  • "Create via merchant — UI only — no known GraphQL mutation for setting recurring flag on merchant"

From the codebase:

  • UPDATE_MERCHANT_MUTATION exists and accepts a recurrence object (isRecurring, frequency, amount, baseDate, isActive)
  • update_recurring already uses this mutation successfully to modify existing streams

What must be verified

Test Question Impact on documentation
Create recurring via mutation Does calling UPDATE_MERCHANT_MUTATION with isRecurring: True on a merchant with NO existing stream actually create a new stream in list_recurring? If no, Example 1 is wrong and we need a different approach
Remove recurring via mutation Does setting isRecurring: False remove the stream from list_recurring? Or do we need Common_MarkAsNotRecurring? Affects Example 2
Cancel vs remove Does isActive: False keep the stream visible but inactive, vs isRecurring: False removing it entirely? Affects distinction between Examples 2 and 3
Transaction-seeded requirement Can we create a recurring stream for a merchant that has transactions but was never auto-detected? (Our use case — renamed payment merchants) Core use case viability
Idempotency Is calling the mutation on an already-recurring merchant safe? Does it preserve existing stream data for omitted fields? Affects PATCH semantics claims

Recommended approach

  1. Implement the tool with the proposed interface
  2. Write integration tests that exercise each scenario above against the live API
  3. Only after tests pass, update the tool documentation with confirmed behavior
  4. Update RECURRING.md with answers to open question #10 and the verified behavior

Do NOT document crossover behavior (e.g., "this creates a recurring stream") as fact until verified by test.

GraphQL Queries Available

Both queries are already defined in queries.py:

MERCHANT_SEARCH_QUERY   # Search merchants by name, returns recurring stream info
GET_MERCHANT_QUERY      # Get single merchant by ID with transaction count, rule count, recurring
UPDATE_MERCHANT_MUTATION # Update name and/or recurrence settings

Implementation Notes

  • The recurrence input maps: isRecurring → recurring presence, frequency → frequency, amount → amount, baseDate → starting, isActive → status (active/canceled)
  • update_recurring in recurring.py already builds the mutation variables — refactor to share logic
  • Consider deprecating update_recurring in favor of update_merchant (or make it a thin wrapper)
  • Expose merchant_id in list_recurring output if not already present

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions