Claude Code mod: stop paid Sume calls after N in a session

Write a Claude Code mod that counts paid Sume MCP calls with a tool.call hook, denies call N+1, and fails closed. Code, matcher, and what it cannot cap.

5 min readSume
All posts

To stop a Claude Code session from making more than N paid Sume calls, write a mod with a tool.call hook that matches Sume's paid MCP tool names, counts each call in a variable, and returns { deny: '...' } without calling next once the count passes N. Claude Code's mods overview and events guide (read 2026-10-03) document exactly those pieces: tool.call fires for MCP tool calls, a matcher can be a regular expression over the tool name, and a hook's variables are shared across its hooks for the life of the session.

The mod below is a counter, not a wallet. It limits how many paid calls the model may try; what each call costs is still decided by Sume's admission check and your balance. Mods need Claude Code v2.1.287 or later.

What name does a Sume tool have inside Claude Code?

Claude Code names an MCP tool mcp__<server>__<tool>, and the events guide's own example matches a whole server with /^mcp__github__/. If you add Sume as claude mcp add --transport http sume https://mcp.sume.com/mcp, as the MCP quickstart does, the server name is sume and a paid image call is mcp__sume__generate_image. If you named the server anything else, change the prefix in the pattern.

The tool ids come from Sume's tools and gates page, which lists the paid set: generate_image, generate_video, music_create, tts_create, stt_create, image_upscale_create, rmbg_create, video_upscale_create, kling-motion-control_create, plus the Avatar creates. Run tools_list in a live session to confirm what your account sees; the docs say not to assume parity with the HTTP API.

What does the mod look like?

A mod is a plugin with .claude-plugin/plugin.json, hooks/hooks.json pointing at your code, and hooks/register.js, per the overview. The module exports register(on). This register.js is the whole guard:

const PAID = /^mcp__sume__(generate_image|generate_video|music_create|tts_create|stt_create|image_upscale_create|rmbg_create|video_upscale_create|avatars_create|avatar-videos_create|avatar-image-to-video_create|kling-motion-control_create|script_run)$/
const LIMIT = 5
let used = 0

async function cap($, e, next) {
  if (used >= LIMIT) {
    return { deny: 'Paid Sume call limit (' + LIMIT + ') reached for this session. Stop and ask the user.' }
  }
  used += 1
  $.ui.log('paid Sume call ' + used + ' of ' + LIMIT + ': ' + e.tool)
  return next(e)
}

export function register(on) {
  on('tool.call', { tool: PAID }, cap).catch(async ($, e, next) => {
    return { deny: 'The Sume call guard failed (' + next.error.kind + '), so this call was not run.' }
  })
}

Why is script_run in the list?

Sume's script_run tool runs a short JavaScript program on the Sume side that can loop over several paid tools. Claude Code sees one call to script_run, not the child calls, so a hook on the individual tool names would miss them. Sume's docs say the script itself is bounded by timeout_seconds (5 to 55), max_calls, and max_paid_calls, and that paid creates inside it still need their own idempotency_key. Counting a script_run as one paid call, as above, is the conservative choice; denying it outright is stricter.

The .catch handler matters because of how Claude Code treats a failing hook: one that throws or times out before calling next is skipped, and the tool call proceeds. Attaching .catch makes the guard answer with a deny instead. The post on mod hooks that fail open goes through that case.

What can this mod not cap?

A counter inside Claude Code is easy to over-trust. The table lists what it bounds and what it leaves open, so you can decide which other layers you still need. None of the limits below is a flaw in the hook; each is a consequence of where a mod runs and what a Claude Code session can see.

What a tool.call counter does and does not bound, from Claude Code docs and Sume docs, read 2026-10-03.
QuestionAnswerSource
Does the count survive a new session?Not by itself. The count is a variable in the mod's module; nothing here writes it to disk, so assume a new session starts at zero.Claude Code mods overview
Does a denied call count?Not in this code: the check runs before the increment, so only allowed calls are counted.This example
Does it see calls inside script_run?No, only the script_run call itself.Sume tools and gates
Does it know the price of a call?No. Price comes from Sume's admission check; dry_run=true previews it.Sume tools and gates
Does it bind a client that is not Claude Code?No. Cursor, Codex, or a script using the same key are unaffected.Claude Code mods overview

What should I pair it with?

No single layer should carry the spend limit. These pairings use only gates that Sume documents and settings that Claude Code documents, and each one covers a gap the counter leaves.

  • Connect with OAuth and leave Write off when the task is read-only: a mcp:read session cannot see paid tools, and a paid call returns insufficient_scope (OAuth and API keys).
  • Have the agent call dry_run=true or generation_admission_preview first, and send max_spend_usd on the real call. Sume enforces max_spend_usd only when it is provided, so a mod can also deny any paid call that omits it.
  • Use Claude Code permission ask rules for the same tools if you want a human in the loop rather than a counter; the ask-rule post covers that route.
  • Treat the counter as one layer. Claude Code's docs describe mods as code that runs with your permissions and is not sandboxed, and --safe-mode turns installed mods off.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume