Skip to content

Commit f2d2145

Browse files
feat: implement auth/pre-registration conformance scenario (#1545)
1 parent 8cbc658 commit f2d2145

6 files changed

Lines changed: 265 additions & 27 deletions

File tree

.github/workflows/conformance.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- uses: actions/checkout@v4
2222
- uses: actions/setup-node@v4
2323
with:
24-
node-version: 20
24+
node-version: 24
2525
cache: npm
2626
- run: npm ci
2727
- run: npm run build
@@ -33,7 +33,7 @@ jobs:
3333
- uses: actions/checkout@v4
3434
- uses: actions/setup-node@v4
3535
with:
36-
node-version: 20
36+
node-version: 24
3737
cache: npm
3838
- run: npm ci
3939
- run: npm run build

package-lock.json

Lines changed: 203 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@
123123
},
124124
"devDependencies": {
125125
"@cfworker/json-schema": "^4.1.1",
126-
"@modelcontextprotocol/conformance": "^0.1.11",
127126
"@eslint/js": "^9.39.1",
127+
"@modelcontextprotocol/conformance": "^0.1.14",
128128
"@types/content-type": "^1.1.8",
129129
"@types/cors": "^2.8.17",
130130
"@types/cross-spawn": "^6.0.6",
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
# Known conformance test failures for v1.x
22
# These are tracked and should be removed as they're fixed.
33
#
4-
# tools_call: conformance runner's test server reuses a single Server
5-
# instance across requests, triggering v1.26.0's "Already connected"
6-
# guard (GHSA-345p-7cg4-v4c7). Fixed in conformance repo (PR #141),
7-
# remove this entry once a new conformance release is published.
8-
#
9-
# auth/pre-registration: scenario added in conformance 0.1.11 that
10-
# requires a dedicated client handler for pre-registered credentials.
11-
# Needs to be implemented in both v1.x and main.
4+
# auth/cross-app-access-complete-flow: SEP-990 Enterprise Managed OAuth
5+
# scenario added in conformance 0.1.14. Requires implementing token
6+
# exchange (RFC 8693) and JWT bearer grant (RFC 7523) in the client.
127
client:
13-
- tools_call
14-
- auth/pre-registration
8+
- auth/cross-app-access-complete-flow

test/conformance/src/everythingClient.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ElicitRequestSchema } from '../../../src/types.js';
1616
import { z } from 'zod';
1717

1818
import { logger } from './helpers/logger.js';
19+
import { ConformanceOAuthProvider } from './helpers/conformanceOAuthProvider.js';
1920
import { handle401, withOAuthRetry } from './helpers/withOAuthRetry.js';
2021

2122
/**
@@ -37,6 +38,11 @@ const ClientConformanceContextSchema = z.discriminatedUnion('name', [
3738
name: z.literal('auth/client-credentials-basic'),
3839
client_id: z.string(),
3940
client_secret: z.string()
41+
}),
42+
z.object({
43+
name: z.literal('auth/pre-registration'),
44+
client_id: z.string(),
45+
client_secret: z.string()
4046
})
4147
]);
4248

@@ -228,6 +234,43 @@ async function runClientCredentialsBasic(serverUrl: string): Promise<void> {
228234

229235
registerScenario('auth/client-credentials-basic', runClientCredentialsBasic);
230236

237+
// ============================================================================
238+
// Pre-registration scenario (no dynamic client registration)
239+
// ============================================================================
240+
241+
async function runPreRegistrationClient(serverUrl: string): Promise<void> {
242+
const ctx = parseContext();
243+
if (ctx.name !== 'auth/pre-registration') {
244+
throw new Error(`Expected pre-registration context, got ${ctx.name}`);
245+
}
246+
247+
// Create a provider pre-populated with registered credentials,
248+
// so the SDK skips dynamic client registration.
249+
const provider = new ConformanceOAuthProvider('http://localhost:3000/callback', {
250+
client_name: 'conformance-pre-registration',
251+
redirect_uris: ['http://localhost:3000/callback']
252+
});
253+
provider.saveClientInformation({
254+
client_id: ctx.client_id,
255+
client_secret: ctx.client_secret,
256+
redirect_uris: ['http://localhost:3000/callback']
257+
});
258+
259+
const oauthFetch = withOAuthRetry('conformance-pre-registration', new URL(serverUrl), handle401, undefined, provider)(fetch);
260+
261+
const client = new Client({ name: 'conformance-pre-registration', version: '1.0.0' }, { capabilities: {} });
262+
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
263+
fetch: oauthFetch
264+
});
265+
266+
await client.connect(transport);
267+
await client.listTools();
268+
await client.callTool({ name: 'test-tool', arguments: {} });
269+
await transport.close();
270+
}
271+
272+
registerScenario('auth/pre-registration', runPreRegistrationClient);
273+
231274
// ============================================================================
232275
// Elicitation defaults scenario
233276
// ============================================================================

test/conformance/src/helpers/withOAuthRetry.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,19 @@ export const withOAuthRetry = (
3838
clientName: string,
3939
baseUrl?: string | URL,
4040
handle401Fn: typeof handle401 = handle401,
41-
clientMetadataUrl?: string
41+
clientMetadataUrl?: string,
42+
existingProvider?: ConformanceOAuthProvider
4243
): Middleware => {
43-
const provider = new ConformanceOAuthProvider(
44-
'http://localhost:3000/callback',
45-
{
46-
client_name: clientName,
47-
redirect_uris: ['http://localhost:3000/callback']
48-
},
49-
clientMetadataUrl
50-
);
44+
const provider =
45+
existingProvider ??
46+
new ConformanceOAuthProvider(
47+
'http://localhost:3000/callback',
48+
{
49+
client_name: clientName,
50+
redirect_uris: ['http://localhost:3000/callback']
51+
},
52+
clientMetadataUrl
53+
);
5154
return (next: FetchLike) => {
5255
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
5356
const makeRequest = async (): Promise<Response> => {

0 commit comments

Comments
 (0)