Skip to content

fix(mcp): honor the configured row limits in export_data (#2012) - #2013

Merged
datlechin merged 1 commit into
mainfrom
fix/mcp-export-row-limit
Aug 4, 2026
Merged

fix(mcp): honor the configured row limits in export_data (#2012)#2013
datlechin merged 1 commit into
mainfrom
fix/mcp-export-row-limit

Conversation

@datlechin

Copy link
Copy Markdown
Member

Fixes #2012.

Root cause

ExportDataTool never read AppSettingsManager.shared.mcp at all. It was the only MCP tool that runs user SQL and returns rows while ignoring Server Configuration entirely: row limits (hardcoded 50,000 / clamp 1...100_000) and query timeout (hardcoded 60s). ExecuteQueryTool is the reference-correct pattern.

Git history shows both tools were born hardcoded in #825. execute_query was later rewired to the settings; export_data was missed. No CHANGELOG entry, commit message, or doc ever justified a higher export ceiling.

The docs already promised the fixed behavior. docs/customization/settings.mdx describes Maximum row limit as "Hard cap on rows per tool call" and Default row limit as "Rows returned when a tool call sets no limit". export_data is a tool call, so the code was wrong, not the docs.

The structural cause is that MCPToolServices carried only connectionBridge and authPolicy, so each tool independently reached around it into the global singleton with copy-pasted boilerplate. Nothing enforced that read, leaving the same omission latent for any future tool.

What changed

export_data now follows the same policy as execute_query: an omitted max_rows uses the default row limit, a supplied value is clamped to the maximum, and the configured query timeout applies.

File Change
MCPLimitResolver.swift (new) Total, non-trapping resolution of max_rows and timeout_seconds from MCPSettings. One policy, shared by every tool.
ExportDataTool.swift Reads the settings; dialect-correct row limit; over-fetch and trim to report is_truncated; routed through ToolQueryExecutor.
MCPSettings.swift validated* accessors; clamps on decode.
AppSettingsManager.swift Clamps row limits and timeout in the mcp didSet, mirroring dataGrid and history.
SettingsValidation.swift mcpRowLimitRange, mcpQueryTimeoutRange.
MCPToolServices.swift Internal settingsProvider seam. The public 2-argument init is unchanged, so no call site breaks.
ExecuteQueryTool, ConfirmDestructiveOperationTool, 2 chat tools Use the shared resolver instead of four copies of the singleton read.
MCPArgumentDecoder, ChatToolArgumentDecoder The default value now flows through the clamp.

Two further bugs found and fixed

A reachable crash. The settings fields are bare TextFields with no validation, and AppSettingsManager clamped only the auth coupling. Typing 0 into "Maximum row limit" made ExecuteQueryTool form clamp: 1...0, which traps. Writing the export fix in that same shape would have added a second crash site, so the resolver is total by construction and the settings clamp at both boundaries.

export_data was broken outright on SQL Server, Oracle, and Teradata. It emitted SELECT * FROM x LIMIT n regardless of dialect, which is a syntax error on those engines. The row limit is now written in each database's own syntax (TOP, FETCH FIRST, LIMIT). The issue was filed against PostgreSQL, so this surfaced only as a limit bug.

Behavior change

Export's effective default drops from 50,000 to 500. Anyone relying on the old default must pass max_rows or raise the setting. The CHANGELOG says so plainly, and export_data now returns is_truncated so a clipped export is visible rather than silent.

Tests

50 tests pass, zero failures, across 6 suites. 27 are new:

  • MCPLimitResolverTests (new): defaulting, clamping, a default above the maximum, and the zero/negative cases that used to trap.
  • MCPSettingsTests: decode clamping, and that the requestable range can never invert.
  • ExportDataToolTests: the SQL emitted for each dialect, the truncation decision, and a probe proving the tool consults the settings seam.
  • ChatToolArgumentDecoderTests: out-of-range and non-finite numbers.

ExecuteQueryToolTests and ToolsListHandlerTests were run as regression checks. SwiftLint --strict reports 0 violations.

Review

An adversarial review raised 8 findings; 7 were refuted against source. The survivor was a pre-existing Int(double) trap on model-controlled AI-chat input, sitting on a line this diff touched and reproduced by the verifier at exit 133. Fixed here, since it is the same class of defect this change exists to eliminate.

Not fixed (pre-existing, unchanged)

A multi-table export applies the limit per table, so one call can return N times the maximum. Changing that to a shared budget across tables is a separate design decision.

Follow-up

Localizable.xcstrings is deliberately not in this commit. Xcode extracts the reworded tool descriptions on its next build, and that file carries unrelated churn.

@mintlify

mintlify Bot commented Aug 3, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 3, 2026, 6:45 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 936a654 into main Aug 4, 2026
3 checks passed
@datlechin
datlechin deleted the fix/mcp-export-row-limit branch August 4, 2026 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Changing Maximum Row Limit for MCP Server not respected in export_data

1 participant