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:
parent
e6fffa2b30
commit
12252466e6
4 changed files with 103 additions and 4 deletions
44
examples/clients/typescript/auth-test-no-application-type.ts
Normal file
44
examples/clients/typescript/auth-test-no-application-type.ts
Normal 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>'
|
||||
);
|
||||
|
|
@ -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'
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue