feat: implement auth/pre-registration conformance scenario (#1545)

This commit is contained in:
Felix Weinberger 2026-02-16 19:27:46 +00:00 • committed by GitHub
commit f2d21458cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 265 additions and 27 deletions

View file

@ -1,14 +1,8 @@
# Known conformance test failures for v1.x
# These are tracked and should be removed as they're fixed.
#
# tools_call: conformance runner's test server reuses a single Server
# instance across requests, triggering v1.26.0's "Already connected"
# guard (GHSA-345p-7cg4-v4c7). Fixed in conformance repo (PR #141),
# remove this entry once a new conformance release is published.
#
# auth/pre-registration: scenario added in conformance 0.1.11 that
# requires a dedicated client handler for pre-registered credentials.
# Needs to be implemented in both v1.x and main.
# auth/cross-app-access-complete-flow: SEP-990 Enterprise Managed OAuth
# scenario added in conformance 0.1.14. Requires implementing token
# exchange (RFC 8693) and JWT bearer grant (RFC 7523) in the client.
client:
- tools_call
- auth/pre-registration
- auth/cross-app-access-complete-flow

View file

@ -16,6 +16,7 @@ import { ElicitRequestSchema } from '../../../src/types.js';
import { z } from 'zod';
import { logger } from './helpers/logger.js';
import { ConformanceOAuthProvider } from './helpers/conformanceOAuthProvider.js';
import { handle401, withOAuthRetry } from './helpers/withOAuthRetry.js';
/**
@ -37,6 +38,11 @@ const ClientConformanceContextSchema = z.discriminatedUnion('name', [
name: z.literal('auth/client-credentials-basic'),
client_id: z.string(),
client_secret: z.string()
}),
z.object({
name: z.literal('auth/pre-registration'),
client_id: z.string(),
client_secret: z.string()
})
]);
@ -228,6 +234,43 @@ async function runClientCredentialsBasic(serverUrl: string): Promise<void> {
registerScenario('auth/client-credentials-basic', runClientCredentialsBasic);
// ============================================================================
// Pre-registration scenario (no dynamic client registration)
// ============================================================================
async function runPreRegistrationClient(serverUrl: string): Promise<void> {
const ctx = parseContext();
if (ctx.name !== 'auth/pre-registration') {
throw new Error(`Expected pre-registration context, got ${ctx.name}`);
}
// Create a provider pre-populated with registered credentials,
// so the SDK skips dynamic client registration.
const provider = new ConformanceOAuthProvider('http://localhost:3000/callback', {
client_name: 'conformance-pre-registration',
redirect_uris: ['http://localhost:3000/callback']
});
provider.saveClientInformation({
client_id: ctx.client_id,
client_secret: ctx.client_secret,
redirect_uris: ['http://localhost:3000/callback']
});
const oauthFetch = withOAuthRetry('conformance-pre-registration', new URL(serverUrl), handle401, undefined, provider)(fetch);
const client = new Client({ name: 'conformance-pre-registration', version: '1.0.0' }, { capabilities: {} });
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
fetch: oauthFetch
});
await client.connect(transport);
await client.listTools();
await client.callTool({ name: 'test-tool', arguments: {} });
await transport.close();
}
registerScenario('auth/pre-registration', runPreRegistrationClient);
// ============================================================================
// Elicitation defaults scenario
// ============================================================================

View file

@ -38,16 +38,19 @@ export const withOAuthRetry = (
clientName: string,
baseUrl?: string | URL,
handle401Fn: typeof handle401 = handle401,
clientMetadataUrl?: string
clientMetadataUrl?: string,
existingProvider?: ConformanceOAuthProvider
): Middleware => {
const provider = new ConformanceOAuthProvider(
'http://localhost:3000/callback',
{
client_name: clientName,
redirect_uris: ['http://localhost:3000/callback']
},
clientMetadataUrl
);
const provider =
existingProvider ??
new ConformanceOAuthProvider(
'http://localhost:3000/callback',
{
client_name: clientName,
redirect_uris: ['http://localhost:3000/callback']
},
clientMetadataUrl
);
return (next: FetchLike) => {
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
const makeRequest = async (): Promise<Response> => {