Skip to content

feat(mcp): opt-in compact native tools/list and trimmed tool guidance - #45084

Merged
aminghadersohi merged 11 commits into
apache:masterfrom
aminghadersohi:mcp-native-list-compaction
Oct 10, 2026
Merged

aminghadersohi merged 11 commits into
apache:masterfrom
aminghadersohi:mcp-native-list-compaction

Conversation

@aminghadersohi

@aminghadersohi aminghadersohi commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

SUMMARY

Builds on #45081 and should merge after it: this branch merges that PR's branch to reuse its native tool inventory test and native_tool_inventory.json. Once #45081 lands, the diff here reduces to the changes below.

1. Opt-in compact native tools/list (default off). New MCP_NATIVE_TOOL_LIST_CONFIG = {"compact": False, "max_description_length": 300}. It only applies when tool search is disabled (native mode). With compact=True, a FastMCP transform bounds each listed description using the shared _truncate_description helper: whole paragraphs first, then whole sentences. Once a whole paragraph fits, a following heading, IMPORTANT block or list is not partly advertised; an overlong first paragraph can be cut at a sentence boundary even if structured. Native mode budgets prose alone, without deducting request-parameter instructions already served untruncated in the unchanged input schema. Compatibility-mode search also budgets prose alone; request instructions remain intact in inputSchema or parameters_hint. Only the listing changes. Names, input/output schemas (nullable unions, constraints, $defs) and annotations are served unchanged. get_tool/tools/call resolve the registered tool, so Pydantic validation and execution do not depend on the setting. FastMCP's $ref inlining is kept, because keeping $refs would be larger. No default changes.

2. Editorial trims of redundant guidance, applied in both modes:

  • generate_chart: the per-chart-type required-field list, the table/pie/SQL-metric examples and the validation-pipeline notes are removed. get_chart_type_schema already serves each type's fields, required fields and examples, and the description now points to it first. The discriminator rules, the natural-language → chart_type lookup and one XY example stay.
  • update_chart: duplicate examples and the "Use when" list are folded; it points to get_chart_type_schema for config.
  • list_charts / list_dashboards / list_datasets: filter-model docstrings that restated the col/opr/value field descriptions are removed, the shared filters description is shortened, deployment-internal wording is dropped from deleted_state, and a developer note moves from the ListDatasetsRequest docstring into a code comment.
  • manage_dashboard_markdown / manage_native_filters: placement and scope text that the tool docstring already covers is shortened on the spec models.

No execution behaviour changes. The description-constraint audits (test_tool_description_constraints.py) pass with independent prose and instruction budgets, including a regression for the list_datasets semantic-view steer in both search modes.

Sizes (regenerated on this branch with locked fastmcp==3.4.7 / mcp==1.29.1; compact UTF-8 JSON of the tools/list entries, 79 tools; chart_type enum excluded as in the inventory; tokens ≈ bytes / 4.6):

