Skip to content

fix(mcp): capText's truncation marker could push output past budget - #654

Open
teyrebaz33 wants to merge 2 commits into
sapiom:mainfrom
teyrebaz33:fix/mcp-captext-budget-overrun
Open

fix(mcp): capText's truncation marker could push output past budget#654
teyrebaz33 wants to merge 2 commits into
sapiom:mainfrom
teyrebaz33:fix/mcp-captext-budget-overrun

Conversation

@teyrebaz33

@teyrebaz33 teyrebaz33 commented Aug 17, 2026

Copy link
Copy Markdown

Primary change type

  • Bug fix
  • Documentation
  • Feature
  • Tests
  • Dependency update
  • Maintenance or refactor

Problem and motivation

execution-projection.ts documents capText (packages/mcp/src/tools/shared.ts) as a "hard char budget" primitive: "the result is bounded for ANY argument combination." The implementation didn't honor that — it sliced text to exactly budget characters, then appended a truncation marker AFTER the slice, so the returned string was budget + marker.length characters, not budget. Concretely, capText(text, 2000, someUrl) could return a 2094-char string.

Summary and scope

Since the marker's own length depends on the dropped-char count, which depends on where we slice, which depends on the marker's length, this resolves it with a small converging loop that shrinks the slice point until slice + marker fits within budget, with a final .slice(0, budget) backstop for degenerate tiny budgets. This also fixes the reported dropped-char count, which the old version could get slightly wrong for the same reason.

Out of scope: no changes to execution-projection.ts itself or to the field budgets it configures; no dependency changes.

Related work

Related issue or discussion: #653

Validation

# packages/mcp: vitest run / tsc --noEmit / eslint src --ext .ts
15/15 test files, 107/107 tests passing (including the new suite)
tsc --noEmit: clean
eslint: 0 errors, 0 warnings

Tests and documentation

Added packages/mcp/src/tools/shared.test.ts (no test file existed for this module before): a 240-case brute-force sweep across budgets (including 0/1/5/10, and the real PREVIEW_BUDGET/DEFAULT_FIELD_BUDGET values) confirming the fix never exceeds budget, plus a regression test verified via git stash to fail 4/6 cases against the pre-fix implementation and pass 6/6 against the fix. No user-facing documentation changes needed.

Compatibility and release impact

  • Breaking or externally visible changes: None. capText's signature and return type are unchanged; only truncated outputs near a field's budget boundary get slightly shorter (by the marker's length) to actually respect the documented bound.
  • Changeset: Added (.changeset/fix-captext-budget-overrun.md), patch bump.

Security

  • I have not included secrets, credentials, private data, or unsanitized logs.
  • This pull request does not publicly disclose a suspected vulnerability. I
    will follow the
    Security Policy for
    private reporting.

AI assistance

  • I did not use AI assistance for this change.
  • I used AI assistance and have described it below.

I used Claude (Anthropic) as a coding assistant throughout: it helped find the bug (including writing a small script that proved the overrun with real numbers), draft the fix and the brute-force test sweep, and run the verification commands quoted above. I reviewed and ran every command myself, read and understood the resulting diff line by line, and can explain and maintain every change in this PR.

Checklist

  • I read CONTRIBUTING.md, and this contribution follows the direct-PR or issue-first policy.
  • This pull request addresses one focused problem and contains no unrelated cleanup.
  • I added or updated tests, or explained above why tests are not applicable.
  • I ran the relevant build, typecheck, lint, and test commands, or explained
    any N/A checks above.
  • I updated documentation for user-facing changes, or marked it N/A above.
  • I added a Changeset for a published-package change, or explained why it is not applicable.
  • I can explain and maintain every submitted change, including any AI-assisted work.

execution-projection.ts documents capText as a "hard char budget"
primitive: "the result is bounded for ANY argument combination". The
implementation didn't honor that -- it sliced text to exactly budget
characters, then appended a truncation marker (e.g. "...[truncated
48000 chars -- open https://... for the full value]") AFTER the slice,
so the returned string was budget + marker.length chars, not budget.

Concretely: capText(text, 2000, someUrl) could return a 2094-char
string. Verified with a 240-case brute-force sweep across budgets
(including 0/1/5/10) and text lengths that the old implementation
violated the bound in the small-budget cases exercised by
PREVIEW_BUDGET (2000).

Fix: since the marker's own length depends on the dropped-char count,
which depends on where we slice, which depends on the marker's length,
resolve this with a small converging loop that shrinks the slice point
until slice + marker fits within budget, with a final .slice(0,
budget) as a hard backstop for degenerate tiny budgets. Also fixes the
reported dropped-char count, which the old version could get slightly
wrong for the same reason.

Added shared.test.ts (no test file existed for this module) with a
regression test that fails 4/6 cases against the old implementation
(verified via git stash) and passes 6/6 against the fix.

No dependency or lockfile changes.
@github-actions github-actions Bot added contribution: incomplete Required pull request information is incomplete or ambiguous contributor: external Pull request author does not have write, maintain, or admin access to sapiom-js needs-triage Awaiting maintainer review and classification review: manual External pull request requires maintainer review before automation size: medium Review size is 101–500 changed lines area: platform-tools Changes to CLI, MCP, sandbox, tools, or plugins labels Aug 17, 2026
@github-actions github-actions Bot added bug Something isn't working and removed contribution: incomplete Required pull request information is incomplete or ambiguous review: manual External pull request requires maintainer review before automation labels Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: platform-tools Changes to CLI, MCP, sandbox, tools, or plugins bug Something isn't working contributor: external Pull request author does not have write, maintain, or admin access to sapiom-js needs-triage Awaiting maintainer review and classification size: medium Review size is 101–500 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant