Skip to content

Refactor: Replace stateful set_active_account with per-tool account_id parameter #314

@mattzcarey

Description

@mattzcarey

Problem

The current account selection flow requires a stateful two-step dance:

  1. Call accounts_list to discover accounts
  2. Call set_active_account to persist the selection in a Durable Object

This breaks in many MCP clients that don't maintain state across tool calls, leading to repeated "No currently active accountId" errors. Multiple issues have been filed about this: #226, #260, #281, #175.

Additional problems with the current approach:

Proposed Solution

Add an optional account_id parameter to every tool that requires an account, and remove the stateful set_active_account tool entirely.

Spec

Resolution logic (resolveAccountId)

When a tool needs an account ID, resolve it in this order:

  1. Account token: Always use props.account.id. Ignore any account_id parameter.
  2. User token + account_id provided: Validate it against props.accounts. Use it if valid, return error listing valid accounts if not.
  3. User token + single account + no account_id: Auto-select the only account.
  4. User token + multiple accounts + no account_id: Return an error response listing available accounts (id + name) so the LLM can self-correct on the next call.

Tool changes

  • Add account_id?: string (optional Zod param) to every tool that currently calls getActiveAccountId()
  • Shared AccountIdParam schema and resolveAccountId() helper in a new packages/mcp-common/src/tools/account.helpers.ts
  • Error responses include available_accounts list — no separate accounts_list call needed to recover

What gets removed

  • set_active_account tool registration (from account.tools.ts)
  • getActiveAccountId() / setActiveAccountId() from the CloudflareMcpAgent interface
  • getActiveAccountId() / setActiveAccountId() implementations from all ~14 app classes
  • active_account_id storage from UserDetails Durable Object
  • MISSING_ACCOUNT_ID_RESPONSE constant

What stays

  • accounts_list tool (for explicit discovery)
  • UserDetails Durable Object class (may be used for other per-user state)

Affected tools (~20+ across packages/mcp-common and apps/)

packages/mcp-common/src/tools/: d1 (5), kv (5), r2 (4), workers (3), zones (2), hyperdrive (4)

apps/: ai-gateway, workers-observability, workers-builds, logpush, autorag, auditlogs, browser-rendering, dex-analysis, dns-analytics, graphql, radar, cloudflare-one-casb

Benefits

  • Stateless: Works with any MCP client, no state management required
  • Fewer round-trips: Single account users auto-resolve; multi-account users get account list in the error response
  • Self-correcting: Error responses include available accounts, so LLMs can retry immediately with the right ID
  • Simpler code: No Durable Object reads on every tool call, synchronous resolution from props

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