Skip to content

feat(ci): add MCP protocol conformance tests#81

Merged
google-oss-prow[bot] merged 4 commits into
kubeflow:mainfrom
Kartikeya-trivedi:feat/mcp-conformance-ci
Jul 19, 2026
Merged

feat(ci): add MCP protocol conformance tests#81
google-oss-prow[bot] merged 4 commits into
kubeflow:mainfrom
Kartikeya-trivedi:feat/mcp-conformance-ci

Conversation

@Kartikeya-trivedi

Copy link
Copy Markdown
Contributor

What this PR does

Adds MCP protocol conformance tests to CI using the official MCP conformance framework, validating that the server correctly implements the protocol (initialize, tools/list, tools/call, etc.) on every PR — with no Kubernetes cluster and no LLM.

Running the suite immediately caught two real bugs, both fixed here:

  1. main is currently broken over HTTP: every tools/list, tools/call, resources/*, and prompts/* request fails with AuditIdentityMiddleware() takes no argumentscreate_server() registers the middleware class instead of an instance, so FastMCP's middleware(context, call_next) call tries to instantiate it with two arguments (regression from feat: add OpenTelemetry tracing for tool calls #21).
  2. DNS rebinding exposure: FastMCP's streamable-HTTP transport accepts requests with an arbitrary Host/Origin header by default, leaving a locally-bound, unauthenticated server reachable from a malicious web page via DNS rebinding.

Fixes #62

Changes

feat(ci): add MCP protocol conformance tests

  • .github/workflows/conformance.yaml — starts kubeflow-mcp serve --transport http with no kubeconfig, waits for it to answer initialize, then runs modelcontextprotocol/conformance@v0.1.16 (active suite) against http://localhost:8000/mcp. Server logs are dumped on failure.
  • tests/conformance/expected-failures.yaml — baseline seeded from real runner output. Every entry is a legitimate gap for this server (scenarios assuming the reference server's fixtures, plus unadvertised capabilities), each with a one-line reason. CI fails on regressions and on baselined scenarios that unexpectedly start passing.
  • make conformance — run the same suite locally (Node.js 20+).
  • CONTRIBUTING.md — documents how to run the suite and maintain the baseline (additive section under Testing).

fix(security): enforce Host/Origin validation on HTTP transport

  • kubeflow_mcp/core/transport_security.py — pure-ASGI middleware (no response buffering, SSE-safe) that validates Host/Origin before requests reach the MCP handler, delegating header checks to the MCP SDK's reference TransportSecurityMiddleware: 421 invalid Host, 403 invalid Origin, 400 non-JSON POST.
  • Wired into the http/sse transports only; stdio untouched. On by default with loopback allowlists (correct for the default 127.0.0.1 bind); configurable via KUBEFLOW_MCP_ALLOWED_HOSTS / KUBEFLOW_MCP_ALLOWED_ORIGINS (comma-separated, :* port wildcard) and KUBEFLOW_MCP_DNS_REBINDING_PROTECTION.
  • dns-rebinding-protection is intentionally not baselined — it is a required pass, so CI guards against regression.
  • 7 unit tests covering allow/reject/disable/custom-allowlist paths.

fix(core): register AuditIdentityMiddleware instance instead of class

  • One-line fix in create_server() (mcp.add_middleware(AuditIdentityMiddleware())) plus the docstring example. Without it the conformance job can never pass on main — reproducible today with a plain curl tools/list against kubeflow-mcp serve --transport http.

Verification

Tests written in this PR, run locally against the exact committed code (rebased on current main):

Unit teststests/unit/core/test_transport_security.py:

test_defaults_allow_loopback_host            PASSED
test_rejects_unknown_host_with_421           PASSED
test_rejects_unknown_origin_with_403         PASSED
test_rejects_non_json_post_with_400          PASSED
test_disabled_protection_allows_any_host     PASSED
test_custom_allowlist_is_honored             PASSED
test_get_stream_without_origin_allowed       PASSED

Full suite: 186 passed. make verify clean (uv lock, ruff check, ruff format).

Conformance suite (active, v0.1.16) — exit 0, Baseline check passed: all failures are expected. Passing scenarios:

✓ server-initialize            ✓ tools-call-simple-text
✓ logging-set-level            ✓ tools-call-error
✓ ping                         ✓ server-sse-multiple-streams (2/2)
✓ tools-list                   ✓ resources-list
✓ prompts-list                 ✓ dns-rebinding-protection (2/2)

All remaining failures are baselined with reasons (reference-server fixtures / unadvertised capabilities).

Manual probes against the live server:

POST /mcp tools/list (before middleware fix)  → {"error":{"message":"AuditIdentityMiddleware() takes no arguments"}}
POST /mcp tools/list (after fix)              → full tool list
POST /mcp (legit)                             → 200
POST /mcp Host: attacker.example.com          → 421
POST /mcp Origin: http://evil.example.com     → 403

References

Validate MCP protocol behavior (initialize, tools/list, tools/call, etc.)
against the running server over streamable HTTP on every PR, with no
Kubernetes cluster and no LLM.

- Add .github/workflows/conformance.yaml: starts kubeflow-mcp over HTTP
  with no kubeconfig, waits for initialize, and runs
  modelcontextprotocol/conformance@v0.1.16 (active suite, Node 22).
- Add tests/conformance/expected-failures.yaml baseline seeded from real
  runner output: fixture-driven scenarios, unadvertised capabilities, and
  the FastMCP DNS-rebinding hardening gap. CI fails on regressions and on
  baselined scenarios that unexpectedly start passing.
- Add `make conformance` to run the suite locally.
- Document conformance testing in CONTRIBUTING.md.

Signed-off-by: Kartikeya Trivedi <kartikeyatrivedi4oct2004@gmail.com>
Copilot AI review requested due to automatic review settings July 14, 2026 10:36
@github-actions

Copy link
Copy Markdown

🎉 Welcome to the Kubeflow MCP Server! 🎉

Thanks for opening your first PR! We're happy to have you as part of our community 🚀

Here's what happens next:

  • If you haven't already, please check out our Contributing Guide for repo-specific guidelines and the Kubeflow Contributor Guide for general community standards
  • Our team will review your PR soon! cc @kubeflow/kubeflow-sdk-team

Join the community:

Feel free to ask questions in the comments if you need any help or clarification!
Thanks again for contributing to Kubeflow! 🙏

Copilot AI 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.

Pull request overview

Adds MCP protocol conformance testing while fixing HTTP middleware registration and DNS rebinding protection.

Changes:

  • Adds CI and local conformance test execution with expected-failure baselines.
  • Adds configurable Host/Origin validation for HTTP and SSE.
  • Corrects AuditIdentityMiddleware registration.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
.github/workflows/conformance.yaml Runs conformance tests in CI.
tests/conformance/expected-failures.yaml Defines known conformance gaps.
Makefile Adds local conformance target.
CONTRIBUTING.md Documents conformance and security configuration.
kubeflow_mcp/core/transport_security.py Implements transport security middleware.
kubeflow_mcp/core/config.py Adds transport security configuration.
kubeflow_mcp/cli.py Wires security into HTTP/SSE transports.
kubeflow_mcp/core/server.py Registers audit middleware correctly.
kubeflow_mcp/core/middleware.py Corrects middleware usage example.
tests/unit/core/test_transport_security.py Tests transport security behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread kubeflow_mcp/core/transport_security.py Outdated
FastMCP's streamable-HTTP transport ships DNS rebinding protection off by
default, so a locally-bound, unauthenticated server accepted requests with an
arbitrary Host/Origin (HTTP 200) and was reachable from a malicious web page via
DNS rebinding. The MCP conformance `dns-rebinding-protection` scenario failed
and had been baselined as a known gap.

Add a pure-ASGI middleware that validates Host/Origin before requests reach the
MCP handler, delegating the header checks to the MCP SDK's reference
TransportSecurityMiddleware so behaviour matches the spec (421 for a bad Host,
403 for a bad Origin, 400 for a non-JSON POST). It is wired into the http/sse
transports only; stdio is unaffected. Protection is on by default and allows
loopback hosts; the allowlists and the on/off switch are configurable via
KUBEFLOW_MCP_ALLOWED_HOSTS / KUBEFLOW_MCP_ALLOWED_ORIGINS /
KUBEFLOW_MCP_DNS_REBINDING_PROTECTION.

Verified end-to-end: the live server now returns 421/403 for bad Host/Origin and
the conformance scenario passes (2/2), so it is removed from the baseline and is
now a required pass that CI will guard against regression.

Signed-off-by: Kartikeya Trivedi <kartikeyatrivedi4oct2004@gmail.com>
create_server() passed the AuditIdentityMiddleware class to
mcp.add_middleware() rather than an instance. FastMCP invokes middleware as
middleware(context, call_next), so every tools/list, tools/call, resources/*,
and prompts/* request over HTTP attempted to instantiate the class with two
arguments and failed with a JSON-RPC error:

    {"error": {"code": 0, "message": "AuditIdentityMiddleware() takes no arguments"}}

This was caught by the MCP conformance suite added in this PR: on current
main, every scenario beyond initialize/ping fails. With this one-line fix
(plus the docstring example), the server answers protocol requests again and
the conformance suite returns to its expected baseline.

Signed-off-by: Kartikeya Trivedi <kartikeyatrivedi4oct2004@gmail.com>
@Kartikeya-trivedi
Kartikeya-trivedi force-pushed the feat/mcp-conformance-ci branch from c57d4eb to 0b64e30 Compare July 14, 2026 10:58
@google-oss-prow google-oss-prow Bot added size/XL and removed size/L labels Jul 14, 2026
@abhijeet-dhumal

Copy link
Copy Markdown
Member

/ok-to-test
Waiting for test scaffold to merge first :

@google-oss-prow google-oss-prow Bot added the ok-to-test Approve CI for external contributors label Jul 18, 2026
Comment thread CONTRIBUTING.md
Comment thread kubeflow_mcp/core/transport_security.py
…ogger

Review follow-ups from kubeflow#81:

- Document KUBEFLOW_MCP_ALLOWED_HOSTS / KUBEFLOW_MCP_ALLOWED_ORIGINS /
  KUBEFLOW_MCP_DNS_REBINDING_PROTECTION in the README environment-variable
  table, and add an in-cluster note: the defaults are loopback-only, so a
  Service/Ingress Host must be allowlisted or requests are rejected with 421.
- Remove the unused module logger from transport_security.py.
- Add load_config() tests covering the new security env vars: CSV parsing
  (with whitespace and trailing commas), the [::1] bracketed origin form,
  and the enable/disable boolean spellings.

Signed-off-by: Kartikeya Trivedi <kartikeyatrivedi4oct2004@gmail.com>
@Kartikeya-trivedi

Copy link
Copy Markdown
Contributor Author

Thanks @abhijeet-dhumal! Documented all three env vars (KUBEFLOW_MCP_ALLOWED_HOSTS / KUBEFLOW_MCP_ALLOWED_ORIGINS / KUBEFLOW_MCP_DNS_REBINDING_PROTECTION) in the README env table in 73aa14e. Also added a note under the in-cluster deployment section with an example allowlist, since the loopback-only defaults mean a Service/Ingress Host would 421 without one, agreed that's where operators would actually get bitten.

The inline nits are addressed in the same commit

@abhijeet-dhumal

Copy link
Copy Markdown
Member

Conformance suite + DNS rebinding protection are solid additions and this also fixes #79

/lgtm
/approve
Thanks @Kartikeya-trivedi for your contribution, great work 🚀

@google-oss-prow google-oss-prow Bot added the lgtm Looks good to me — approved by a reviewer label Jul 19, 2026
@google-oss-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: abhijeet-dhumal

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow google-oss-prow Bot added the approved Approved by an approver in OWNERS label Jul 19, 2026
@google-oss-prow
google-oss-prow Bot merged commit e869e3a into kubeflow:main Jul 19, 2026
12 of 13 checks passed
@google-oss-prow google-oss-prow Bot added this to the v0.1 milestone Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Approved by an approver in OWNERS lgtm Looks good to me — approved by a reviewer ok-to-test Approve CI for external contributors size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: Add MCP protocol conformance tests

3 participants