fix(mcp): satisfy type discipline gate and the merged input-schema key

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-30 02:10:08 +00:00
parent c2f85ca2e7
commit 072898ab19
5 changed files with 18 additions and 18 deletions

View file

@ -136,7 +136,7 @@ def caller_sign_in_for(server: MCPServer, user_api_key_auth: UserAPIKeyAuth | No
"""The merged sign-in requirement for ``server``: the OBO server's own issuer/scopes plus every
registered provider's contribution. ``None`` when nothing requires sign-in, which is also the gate the
connect-time challenge branches on."""
contributions: Final = [
contributions: Final = tuple(
contribution
for contribution in (
*(
@ -147,7 +147,7 @@ def caller_sign_in_for(server: MCPServer, user_api_key_auth: UserAPIKeyAuth | No
*(provider.caller_sign_in(server, user_api_key_auth) for provider in _providers()),
)
if contribution is not None
]
)
if not contributions:
return None
issuers: Final = tuple(dict.fromkeys(itertools.chain.from_iterable(c.issuers for c in contributions)))

View file

@ -2625,9 +2625,9 @@ def _caller_sign_in_protected_resource_response(
if sign_in is None or not sign_in.issuers:
return None
return {
"authorization_servers": list(sign_in.issuers),
"authorization_servers": sign_in.issuers,
"resource": resource_url,
"scopes_supported": list(sign_in.scopes),
"scopes_supported": sign_in.scopes,
}

View file

@ -1589,7 +1589,7 @@ if MCP_AVAILABLE:
) -> bool:
"""Sign-in challenges are issued only on a single-server connect the key's grant admits, so a key
without access gets the grant's 403 instead of a sign-in it could not use."""
if len(mcp_servers or []) != 1:
if len(mcp_servers or ()) != 1:
return False
allowed: Final = await operations._get_allowed_mcp_servers(
user_api_key_auth=user_api_key_auth, mcp_servers=mcp_servers, client_ip=client_ip
@ -1746,8 +1746,8 @@ if MCP_AVAILABLE:
await operations._get_allowed_mcp_servers(
user_api_key_auth=user_api_key_auth, mcp_servers=mcp_servers, client_ip=client_ip
)
if server and len(mcp_servers or []) == 1
else []
if server and len(mcp_servers or ()) == 1
else ()
)
if (
server

View file

@ -447,8 +447,10 @@ class Agent365Guardrail(CustomGuardrail):
if not (self.default_on and server.keeps_caller_authorization):
return None
if user_api_key_auth is not None:
probe: Final[dict[str, Mapping[str, object]]] = { # pyright: ignore[reportUnknownVariableType] # UserAPIKeyAuth metadata dicts are untyped
"metadata": {
probe: Final[
dict[str, Mapping[str, object]]
] = { # mutable-ok: should_run_guardrail takes a mutable data dict # pyright: ignore[reportUnknownVariableType] # UserAPIKeyAuth metadata dicts are untyped
"metadata": { # mutable-ok: should_run_guardrail takes a mutable data dict
"user_api_key_metadata": user_api_key_auth.metadata, # pyright: ignore[reportUnknownMemberType] # UserAPIKeyAuth.metadata is a raw dict
"user_api_key_team_metadata": user_api_key_auth.team_metadata, # pyright: ignore[reportUnknownMemberType] # UserAPIKeyAuth.team_metadata is a raw dict
}

View file

@ -193,9 +193,7 @@ def _make_guardrail(
)
def _default_fallback_guardrail(
handler: FakeHandler, exchanger: StubTokenExchanger | None = None
) -> Agent365Guardrail:
def _default_fallback_guardrail(handler: FakeHandler, exchanger: StubTokenExchanger | None = None) -> Agent365Guardrail:
return _make_guardrail(handler, exchanger=exchanger)
@ -416,7 +414,7 @@ class TestAllowFlow:
handler: Final = FakeHandler([_allow_response()])
guardrail: Final = _make_guardrail(handler)
schema: Final = {"type": "object", "properties": {"to": {"type": "string"}}, "required": ["to"]}
await _run(guardrail, _mcp_data(mcp_tool_description="Send an email", mcp_tool_input_schema=schema))
await _run(guardrail, _mcp_data(mcp_tool_description="Send an email", mcp_input_schema=schema))
assert handler.calls[0].json["tool"] == {
"name": "send_email",
"description": "Send an email",
@ -431,7 +429,7 @@ class TestAllowFlow:
async def test_evaluate_payload_omits_missing_or_malformed_tool_metadata(self, description, schema):
handler: Final = FakeHandler([_allow_response()])
guardrail: Final = _make_guardrail(handler)
await _run(guardrail, _mcp_data(mcp_tool_description=description, mcp_tool_input_schema=schema))
await _run(guardrail, _mcp_data(mcp_tool_description=description, mcp_input_schema=schema))
assert handler.calls[0].json["tool"] == {"name": "send_email"}
@pytest.mark.asyncio
@ -617,9 +615,7 @@ class TestFailOpenOptIn:
@pytest.mark.asyncio
@pytest.mark.parametrize(("responses", "exchange_results"), AVAILABILITY_FAILURES)
async def test_constructor_default_blocks_each_availability_failure_with_503(self, responses, exchange_results):
guardrail: Final = _default_fallback_guardrail(
FakeHandler(responses), StubTokenExchanger(exchange_results)
)
guardrail: Final = _default_fallback_guardrail(FakeHandler(responses), StubTokenExchanger(exchange_results))
assert guardrail.unreachable_fallback == "fail_closed"
with pytest.raises(HTTPException) as exc_info:
await _run(guardrail, _mcp_data())
@ -846,7 +842,9 @@ class TestUnreachableFallback:
@pytest.mark.asyncio
async def test_exchange_upstream_unavailable_follows_fail_open(self):
exchanger: Final = StubTokenExchanger([Error(CredError.of_upstream_unavailable("Entra throttled the exchange"))])
exchanger: Final = StubTokenExchanger(
[Error(CredError.of_upstream_unavailable("Entra throttled the exchange"))]
)
handler: Final = FakeHandler([])
guardrail: Final = _make_guardrail(handler, exchanger=exchanger, unreachable_fallback="fail_open")
data: Final = _mcp_data()