Skip to content

Commit cf41441

Browse files
authored
Send application_type during Dynamic Client Registration (SEP-837) (#2930)
1 parent 60f37e9 commit cf41441

6 files changed

Lines changed: 35 additions & 38 deletions

File tree

.github/actions/conformance/expected-failures.2026-07-28.yml

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,6 @@ client:
2828
# branch takes the per-request _meta path.
2929
- tools_call
3030

31-
# --- SEP-837 (application_type during DCR) ---
32-
# The sep-837-application-type-present check only fires on 2026-version
33-
# runs; the client omits application_type during Dynamic Client
34-
# Registration, so every auth scenario that reaches DCR fails it on this
35-
# leg (the same scenarios pass at their default version in the 2025 legs).
36-
- auth/metadata-default
37-
- auth/metadata-var1
38-
- auth/metadata-var2
39-
- auth/metadata-var3
40-
- auth/scope-from-www-authenticate
41-
- auth/scope-from-scopes-supported
42-
- auth/scope-omitted-when-undefined
43-
- auth/token-endpoint-auth-basic
44-
- auth/token-endpoint-auth-post
45-
- auth/token-endpoint-auth-none
46-
- auth/offline-access-not-supported
47-
# SEP-2468 (authorization response iss parameter) is implemented, but these
48-
# 2026-introduced scenarios reach DCR and so still fail the application_type
49-
# check above; they unblock with SEP-837, not SEP-2468.
50-
- auth/iss-supported
51-
- auth/iss-not-advertised
52-
- auth/iss-supported-missing
53-
- auth/iss-wrong-issuer
54-
- auth/iss-unexpected
55-
- auth/iss-normalized
56-
5731
# --- Auth scenarios cut short by the 2026 connection lifecycle ---
5832
# The auth fixture flow drives the 2025 stateful lifecycle; the 2026-mode
5933
# mock rejects the MCP POST before the scope-escalation behaviour these

.github/actions/conformance/expected-failures.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,6 @@ client:
2626
# SEP-2352 (authorization server migration): client does not re-register when
2727
# PRM authorization_servers changes.
2828
- auth/authorization-server-migration
29-
# SEP-837 (application_type during DCR): the check fires on every non-legacy
30-
# spec version (the default LATEST is 2026-07-28). The client omits
31-
# application_type during Dynamic Client Registration, so every scenario that
32-
# reaches DCR fails it. SEP-2468 iss validation is implemented, so these now
33-
# fail only on the application_type check, not on iss.
34-
- auth/offline-access-not-supported
35-
- auth/iss-supported
36-
- auth/iss-not-advertised
37-
- auth/iss-supported-missing
38-
- auth/iss-wrong-issuer
39-
- auth/iss-unexpected
40-
- auth/iss-normalized
4129

4230
# --- Pre-existing scenarios that fail on checks added after conformance 0.1.15 ---
4331
# SEP-990 (enterprise-managed authorization extension): no fixture handler /

docs/migration.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,21 @@ If you relied on extra fields round-tripping through MCP types, move that data i
13031303

13041304
## New Features
13051305

1306+
### OAuth Dynamic Client Registration sends `application_type` (SEP-837)
1307+
1308+
`OAuthClientMetadata` now carries an `application_type` field that is sent during Dynamic Client Registration. It defaults to `"native"`, which suits MCP clients that use loopback redirect URIs (CLI and desktop apps); browser-based clients served from a non-local host should set it to `"web"`:
1309+
1310+
```python
1311+
from mcp.shared.auth import OAuthClientMetadata
1312+
1313+
client_metadata = OAuthClientMetadata(
1314+
redirect_uris=["https://app.example.com/callback"],
1315+
application_type="web",
1316+
)
1317+
```
1318+
1319+
Under OIDC, omitting `application_type` defaults to `"web"`, which an authorization server may reject for the `localhost` redirect URIs native clients use; sending `"native"` avoids that. Non-OIDC servers ignore the parameter.
1320+
13061321
### 2025-11-25 and 2026-07-28 protocol fields modeled
13071322

13081323
`mcp.types` models the 2025-11-25 and 2026-07-28 protocol fields (e.g. `resultType`, `ttlMs`/`cacheScope` on cacheable results, `inputResponses`/`requestState` on retried requests), so inbound payloads carrying these keys parse into typed fields and round-trip. `ttlMs`/`cacheScope` default to `0`/`"private"` (immediately stale, not shared-cacheable); `resultType` defaults to `"complete"` on concrete results (`None` on `EmptyResult`); the server strips all of them from the wire at pre-2026 versions.

src/mcp/shared/auth.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class OAuthClientMetadata(BaseModel):
6767
# servers may also return additional types they support
6868
response_types: list[str] = ["code"]
6969
scope: str | None = None
70+
# SEP-837: OIDC application_type. Defaults to "native" since MCP clients typically use
71+
# loopback redirect URIs; set "web" for remote browser-based clients on a non-local host.
72+
application_type: Literal["web", "native"] = "native"
7073

7174
# these fields are currently unused, but we support & store them for potential
7275
# future use

tests/client/test_auth.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Tests for refactored OAuth client authentication implementation."""
22

33
import base64
4+
import json
45
import time
56
from unittest import mock
67
from urllib.parse import parse_qs, quote, unquote, urlparse
@@ -1049,6 +1050,21 @@ def test_falls_back_when_metadata_has_no_registration_endpoint(self):
10491050
assert request.method == "POST"
10501051

10511052

1053+
def test_registration_request_sends_application_type():
1054+
"""SEP-837: the DCR body carries application_type, defaulting to native and overridable."""
1055+
redirect_uris: list[AnyUrl] = [AnyUrl("http://localhost:3000/callback")]
1056+
1057+
default_request = create_client_registration_request(
1058+
None, OAuthClientMetadata(redirect_uris=redirect_uris), "https://auth.example.com"
1059+
)
1060+
assert json.loads(default_request.content)["application_type"] == "native"
1061+
1062+
web_request = create_client_registration_request(
1063+
None, OAuthClientMetadata(redirect_uris=redirect_uris, application_type="web"), "https://auth.example.com"
1064+
)
1065+
assert json.loads(web_request.content)["application_type"] == "web"
1066+
1067+
10521068
class TestAuthFlow:
10531069
"""Test the auth flow in httpx."""
10541070

tests/interaction/auth/test_flow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ async def test_the_dcr_request_carries_the_client_metadata() -> None:
203203
"grant_types": ["authorization_code", "refresh_token"],
204204
"response_types": ["code"],
205205
"scope": "mcp",
206+
"application_type": "native",
206207
"client_name": "interaction-suite",
207208
"software_id": "interaction-test-suite",
208209
}

0 commit comments

Comments
 (0)