Listing text-only structured output
Before (#45081, historical pre-merge baseline) 218,156 B (~47.4k tok) 524,788 B
Default, after trims and master merge 211,521 B (~46.0k tok) 518,851 B
Compact (compact=True, 300) 160,120 B (~34.8k tok) 467,450 B

Historical pre-merge text-only per-tool reductions in the default listing: generate_chart −3,731 B, update_chart −824, manage_dashboard_markdown −777, list_datasets −507, list_charts/list_dashboards −433 each, manage_native_filters −224, and the other list tools −71 each. The inventory now records compact_description_chars per tool and compact_catalog_bytes totals, and enforces both with the existing growth budget.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A

TESTING INSTRUCTIONS

pytest tests/unit_tests/mcp_service/test_native_tool_surface.py \
  tests/unit_tests/mcp_service/test_mcp_server.py \
  tests/unit_tests/mcp_service/test_tool_description_constraints.py
# regenerate the inventory after an intentional change
SUPERSET_MCP_UPDATE_TOOL_INVENTORY=1 pytest tests/unit_tests/mcp_service/test_native_tool_surface.py

New tests in test_native_tool_surface.py:

  • test_compact_listing_is_off_by_default: the default config, an empty config, and a zero budget all leave the listing alone.
  • test_compact_listing_only_bounds_descriptions (both output modes at 150, 250, and 300 characters): every wire field except description is identical to the default listing. Each description is non-empty, within the prose budget, equals _truncate_description(...) of the full text, and is a prefix of it. The semantic-view discovery/query steer survives at every tested budget.
  • test_compact_schemas_keep_nullability_constraints_and_definitions: input and output schemas are identical, and the counts of nullable anyOf, enum, minimum/maximum, maxLength, $defs/$ref, etc. match.
  • test_compact_listing_keeps_full_definitions_for_calls: get_tool returns the full description and parameters for every tool.
  • test_compact_listing_validates_calls_identically: constraint violations, wrong types and missing required fields give identical validation errors, and nullable fields accept null.
  • test_compact_listing_keeps_chart_guidance_reachable: the compact chart tools reference get_chart_type_schema, which returns required fields and examples for every core chart type, explicitly including the box-plot metrics/distribute_across requirements and sample-axis examples.

In test_mcp_server.py, run_server passes MCP_NATIVE_TOOL_LIST_CONFIG through only in native mode.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

…ibility mode

Add tests that build the native and compatibility (search_tools/call_tool)
surfaces from the registered tools and the default middleware stack, and
check that both expose the same definitions, results, validation errors, and
authorization decisions: RBAC, token scopes, restricted-principal policy, and
permission revocation. Record a measured native catalog inventory (per-tool
annotations, description and schema sizes, list entry sizes with structured
output disabled and enabled, catalog totals) and fail on unreviewed growth.

Fix LoggingMiddleware dropping isError when it rewraps a returned error
result. Calls forwarded by the call_tool proxy return their converted error
as a result, so permission and validation errors reached clients as
successful results and were logged as successes.

Document the native and compatibility discovery modes, migration, and
mixed-version rollout.
Add MCP_NATIVE_TOOL_LIST_CONFIG (compact=False by default). When tool
search is disabled and compact is enabled, tools/list bounds each
description with the same rule tool search applies to its results,
shared through a single _bounded_description helper. Schemas,
annotations and server-side validation are unchanged; tools/call
resolves the registered tool.

Trim redundant guidance from the largest tool definitions:
generate_chart and update_chart defer per-type fields and examples to
get_chart_type_schema, the list tools drop filter docstrings that repeat
field descriptions, and dashboard markdown/native filter specs shorten
placement and scope text. Regenerate the native tool inventory with
compact catalog totals.
@github-actions github-actions Bot added the doc Namespace | Anything related to documentation label Oct 8, 2026
@netlify

netlify Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit cf056c0
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6ac87b99b548aa0008da1d9d
😎 Deploy Preview https://deploy-preview-45084--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread superset/mcp_service/server.py
@codeant-ai-for-open-source

codeant-ai-for-open-source Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

CodeAnt PR Risk: Low Risk

  • The PR appears safe to merge; it documents MCP discovery modes and adds opt-in compaction for native tool listings.
  • Tests cover both startup paths, listing schemas, tool calls, and authorization; the factory configuration issue is fixed in the current code.

Assessed commit: a82b4e6854c2

@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.74%. Comparing base (4ca736d) to head (a82b4e6).
⚠️ Report is 16 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #45084      +/-   ##
==========================================
- Coverage   82.79%   82.74%   -0.06%     
==========================================
  Files        3016     3012       -4     
  Lines      192466   191771     -695     
  Branches    44835    44406     -429     
==========================================
- Hits       159354   158674     -680     
+ Misses      30079    30067      -12     
+ Partials     3033     3030       -3     
Flag Coverage Δ
hive 35.34% <5.00%> (-0.05%) ⬇️
mysql 53.79% <5.00%> (+<0.01%) ⬆️
postgres 53.80% <5.00%> (+<0.01%) ⬆️
presto 37.18% <5.00%> (+0.01%) ⬆️
python 86.48% <100.00%> (+0.02%) ⬆️
sqlite 53.54% <5.00%> (+<0.01%) ⬆️
unit 80.12% <100.00%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

AI review at head b2558a6

Verdict: CLEAN

Reviewed the full 14-file diff against master, including the merged prerequisite changes, for correctness, regressions, security against SECURITY.md, and test coverage. No actionable findings identified.

Checked that compaction changes only listed descriptions, leaves schemas/annotations and call validation intact, and preserves registered definitions for calls. Reviewed both startup paths, shared search serialization, returned-error logging, editorial trims, documentation, and inventory/authorization contract tests.

Validation with FastMCP 3.4.7 and MCP 1.29.1:

  • test_native_tool_surface.py, test_mcp_server.py, test_middleware_logging.py, and test_tool_description_constraints.py: 266 passed.
  • test_tool_search_transform.py: 104 passed.
  • Full-diff git diff --check: passed.

The local HTTP health endpoint was unavailable; these unit tests ran independently. No fixes, commits, or pushes were needed. Live head was reverified before posting.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Acceptance test at head b2558a6

Setup: fresh Python 3.11 venv from requirements/development.txt + editable install of this head, a throwaway SQLite metadata DB (superset db upgrade, fab create-admin, superset init), and real superset mcp run servers over streamable HTTP, queried with a fastmcp.Client (tools/list, tools/call). "Before" is the same setup at the base commit 8609bf7 (the native-named-tools branch). Servers run as the dev admin, which sees 77 tools in this config. Servers, worktree and DB are torn down.

Server Config Port
before tool search off, base commit 5104
default tool search off, MCP_NATIVE_TOOL_LIST_CONFIG unset 5101
compact tool search off, {"compact": True, "max_description_length": 300} 5102
search on tool search on, compact config also set 5103
default / compact + structured as above with MCP_STRUCTURED_OUTPUT_ENABLED = True 5105 / 5106

Unit tests: test_native_tool_surface.py, test_mcp_server.py, test_tool_description_constraints.py, test_middleware_logging.py: 266 passed.

Acceptance criteria this PR claims

1. Each tool is listed under its real name with a bounded, useful description, and its input schema, output schema and annotations stay accurate. PASS

  • Live tools/list: before, default and compact all list the same 77 names. The compact server logs Compact native tool list enabled (max_description_length=300).
  • Compact: the longest description is 300 chars (none over). Every compact description is a prefix of the full one after docstring dedent. Before vs compact: generate_chart 6,688 → 33 chars, update_chart 2,652 → 45.
  • For every tool, every wire field except description is byte-identical between default and compact: inputSchema, annotations (77/77 present), and outputSchema (77/77 present in structured mode).
  • Note, not a failure: generate_chart's compact description is only "Preview a chart; optionally save.", because its request-parameter instructions use most of the budget. The pointer to get_chart_type_schema is still listed, in the config field description of the input schema, and get_chart_type_schema(chart_type="xy") returns fields and examples.

2. One canonical definition and execution path, with both structured-output settings tested. PASS

  • A single _bounded_description helper serves both the tool-search serializers and the compact transform. The transform only overrides list_tools, so get_tool/tools/call resolve the registered tool.
  • Live tools/call, default vs compact, 9 cases: health_check, get_chart_type_schema, list_datasets (valid and page_size over the max), list_charts page=0, list_dashboards with a bad filter column, generate_chart with no config and with an xy config, update_chart with a non-object config. isError and the text are identical in every case, apart from timestamps, uptime and query duration. Example: Validation error in list_datasets: request.page_size: Must be at most the allowed maximum.
  • Structured mode: structuredContent is present on both servers and identical apart from timestamp. Validation errors are identical.

3. Schemas stay valid and keep nullability, server validation is unaffected, and catalog size is measured. PASS

  • Every compact inputSchema passes Draft202012Validator.check_schema. There are no unresolved $refs (refs are inlined, 0 remaining) and 227 nullable anyOf branches, identical to default.
  • Live listing size (compact JSON, chart_type enum included):
Listing text-only structured
before 215,049 B —
default (trims only) 207,339 B (−3.6%) 508,542 B
compact 155,719 B (−27.6%) 456,838 B

These relative reductions match the PR body's table. tools/list returns one page (nextCursor=None) on all servers.

4. No default changes and the opt-in is gated. PASS

  • With the config unset, the default server lists full descriptions and does not log the compact transform.
  • With tool search on (search on server), the compact config is ignored: 4 listed tools (pinned tools plus search_tools/call_tool) and no compact log line.
  • The factory startup path reads both MCP_TOOL_SEARCH_CONFIG and MCP_NATIVE_TOOL_LIST_CONFIG from the Flask config, and test_mcp_server.py covers this.
  • docs/admin_docs/configuration/mcp-server.mdx documents the option and its default.

Out of scope for this PR, so not tested: RBAC/permission parity between native and compatibility paths, mixed-version behaviour, and target-client size qualification.

Verdict: PASS. No fixes needed, nothing pushed.

@gabotorresruiz gabotorresruiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @aminghadersohi, thanks for splitting this out from #45081, and the size table made the diff easy to follow. I checked out the head, ran tests/unit_tests/mcp_service in full plus the three suites in your testing instructions, and queried three real superset mcp run servers (default, compact=True at 300, and at 250) over HTTP. The opt-in is inert by default, and with compact=True every wire field except description is byte identical for all 79 tools in both output modes, with tools/call results and validation errors identical too. Two things I would like to settle before this merges, both on the lines below: the per tool budget at and below 300, and the recorded inventory. Happy to re-review quickly once those are sorted. CI is green at b2558a6.

Comment thread superset/mcp_service/server.py Outdated
Comment thread tests/unit_tests/mcp_service/native_tool_inventory.json Outdated
Comment thread tests/unit_tests/mcp_service/test_native_tool_surface.py
Comment thread docs/admin_docs/configuration/mcp-server.mdx Outdated
@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Addressed the review’s budget and inventory points in ab667c9. Native descriptions use the full prose budget, with non-empty descriptions and unchanged schemas at 150/250/300 in both output modes; compatibility search is unchanged. Regenerated the inventory and updated the body table, pinned the box-plot required fields/examples, and corrected the structured-paragraph wording. All four inline threads have replies naming the commit and are resolved. Validation: tests/unit_tests/mcp_service — 7,494 passed, 4 skipped; focused native-surface/search-transform run — 133 passed; staged and branch-changed pre-commit checks, including mypy, passed.

@gabotorresruiz gabotorresruiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @aminghadersohi, thanks for the quick turnaround. Three of the four are closed on this head: nothing in the native listing comes out empty at 150, 250 or 300 in either output mode, list_datasets keeps its list_metrics/get_table steer at every budget, the structured paragraph wording reads right, and the new box_plot assertion does fail when get_chart_type_schema stops requiring distribute_across. The inventory is the one still open, on its thread below, and it is a single regeneration away. I ran tests/unit_tests/mcp_service in full and queried real superset mcp run servers at 300, 250, 150, 100 and 60. CI is green at 325c6ca. Happy to approve as soon as that file is squared.

Comment thread superset/mcp_service/server.py Outdated

@gabotorresruiz gabotorresruiz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aminghadersohi Both of my open items check out on this head, so this is an approve from me. The inventory regenerates to a zero diff against the committed file under the locked fastmcp==3.4.7 / mcp==1.29.1, and I measured the search budget fix live at 150, 250 and 300 in both include_schemas modes. Full tests/unit_tests/mcp_service is green apart from three dateutil transition-offset tests that fail identically on master. CI is green on cf056c0f. Details are on the three threads; one wording nit inline.

Comment thread superset/mcp_service/server.py Outdated
@aminghadersohi
aminghadersohi merged commit e538ebf into apache:master Oct 10, 2026
71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Namespace | Anything related to documentation size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants