Skip to content

test(integration): add MCP gateway coverage wave 1 with a dedicated mcp shard and proxy coverage artifact - #42711

Merged
yuneng-berri merged 11 commits into
mainfrom
litellm_mcp_integration_coverage
Sep 23, 2026
Merged

yuneng-berri merged 11 commits into
mainfrom
litellm_mcp_integration_coverage

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • MCP gateway breaks often and no integration test notices
  • The contracts.json manifest made adding integration tests a chore
  • MCP shared the extensions shard, so no MCP-only signal or coverage

How it solves it:

  • 218 new MCP integration nodes across transports, entry points, grants, auth modes
  • Peer doubles at the protocol edge: SSE, stdio, OpenAPI, OAuth 2.1 server, slow and disconnecting peers
  • Dedicated mcp CircleCI shard with xdist and a proxy coverage artifact
  • Groups live in run.py, browser list next to the browser tests, manifest gone
  • Six confirmed product bugs recorded as pytest.skip("BUG: ...") with evidence below

User Flow

Before: a maintainer merges an MCP change that silently breaks tool access and only learns about it from a customer report

  1. They open a PR that touches the MCP gateway and CircleCI runs the extensions integration shard, which holds 11 MCP tests
  2. Every check is green, the PR merges, and a release ships
  3. A customer sends POST https://litellm-domain/mcp tools/call with a key that has no grant for that server and gets "isError": false and the tool output back
  4. The customer opens https://litellm-domain/ui/?page=logs and sees the call billed at $0

After: the same PR turns the mcp shard red before merge

  1. They open the same PR and CircleCI runs a dedicated mcp integration shard with 229 tests
  2. The shard fails on test_mcp_access_matrix.py::test_subject_grant_lists_only_reachable_tools_and_denies_the_rest[...] with denied call succeeded: {...} and the peer-observed request attached
  3. They open the shard's coverage.txt artifact to see which MCP module lines the suite reached
  4. The PR does not merge until the denial works again

Relevant issues

Follow-up to #42687 (skipped nodes count as complete, shard cap dropped), which this PR builds on

Affected release

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

This PR changes no proxy behavior, so the proof is what the harness observes, measured against a real proxy pair (two workers on :4000 and :4001), real Postgres and Redis, and the peer doubles from tests/integration/_support/. No provider APIs are involved: every MCP peer, LLM upstream and OAuth server is a local double at the protocol edge, which is the point of the suite

Part A: node counts per shard, before and after the manifest removal

Collected with pytest --collect-only -q over each group's directories at the merge base (5028f9e) and at the tip

Shard Before After
management 34 34
accounting 35 35
database 3 3
providers 83 83
extensions 48 (37 + 11 mcp) 37
mcp 11 (inside extensions) 229
sdk 2 2
cost 483 483

Every non-MCP shard collects the same nodes as before. mcp grew from 11 to 229 nodes, so 218 nodes were added

Part A: MCP module coverage of the proxy, before and after

INTEGRATION_COVERAGE=1 runs the proxy under coverage run --parallel-mode scoped to the MCP modules (tests/integration/mcp_coverage.toml), sends SIGTERM at the end so the data flushes, then combines and writes coverage.txt and an HTML report under the suite results, which CircleCI stores as an artifact. The mcp shard has it on. Measured locally by running the same command with the 11 existing MCP nodes at the base and all 229 at the tip (the MCP modules are byte-identical at both commits, this PR does not touch litellm/)

Lines Branches coverage.txt total
Before (11 nodes) 6556 / 17665 (37.1%) 798 / 5864 (13.6%) 31%
After (229 nodes) 10021 / 18216 (55.0%) 2012 / 6064 (33.2%) 50%

Line totals differ because coverage only lists modules the proxy imported during the run and the new tests import more of them

Part B: local run of the mcp shard at the tip

$ INTEGRATION_WORKERS=4 INTEGRATION_COVERAGE=1 python -m integration.run mcp
============ 223 passed, 6 skipped, 4 warnings in 156.15s (0:02:36) ============

Part B: bug table, one row per BUG: skip

Each of these fails on the product, so the test skips with the symptom and the table carries the evidence. A guarded assertion sits right after every skip, so the test goes red again the moment the product starts behaving

Test node Request sent Observed Expected
test_mcp_management.py::test_duplicate_alias_is_rejected_so_tool_prefixes_cannot_collide POST /v1/mcp/server with alias equal to an existing server's alias 201 and a second server_id; both servers now expose <alias>-add 400 naming the duplicate alias
test_mcp_lifecycle.py::test_key_grant_added_by_key_update_is_visible_to_mcp_tool_listing_before_the_cache_ttl POST /key/update adding object_permission.mcp_servers: [<id>], then POST /mcp tools/list with that key tools: [] for the full 60s key cache TTL; the second worker agrees; no invalidation is published the granted server's tools appear within seconds through cache invalidation
test_mcp_oauth_flows.py::test_token_exchange_without_a_subject_token_is_rejected_before_any_upstream_request POST /mcp-rest/tools/call on an oauth2_token_exchange server with no caller Authorization 500; peer and token endpoint untouched (fail-closed) 401 telling the caller a subject token is required
test_mcp_oauth_flows.py::test_delegated_auth_forwards_the_callers_bearer_untouched[rest] POST /mcp-rest/tools/call on an oauth_delegate server with Authorization: Bearer user-... peer receives tools/call with no authorization header; /mcp, /{server}/mcp, /mcp/ and /mcp/sse forward it the caller's bearer reaches the peer on /mcp-rest too
test_mcp_accounting_guardrails.py::test_pre_mcp_call_guardrail_blocks_before_the_peer_and_still_logs_spend[rest] POST /mcp-rest/tools/call with an argument containing the content filter's blocked word guardrail blocks before the peer, but the LiteLLM_SpendLogs row has model="" and no tool name in mcp_tool_call_metadata; the JSON-RPC entry points log MCP: <alias>-add the blocked call's spend row names the server and tool like a served call does
test_mcp_llm_endpoints.py::test_auto_approved_gateway_tool_is_listed_executed_once_and_fed_back[messages_bridge] (and the other messages_bridge cases) POST /v1/messages against a non-Anthropic model with mcp_servers: [{server_url: "litellm_proxy", require_approval: "never"}] the upstream sees the second request without the tool_result block, so the proxy re-runs the tool on every loop iteration until the cap one tool call, then the final text the sum is 5

Part B: mutation proof for invariants 1, 2, 7 and 8

Each mutation was applied to litellm/ in a scratch worktree only, the matching tests were run against that proxy, then the worktree was restored with git checkout litellm/ and the same tests passed again. Nothing under litellm/ is in this PR's diff

Invariant Mutation (scratch worktree) Matching tests Result
1: a denied call never reaches the peer operations.py: disable the server-level authorization check test_mcp_access_matrix.py 48 failed, 42 passed; representative failure denied call succeeded: {"jsonrpc":"2.0","id":1,"result":{"content":[{"text":"3",...}],"isError":false}}
2: tool list equals callable set operations.py: resolve the caller's allowed servers to the whole registry test_mcp_access_matrix.py 74 failed, 19 passed; listings show ungranted tools that then fail or succeed unexpectedly on call
7: credentials never stored plaintext encrypt_value_helper: return the string unchanged test_mcp_credentials.py -k encrypted_at_rest 4 failed; all four auth modes (api_key, bearer_token, basic, authorization) found the secret in LiteLLM_MCPServerTable
8: one spend row per call with configured cost cost_calculator.py: calculate_mcp_tool_call_cost returns 0.0 when a logging object exists test_mcp_accounting_guardrails.py -k configured_cost Obtained: 0.0 / Expected: 0.5 ± 5.0e-07 on the add row, same for the default cost row

After restoring the worktree: test_mcp_access_matrix.py 138 passed, test_mcp_credentials.py and the accounting file all green in the full run above

Fix-history mapping: 368 integration-testable rows

Each row of mcp-fix-history.md maps to the test node that would catch a regression of that fix, or to not covered: <reason>. Rows are keyed by the fix commit's short sha. 260 rows map to a node, 108 are not covered, the reasons repeat because they share a cause: JWT auth, network origin policy, proxy log text, UI-only changes, or behavior the harness cannot observe without a real third party

