Skip to content

feat: publish tool schemas to dataplane - #6348

Merged
Lang-Akshay merged 4 commits into
mainfrom
user/luca/publish-dataplane-tool-schemas
Aug 25, 2026
Merged

feat: publish tool schemas to dataplane#6348
Lang-Akshay merged 4 commits into
mainfrom
user/luca/publish-dataplane-tool-schemas

Conversation

@lucarlig

@lucarlig lucarlig commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • publish visibility-filtered MCP tool input schemas in the existing per-user Redis snapshot
  • key schemas by virtual host, backend, and original upstream tool name
  • reject non-object schemas instead of publishing an invalid dataplane contract

This supplies the request-scoped schema source required by contextforge-org/contextforge-data-plane#109. The dataplane validates client-supplied Mcp-Param-* headers before forwarding them unchanged; it does not reconstruct headers or call tools/list on the request path. The existing periodic control-plane discovery/publisher flow remains the schema producer.

Related: #6147 and #6256.

Validation

  • publisher unit tests: 26 passed
  • Ruff and Black
  • cross-repo E2E with the official conformance fixture: matching plain/Base64 values pass; mismatched, invalid Base64, and missing headers return HTTP 400 / JSON-RPC -32020

@lucarlig

Copy link
Copy Markdown
Collaborator Author

No longer needed. The dataplane consumer now treats Mcp-Param-* as transparent gateway metadata: it forwards the client-supplied headers unchanged and leaves validation to the upstream MCP server. It no longer consumes published tool schemas or calls tools/list on the call path. contextforge-org/contextforge-data-plane#109 passes the complete composed conformance run against the stock published control-plane image.

@lucarlig lucarlig closed this Aug 21, 2026
@lucarlig lucarlig reopened this Aug 24, 2026
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
@lucarlig
lucarlig force-pushed the user/luca/publish-dataplane-tool-schemas branch from 95c6e3e to c5d88d8 Compare August 24, 2026 08:13
@lucarlig

Copy link
Copy Markdown
Collaborator Author

Reopened: the dataplane design now validates header/body consistency from control-plane-published schemas before forwarding, so this publisher contract is required. This supersedes the earlier closure rationale; the dataplane still does not call tools/list or reconstruct parameter headers.

@msureshkumar88 msureshkumar88 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this — the wiring for tenant-safe schema publishing (tool_schemas keyed by user → virtual host → gateway → tool name) looks right, and the diff stays tightly scoped to that goal. Most of the extra lines are just typing scaffolding forced by the new TypedDict fields, no scope creep. No Alembic migration needed here, correctly.

One thing I'd like addressed before merge though:

Blocking: single bad tool schema takes down dataplane publishing for every tenant, not just its owner

In _build_user_data (mcpgateway/services/dataplane_publisher.py):

for tool in visible_tools:
    if not isinstance(tool.input_schema, dict):
        raise ValueError(f"Tool {tool.id} has a non-object input schema")

This runs inside a dict comprehension in get_data_from_db that builds every active user's config in one expression. If this raises for any tool visible to any user, the whole comprehension aborts, get_data_from_db returns None, and the publish cycle is skipped entirely — leaving all tenants on a stale (eventually expired) snapshot until someone finds and fixes the one offending row.

Given input_schema is NOT NULL and both the Pydantic layer and the ORM before_insert/before_update event make it hard (not impossible — legacy rows, bulk imports, bootstrap_db.py can bypass both) to get a non-dict value in there, this is a low-probability but high-blast-radius failure mode: one tenant's bad data silently breaks routing for everyone else.

Suggest scoping the fail-closed behavior to the single tool instead — exclude it from tool_by_id and log a warning with the tool id, rather than raising out of the comprehension. Would also want test_build_user_data_rejects_non_object_tool_schema updated to assert exclusion (and ideally a test proving other users/tools in the same cycle are unaffected) rather than pytest.raises.

Everything else (no docs needed, no unrelated changes, tests passing, CI green) looks good — happy to re-review once that's addressed.

Signed-off-by: lucarlig <luca.carlig@ibm.com>
@lucarlig

Copy link
Copy Markdown
Collaborator Author

@msureshkumar88 Thanks — fixed in 6fae49d. _build_user_data now excludes only the visible tool whose input_schema is not an object, logs a warning with the tool ID, and continues building the rest of the multi-tenant snapshot. I updated the focused regression test to verify that the malformed tool is excluded while a valid tool remains, and the mocked DB-cycle test now includes a malformed private tool while still asserting snapshots for all three users. The publisher unit tests, pre-commit hooks, Ruff, Bandit, Interrogate, Pylint, and package verification all pass. Ready for re-review.

@msureshkumar88 msureshkumar88 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for tightening this up — the malformed-schema isolation fix looks solid. Verified _build_user_data now excludes only the offending tool (with a warning log) instead of failing the whole snapshot, and the new test (test_build_user_data_excludes_non_object_tool_schema) plus the extended multi-user payload test cover it well.

Scope checks out against #6147: tool_schemas is nested under subject → virtual host → gateway, and Tool has a (gateway_id, original_name) uniqueness constraint, so there's no cross-tenant schema leakage. No Alembic migration needed (input_schema already existed), backend-only change, feature stays gated behind dataplane_publisher (off by default), and the **gateway_config → explicit-key refactor is behavior-preserving. Not a breaking change.

Two non-blocking notes for a follow-up, not required here:

  • Schema duplication: a public tool's schema is now serialized once per visible user in the snapshot rather than once total (previously only short tool names were duplicated). Worth a follow-up to dedupe by (gateway_id, tool_name) at scale.
  • No size/depth bound on published input_schema — combined with the per-user duplication above, an oversized schema on a widely-visible tool amplifies snapshot size. Not exploitable today, but worth tracking as a hardening item.

Approving — nice fix.

@Lang-Akshay Lang-Akshay left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@Lang-Akshay
Lang-Akshay added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit 2bf3f70 Aug 25, 2026
36 checks passed
@Lang-Akshay
Lang-Akshay deleted the user/luca/publish-dataplane-tool-schemas branch August 25, 2026 10:38
madhu-mohan-jaishankar pushed a commit that referenced this pull request Aug 31, 2026
* feat: publish tool schemas to dataplane

Signed-off-by: lucarlig <luca.carlig@ibm.com>

* fix: tighten dataplane schema publishing

Signed-off-by: lucarlig <luca.carlig@ibm.com>

* fix: reject invalid dataplane tool schemas

Signed-off-by: lucarlig <luca.carlig@ibm.com>

* fix: isolate malformed dataplane tool schemas

Signed-off-by: lucarlig <luca.carlig@ibm.com>

---------

Signed-off-by: lucarlig <luca.carlig@ibm.com>
@prakhar-singh1928 prakhar-singh1928 mentioned this pull request Aug 31, 2026
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.

3 participants