SEP-837: application_type check in DCR registration (#284)

* Add SEP-837 application_type check to DCR registration

SEP-837 requires MCP clients to specify an appropriate application_type
during Dynamic Client Registration so OIDC authorization servers can
apply the correct redirect-URI constraints.

- Traceability yaml: 1 check (presence + valid value), 4 excluded
  (class-specific SHOULDs unobservable; UI/robustness)
- Check added in the shared createAuthServer DCR handler so it fires in
  every auth scenario that performs DCR (no new scenario)
- withOAuthRetry now sets application_type: native (passing example;
  the conformance example clients are CLI tools)
- New auth-test-no-application-type negative client + vitest case

* Widen ConformanceOAuthProvider metadata type for application_type

Fixes CI typecheck (TS2353 at withOAuthRetry.ts:76): the SDK's
OAuthClientMetadataSchema doesn't include application_type yet.
registerClient() spreads clientMetadata verbatim into the /register
POST body, so a local type intersection is sufficient to get the
field on the wire without an SDK release.

* Set application_type in runAuthMigrationClient

The SEP-2352 authorization-server-migration handler constructs its own
ConformanceOAuthProvider (not via withOAuthRetry), so it was missing
application_type after rebasing onto main. The scenario does DCR twice
(old AS, new AS) and the SEP-837 check fires on each.

---------

Co-authored-by: Paul Carleton <paulc@anthropic.com>
This commit is contained in:
Den Delimarsky 2026-05-19 15:28:50 -07:00 • committed by GitHub
commit 12252466e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 103 additions and 4 deletions

View file

@ -0,0 +1,44 @@
/**
* Negative client for SEP-837: registers via DCR WITHOUT application_type.
* Expected to trigger sep-837-application-type-present = FAILURE.
*/
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import {
withOAuthRetryWithProvider,
handle401
} from './helpers/withOAuthRetry';
import { ConformanceOAuthProvider } from './helpers/ConformanceOAuthProvider';
import { runAsCli } from './helpers/cliRunner';
export async function runClient(serverUrl: string): Promise<void> {
const provider = new ConformanceOAuthProvider(
'http://localhost:3000/callback',
{
client_name: 'auth-test-no-application-type',
redirect_uris: ['http://localhost:3000/callback']
// application_type intentionally omitted
}
);
const oauthFetch = withOAuthRetryWithProvider(
provider,
new URL(serverUrl),
handle401
)(fetch);
const client = new Client(
{ name: 'auth-test-no-application-type', version: '1.0.0' },
{ capabilities: {} }
);
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
fetch: oauthFetch
});
await client.connect(transport);
await client.listTools();
await transport.close();
}
runAsCli(
runClient,
import.meta.url,
'auth-test-no-application-type <server-url>'
);

View file

@ -284,7 +284,8 @@ async function runAuthMigrationClient(serverUrl: string): Promise<void> {
'http://localhost:3000/callback',
{
client_name: 'auth-migration-client',
redirect_uris: ['http://localhost:3000/callback']
redirect_uris: ['http://localhost:3000/callback'],
application_type: 'native'
}
);

View file

@ -6,6 +6,16 @@ import {
OAuthTokens
} from '@modelcontextprotocol/sdk/shared/auth.js';
/**
* SEP-837 adds `application_type` to DCR; the SDK's OAuthClientMetadataSchema
* doesn't include it yet. The SDK spreads clientMetadata verbatim into the
* /register POST body, so widening the type here is sufficient to get the
* field on the wire. Drop this once the SDK schema is updated.
*/
type ConformanceClientMetadata = OAuthClientMetadata & {
application_type?: 'native' | 'web';
};
export class ConformanceOAuthProvider implements OAuthClientProvider {
private _clientInformation?: OAuthClientInformationFull;
private _tokens?: OAuthTokens;
@ -17,7 +27,7 @@ export class ConformanceOAuthProvider implements OAuthClientProvider {
constructor(
private readonly _redirectUrl: string | URL,
private readonly _clientMetadata: OAuthClientMetadata,
private readonly _clientMetadata: ConformanceClientMetadata,
private readonly _clientMetadataUrl?: string | URL
) {}
@ -25,7 +35,7 @@ export class ConformanceOAuthProvider implements OAuthClientProvider {
return this._redirectUrl;
}
get clientMetadata(): OAuthClientMetadata {
get clientMetadata(): ConformanceClientMetadata {
return this._clientMetadata;
}

View file

@ -70,7 +70,10 @@ export const withOAuthRetry = (
'http://localhost:3000/callback',
{
client_name: clientName,
redirect_uris: ['http://localhost:3000/callback']
redirect_uris: ['http://localhost:3000/callback'],
// SEP-837: the conformance example clients are CLI tools, i.e. native
// applications under the OIDC client-type taxonomy.
application_type: 'native'
},
clientMetadataUrl
);