oauth: 117 rows, 100 mapped, 17 not covered
Test node or reason Fix commits
test_mcp_oauth_flows.py::test_per_user_authorization_code_with_pkce_binds_the_token_to_the_authorizing_user c7e4160 97211bc 93bd3d6 55ab5ee a0bb021 7c1d060 88b3cfd 229e136 7cb100a 238609e 7041f57 287a89e feedab2 447d50f a81c6ce 2f15b5f 9dbebf2 52df186 e16ad04 168bc38 fc3c21e f5b4564 ef108e7 862bad3 bd1c3ea b7813aa
test_mcp_oauth_flows.py::test_dcr_bridge_relays_client_registration_and_advertises_gateway_endpoints ee676d5 bb9b4c4 fc95d22 93e7e8d 465ebb1 cc40050 a66e091 c6a0ad5 20dd0e6 a0d5df2 2eb37d7 0d9c3cf 1f1628d 67d54fd f903825 55ff3a2 7df848a f308bd9 7e0af8f c0327cd f5ea72b 3235f4a 15ff389
test_oauth_configuration.py::test_partial_discovery_and_unrelated_edit_keep_actual_authorization_destination 362d99e 17863fa 8d5a675 f04fb74 19e6f03 7984ae4 5ddff61 b16e611 930676a 5e10507 afd7917 ad73f3a 032a2f2 b3af125 e4a6516 8650f6c 66f012a 25ada3a 446a6e8 0f56038
test_mcp_oauth_flows.py::test_client_credentials_token_is_minted_once_and_sent_as_bearer ce8d6f7 c673bcd 97b7eaa a9fac3c 7a63e51 2f349f6 2883e36 9bf3907 8300657 200a38c
test_mcp_oauth_flows.py::test_delegated_auth_forwards_the_callers_bearer_untouched 6da516e f6eaca9 58f1814 98818df edf00bb 732832d b9df7fa 466f06d
test_mcp_oauth_flows.py::test_token_exchange_swaps_the_callers_subject_token_and_never_forwards_it 291d02f 50874f9 8fcbc35 9335ede 7baf255 ec808ed 1528f43
test_mcp_credentials.py::test_static_credential_reaches_the_peer_in_its_mode_shape_and_is_encrypted_at_rest 040aa9d 48572c9
test_mcp_management.py::test_config_declared_server_behaves_like_database_server_but_is_read_only 99b85a3 74a15c2
test_mcp_access_matrix.py::test_same_tool_name_on_two_servers_routes_by_prefix b2ea36f
test_mcp_oauth_flows.py::test_authorization_request_without_pkce_is_refused_before_reaching_the_authorization_server 5aabfcc
not covered: needs JWT auth configured on the proxy; the integration proxy config has no JWT issuer 327515a 61e3b5d 5331879 88d0371 eda98f3 92e182b e16aa9f 4ba7221 d6503d1 dd38e9f
not covered: asserts on proxy log text, which the harness does not capture ef67412 cfbcef3 1b0ae3a
not covered: EMA retention gate has no observable behavior through the proxy API in this harness ddae6ea cfa074d
not covered: MCP client allowlist and session admission policy are not configured on the integration proxy 94e4dd7
not covered: network origin or IP allowlist policy; the harness runs everything on loopback f2f65a6
access_control: 58 rows, 42 mapped, 16 not covered
Test node or reason Fix commits
test_mcp_access_matrix.py::test_subject_grant_lists_only_reachable_tools_and_denies_the_rest 0c98afa 1a9c6ce d6b8cb9 0d69482 222cb29 3c2138f 659eb04 c4982ca f837578 473f43d b2d4dde b5d38b8 e25cab6 688f535 6003187 8398952 57b0d7f
test_mcp_oauth_flows.py::test_dcr_bridge_relays_client_registration_and_advertises_gateway_endpoints da036ad 6a75bbd c46863b 362c78e ea64ef7 9e6d6e5 c59f16f 50cc2c0
test_mcp_management.py::test_access_group_membership_follows_edits 5b7063d 909a5f5 ea01533 123a9ce
test_mcp_access_matrix.py::test_key_without_any_grant_sees_no_scoped_server c59fc6d 7256bd3
test_mcp_transports.py::test_server_initiated_sampling_and_elicitation_surface_as_errors_not_success e208b4e
test_mcp_lifecycle.py::test_health_intersects_route_restricted_key_grants_in_both_management_modes e21db01
test_mcp_oauth_flows.py::test_token_exchange_swaps_the_callers_subject_token_and_never_forwards_it 8cbaba8
test_mcp_transports.py::test_every_entry_point_lists_and_calls_every_peer_transport ee76c9a
test_mcp_access_matrix.py::test_missing_or_wrong_key_is_rejected_before_the_peer 7640487
test_mcp_transports.py::test_rest_and_streamable_http_agree_on_tool_list_and_result 33a92bd
test_mcp_llm_endpoints.py::test_gateway_tool_without_auto_approval_returns_the_call_to_the_caller_and_never_hits_the_peer cf23df9
test_mcp_credentials.py::test_caller_headers_for_other_servers_and_unknown_headers_never_reach_the_peer 65d0dcf
test_mcp_oauth_flows.py::test_delegated_auth_forwards_the_callers_bearer_untouched ddec3b2
test_mcp_oauth_flows.py::test_per_user_authorization_code_with_pkce_binds_the_token_to_the_authorizing_user c370503
test_mcp_management.py::test_secrets_never_appear_in_server_listing_or_detail 31d0d85
not covered: Admin UI, submissions or connector-import surface, outside the proxy API harness 14639bb 4d2b722 7ce573e f5f8ba9 b96f1aa 9196098
not covered: MCP client allowlist and session admission policy are not configured on the integration proxy c230393 5f6702e c0f335d
not covered: asserts on proxy log text, which the harness does not capture 9c41077 257d671 c3fcafb
not covered: needs JWT auth configured on the proxy; the integration proxy config has no JWT issuer 0536fb3
not covered: the gateway-owned per-server auth challenge is not exercised in wave 1 1bcb587
not covered: network origin or IP allowlist policy; the harness runs everything on loopback 1c3c1af
not covered: public-route detection needs a public MCP hub configured; not in the integration config 73869f0
credentials_byok: 38 rows, 28 mapped, 10 not covered
Test node or reason Fix commits
test_mcp_credentials.py::test_static_credential_reaches_the_peer_in_its_mode_shape_and_is_encrypted_at_rest 574ea15 70ef8b2 6c517bf fdb8e35 258176d be50693 84e1478 8a3add3 1355347 feeda1a cd681a5 35d2046 2f23cf5 050cef5
test_mcp_oauth_flows.py::test_client_credentials_token_is_minted_once_and_sent_as_bearer 44a6c65 d234229 8bfd8ba 5b64239
test_oauth_configuration.py::test_same_url_oauth_credentials_and_revocation_are_isolated_by_user_and_server aa35131 4812473 05f39bf
test_mcp_oauth_flows.py::test_delegated_auth_forwards_the_callers_bearer_untouched 364e466 97b964d
test_mcp_credentials.py::test_byok_server_uses_the_calling_users_stored_credential_and_fails_closed_without_one 731f79f 1436ee9
test_mcp_transports.py::test_rest_and_streamable_http_agree_on_tool_list_and_result 3f15dc3
test_mcp_oauth_flows.py::test_dcr_bridge_relays_client_registration_and_advertises_gateway_endpoints 0053527
test_mcp_management.py::test_config_declared_server_behaves_like_database_server_but_is_read_only 360adbe
not covered: Admin UI, submissions or connector-import surface, outside the proxy API harness 3169c80 493c98f 40f5d53 0557a95 7705f0b
not covered: master key rotation restarts the proxy with a new salt; not automated f3639a6 e0b32eb
not covered: needs an Entra/Agent 365 tenant double; not built in this wave b7f53ce
not covered: the OpenAPI peer is registered without credentials in wave 1 da7a10e
not covered: internal encoding helper refactor with no observable proxy behavior c76c300
guardrails: 29 rows, 10 mapped, 19 not covered
Test node or reason Fix commits
test_mcp_accounting_guardrails.py::test_pre_mcp_call_guardrail_blocks_before_the_peer_and_still_logs_spend 7210403 c91ca90 4a7d8bb eba05f4 df739bd 0f7105f a1e7293 9832d6e 8ee599a
test_mcp_credentials.py::test_byok_server_uses_the_calling_users_stored_credential_and_fails_closed_without_one 95b8e2a
not covered: semantic tool filter needs an embedding model; not scripted in this wave 53e5b22 5421fdf 8ca809d ae2462b 898182b 1e8c2f7 6041d37 5e73994 034f4fd
not covered: needs an Entra/Agent 365 tenant double; not built in this wave 0e27a52 5355cbe d762311 1359670 485f452 8af770c de2f6b2
not covered: request-selected guardrails are not exercised; only default_on guardrails run in wave 1 5b91195 743684b
not covered: guardrails on the OpenAPI tool path are not exercised in wave 1 5daf016
management_crud: 29 rows, 17 mapped, 12 not covered
Test node or reason Fix commits
test_mcp_access_matrix.py::test_subject_grant_lists_only_reachable_tools_and_denies_the_rest 36bd7f1 0d70359 3489e65 b7bbddb 83fcaca
test_mcp_management.py::test_config_declared_server_behaves_like_database_server_but_is_read_only 25af172 fafd294 02a9317
test_oauth_configuration.py::test_partial_discovery_and_unrelated_edit_keep_actual_authorization_destination f59cd30 40d999b
test_mcp_management.py::test_secrets_never_appear_in_server_listing_or_detail ace3c65 4e2b2d9
test_mcp_credentials.py::test_static_credential_reaches_the_peer_in_its_mode_shape_and_is_encrypted_at_rest 25c8926
test_mcp_oauth_flows.py::test_delegated_auth_forwards_the_callers_bearer_untouched 7d64f9d
test_mcp_oauth_flows.py::test_dcr_bridge_relays_client_registration_and_advertises_gateway_endpoints 5ec4c16
test_mcp_management.py::test_edit_url_moves_calls_to_the_new_peer_without_touching_grants c75184b
test_mcp_lifecycle.py::test_saved_headers_reach_real_mcp_tool_and_survive_unrelated_edit 2d4c13c
not covered: Admin UI, submissions or connector-import surface, outside the proxy API harness ca287c1 bbbd030 0b2dd9b 3523f37 2453697 318b6a4 3275459 51ba6e3 aeb55e7
not covered: /mcp-rest/test/connect is not exercised in wave 1 15392e7
not covered: asserts on proxy log text, which the harness does not capture df75d29
not covered: connector catalog entries carry no proxy behavior to assert 3334ee9
llm_endpoint_tools: 21 rows, 19 mapped, 2 not covered
Test node or reason Fix commits
test_mcp_llm_endpoints.py::test_auto_approved_gateway_tool_is_listed_executed_once_and_fed_back 35d3478 5d019fe 7b181ef 56cda9f 29c23db 454ce50
test_mcp_access_matrix.py::test_subject_grant_lists_only_reachable_tools_and_denies_the_rest 7962407 7be3ddd a78dc69
test_mcp_transports.py::test_every_entry_point_lists_and_calls_every_peer_transport 92ff54f 179ebdb
test_mcp_transports.py::test_server_initiated_sampling_and_elicitation_surface_as_errors_not_success b085a3c
test_mcp_lifecycle.py::test_health_intersects_route_restricted_key_grants_in_both_management_modes 576c1bc
test_mcp_llm_endpoints.py::test_server_scoped_gateway_url_exposes_only_that_servers_tools 2a1527c
test_mcp_transports.py::test_rest_and_streamable_http_agree_on_tool_list_and_result 6de5373
test_mcp_resilience.py::test_tool_error_surfaces_as_error_with_the_peer_message_and_never_as_success 0de60a2
test_mcp_access_matrix.py::test_same_tool_name_on_two_servers_routes_by_prefix a30e1f6
test_mcp_credentials.py::test_byok_server_uses_the_calling_users_stored_credential_and_fails_closed_without_one cd3ac05
test_mcp_lifecycle.py::test_same_url_server_grants_scope_discovery_and_direct_or_virtual_execution 02bce7b
not covered: needs JWT auth configured on the proxy; the integration proxy config has no JWT issuer 68efe69
not covered: the OpenAPI peer is registered without credentials in wave 1 9380940
discovery_listing: 18 rows, 12 mapped, 6 not covered
Test node or reason Fix commits
test_oauth_configuration.py::test_partial_discovery_and_unrelated_edit_keep_actual_authorization_destination b277be0 da61fa3 935611d
test_mcp_access_matrix.py::test_same_tool_name_on_two_servers_routes_by_prefix 4296a5d d5b8eff cf94f4d
test_mcp_resilience.py::test_unreachable_peer_errors_while_a_healthy_sibling_keeps_serving 69029c1 87de0e8
test_mcp_accounting_guardrails.py::test_pre_mcp_call_guardrail_blocks_before_the_peer_and_still_logs_spend a3e9ed3
test_mcp_credentials.py::test_byok_server_uses_the_calling_users_stored_credential_and_fails_closed_without_one 87e2e7a
test_mcp_transports.py::test_rest_and_streamable_http_agree_on_tool_list_and_result cf08c07
test_mcp_access_matrix.py::test_subject_grant_lists_only_reachable_tools_and_denies_the_rest f426912
not covered: no scripted peer paginates tools/list in wave 1 5767a2d 4a6a387
not covered: no matrix node exercises this path in wave 1 51aa021
not covered: needs JWT auth configured on the proxy; the integration proxy config has no JWT issuer 6d5c2d8
not covered: guardrails on the OpenAPI tool path are not exercised in wave 1 6c865d1
not covered: Admin UI, submissions or connector-import surface, outside the proxy API harness 963816c
protocol_transport: 17 rows, 13 mapped, 4 not covered
Test node or reason Fix commits
test_mcp_transports.py::test_every_entry_point_lists_and_calls_every_peer_transport 0d0f73c c085619 a1588c2
test_mcp_transports.py::test_official_client_session_lists_and_calls_through_gateway aea13ee 7270f72
test_mcp_resilience.py::test_slow_peer_beyond_configured_timeout_errors_and_does_not_hang_the_gateway dbf9490 a01b421
test_mcp_access_matrix.py::test_same_tool_name_on_two_servers_routes_by_prefix 3fb5056 6b3f07b
test_mcp_oauth_flows.py::test_delegated_auth_forwards_the_callers_bearer_untouched 4a25cce
test_mcp_transports.py::test_progress_notifications_do_not_break_result_and_slow_tool_completes f922be3
test_mcp_access_matrix.py::test_missing_or_wrong_key_is_rejected_before_the_peer 69b0dd2
test_mcp_resilience.py::test_tool_error_surfaces_as_error_with_the_peer_message_and_never_as_success 0a4640f
not covered: Admin UI, submissions or connector-import surface, outside the proxy API harness a835e75
not covered: debug headers on GET streams are not asserted in wave 1 c972bbe
not covered: the integration proxy runs without a root_path d193c7a
not covered: no matrix node sends a multi-byte body split across the routing peek 6bbcf1d
health_resilience: 15 rows, 3 mapped, 12 not covered
Test node or reason Fix commits
test_mcp_lifecycle.py::test_health_intersects_route_restricted_key_grants_in_both_management_modes 8c82c32 eefd5e3
test_oauth_configuration.py::test_partial_discovery_and_unrelated_edit_keep_actual_authorization_destination 84c1df9
not covered: DB outage injection is not available in the harness 61c7e70 f96899a f5f03cb fea402c
not covered: internal exception classification with no distinct observable response d0ee110 424443c
not covered: SSRF guard needs a non-loopback fetch target; the harness runs on loopback 51a3e90 1bb04cd
not covered: review-feedback refactor with no observable proxy behavior 69be5be ad31e79
not covered: outbound concurrency limit is not observable with a single scripted call 9652509
not covered: stdio command allowlist is not configured on the integration proxy 7b7f304
accounting_logging: 14 rows, 7 mapped, 7 not covered
Test node or reason Fix commits
test_mcp_credentials.py::test_static_credential_reaches_the_peer_in_its_mode_shape_and_is_encrypted_at_rest 3883a89 0ee9e1e 3fc483d
test_mcp_accounting_guardrails.py::test_pre_mcp_call_guardrail_blocks_before_the_peer_and_still_logs_spend e073cd3
test_mcp_oauth_flows.py::test_per_user_authorization_code_with_pkce_binds_the_token_to_the_authorizing_user f9f5c03
test_mcp_accounting_guardrails.py::test_each_tool_call_writes_one_spend_row_with_server_tool_and_configured_cost bfff5e8
test_mcp_accounting_guardrails.py::test_key_spend_and_key_max_budget_count_mcp_tool_calls fabe5c2
not covered: asserts on proxy log text, which the harness does not capture 5b13dfc 1425c71 902dd7b d0f1c38 7c52cde 039a2d8
not covered: no logging callback double receives MCP events in wave 1 59eeae3
multi_worker_cache: 12 rows, 9 mapped, 3 not covered
Test node or reason Fix commits
test_mcp_credentials.py::test_byok_server_uses_the_calling_users_stored_credential_and_fails_closed_without_one 3c536bf ceb1f04
test_mcp_oauth_flows.py::test_per_user_authorization_code_with_pkce_binds_the_token_to_the_authorizing_user 8841cbc 2001d91
test_mcp_access_matrix.py::test_subject_grant_lists_only_reachable_tools_and_denies_the_rest f883729 03f6818
test_mcp_management.py::test_edit_url_moves_calls_to_the_new_peer_without_touching_grants c038aaf
test_mcp_lifecycle.py::test_health_intersects_route_restricted_key_grants_in_both_management_modes ca03c88
test_oauth_configuration.py::test_same_url_oauth_credentials_and_revocation_are_isolated_by_user_and_server 1fb2b4a
not covered: credential eviction on the peer worker is not asserted in wave 1 fd834f6
not covered: the 256-identity catalog bound needs more callers than the matrix creates fdb7da7
not covered: discovery cache byte bound is not observable through the proxy API 9d31de2

Type

✅ Test
🚄 Infrastructure

Caveats (if any)

Medium

  • Six product bugs are documented, not fixed. Each carries a BUG: skip; the table above has the repro
  • The mcp shard takes about 3 minutes with 4 xdist workers, the longest of the integration shards
  • test_mcp_lifecycle.py runs a Hypothesis state machine capped at 5 examples of 6 steps to stay inside the HTTP budget

Low

  • Coverage percentages come from a local run; the CircleCI artifact from the first run-ci is the authoritative After
  • Removing contracts.json drops the covers requirement. Existing @pytest.mark.covers(...) markers stay registered and inert
  • The 108 not covered rows are honest gaps for a later wave (JWT auth mode, origin allowlists, log assertions, UI)

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/8c808db0bc054c3c8a1700ff5a843c56
Open in Devin Desktop: https://app.devin.ai/desktop/session/8c808db0bc054c3c8a1700ff5a843c56?variant=devin

@devin-ai-integration
devin-ai-integration Bot requested a review from a team September 23, 2026 08:13
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot changed the title test(integration): MCP gateway coverage wave 1 with a dedicated mcp shard and proxy coverage artifact test(integration): add MCP gateway coverage wave 1 with a dedicated mcp shard and proxy coverage artifact Sep 23, 2026
@greptile-apps

greptile-apps Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge, with the prior REST denial regression check now fully corrected.

Summary

This PR substantially expands MCP gateway integration coverage and gives it a dedicated CircleCI shard.

  • Adds local MCP, OAuth, OpenAPI, SSE, stdio, and failure-mode peers with broad transport, authorization, lifecycle, accounting, and resilience coverage.
  • Moves integration ownership from the node-level contracts manifest to directory groups defined in run.py.
  • Runs the MCP shard with four workers and produces proxy coverage text and HTML artifacts.
  • The latest revision correctly tightens REST denial validation to require a 403 access_denied response.

Reviews (3) · Last reviewed commit: "test(integration): pin the REST denied-s..."

Comment thread tests/integration/conftest.py
Comment thread tests/integration/mcp/test_mcp_access_matrix.py Outdated
@codecov

codecov Bot commented Sep 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread tests/integration/mcp/test_mcp_access_matrix.py Outdated
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

yuneng-berri and others added 11 commits September 23, 2026 14:37
…quirement

Groups live as a GROUPS literal in run.py, the browser expectations move next to the
browser tests, and the runner fails only on pytest failure, collection errors or a
selected file that collects zero tests. The covers marker stays registered for the
existing tests but is no longer checked. The mcp directory gets its own group

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… for the MCP modules

The mcp shard sets it. The proxy and its peer start under coverage run in parallel mode,
get SIGTERM after the tests so coverage flushes, and the combined text and HTML reports
land in the suite results that CircleCI already stores as artifacts

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ises SIGTERM

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…P peer doubles

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ience and lifecycle coverage

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…initialize as a leaked call and satisfy the test-tree lint

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…r empty

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…enied

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot force-pushed the litellm_mcp_integration_coverage branch from 35f6f3e to 951b9f9 Compare September 23, 2026 14:37
@yuneng-berri
yuneng-berri merged commit e26a645 into main Sep 23, 2026
92 of 93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant