Skip to content

Commit 4262996

Browse files
committed
add final endpoint method
1 parent 146181b commit 4262996

File tree

3 files changed

+224
-9
lines changed

3 files changed

+224
-9
lines changed

src/scenarios/client/auth/march-spec-backcompat.ts

Lines changed: 216 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@ import { ScenarioUrls } from '../../../types.js';
33
import { createAuthServer } from './helpers/createAuthServer.js';
44
import { createServer } from './helpers/createServer.js';
55
import { ServerLifecycle } from './helpers/serverLifecycle.js';
6-
import { Request, Response } from 'express';
6+
import express, { Request, Response } from 'express';
77

8-
export class AuthMarchSpecBackcompatScenario implements Scenario {
9-
name = 'auth/march-spec-backcompat';
8+
export class Auth20250326OAuthMetadataBackcompatScenario implements Scenario {
9+
name = 'auth/2025-03-26-oauth-metadata-backcompat';
1010
description =
11-
'Tests March 2024 spec OAuth flow: no PRM (Protected Resource Metadata), OAuth metadata at root location';
11+
'Tests 2025-03-26 spec OAuth flow: no PRM (Protected Resource Metadata), OAuth metadata at root location';
1212
private server = new ServerLifecycle();
1313
private checks: ConformanceCheck[] = [];
1414

1515
async start(): Promise<ScenarioUrls> {
1616
this.checks = [];
17-
// TODO: I want to move the Auth URL's below a path.
18-
1917
// Legacy server, so we create the auth server endpoints on the
2018
// same URL as the main server (rather than separating AS / RS).
2119
const authApp = createAuthServer(this.checks, this.server.getUrl, {
@@ -113,3 +111,215 @@ export class AuthMarchSpecBackcompatScenario implements Scenario {
113111
return this.checks;
114112
}
115113
}
114+
115+
export class Auth20250326OEndpointFallbackScenario implements Scenario {
116+
name = 'auth/2025-03-26-oauth-endpoint-fallback';
117+
description =
118+
'Tests OAuth flow with no metadata endpoints, relying on fallback to standard OAuth endpoints at server root (2025-03-26 spec behavior)';
119+
private server = new ServerLifecycle();
120+
private checks: ConformanceCheck[] = [];
121+
122+
async start(): Promise<ScenarioUrls> {
123+
this.checks = [];
124+
125+
const app = createServer(
126+
this.checks,
127+
this.server.getUrl,
128+
this.server.getUrl,
129+
{ prmPath: null }
130+
);
131+
132+
// needed for /token endpoint
133+
app.use(express.urlencoded({ extended: true }));
134+
135+
app.get(
136+
'/.well-known/oauth-authorization-server',
137+
(req: Request, res: Response) => {
138+
this.checks.push({
139+
id: 'no-oauth-metadata',
140+
name: 'No OAuth Metadata',
141+
description:
142+
'Client attempted to fetch OAuth metadata, but fallback scenario does not provide it',
143+
status: 'SUCCESS',
144+
timestamp: new Date().toISOString(),
145+
details: {
146+
url: req.url,
147+
path: req.path,
148+
note: '2025-03-26 spec behavior: fallback to direct endpoints'
149+
}
150+
});
151+
152+
res.status(404).json({
153+
error: 'not_found',
154+
error_description: 'OAuth metadata not available (fallback behavior)'
155+
});
156+
}
157+
);
158+
159+
app.get(
160+
'/.well-known/oauth-protected-resource',
161+
(req: Request, res: Response) => {
162+
this.checks.push({
163+
id: 'no-prm-root',
164+
name: 'No PRM at Root',
165+
description:
166+
'Client attempted to fetch PRM at root location, but March spec does not have PRM',
167+
status: 'SUCCESS',
168+
timestamp: new Date().toISOString(),
169+
details: {
170+
url: req.url,
171+
path: req.path,
172+
note: '2025-03-26 spec behavior: no PRM available'
173+
}
174+
});
175+
176+
res.status(404).json({
177+
error: 'not_found',
178+
error_description: 'PRM not available (March spec behavior)'
179+
});
180+
}
181+
);
182+
183+
app.get(
184+
'/.well-known/oauth-protected-resource/mcp',
185+
(req: Request, res: Response) => {
186+
this.checks.push({
187+
id: 'no-prm-path',
188+
name: 'No PRM at Path',
189+
description:
190+
'Client attempted to fetch PRM at path-based location, but March spec behavior does not have PRM',
191+
status: 'SUCCESS',
192+
timestamp: new Date().toISOString(),
193+
details: {
194+
url: req.url,
195+
path: req.path,
196+
note: '2025-03-26 spec behavior: no PRM available'
197+
}
198+
});
199+
200+
res.status(404).json({
201+
error: 'not_found',
202+
error_description: 'PRM not available (March spec behavior)'
203+
});
204+
}
205+
);
206+
207+
app.get('/authorize', (req: Request, res: Response) => {
208+
this.checks.push({
209+
id: 'authorization-request',
210+
name: 'AuthorizationRequest',
211+
description: 'Client made authorization request to fallback endpoint',
212+
status: 'SUCCESS',
213+
timestamp: new Date().toISOString(),
214+
specReferences: [
215+
{
216+
id: 'MCP-2025-03-26-Authorization',
217+
url: 'https://modelcontextprotocol.io/specification/2025-03-26/authorization'
218+
}
219+
],
220+
details: {
221+
response_type: req.query.response_type,
222+
client_id: req.query.client_id,
223+
redirect_uri: req.query.redirect_uri,
224+
state: req.query.state,
225+
code_challenge: req.query.code_challenge ? 'present' : 'missing',
226+
code_challenge_method: req.query.code_challenge_method
227+
}
228+
});
229+
230+
const redirectUri = req.query.redirect_uri as string;
231+
const state = req.query.state as string;
232+
const redirectUrl = new URL(redirectUri);
233+
redirectUrl.searchParams.set('code', 'test-auth-code');
234+
if (state) {
235+
redirectUrl.searchParams.set('state', state);
236+
}
237+
238+
res.redirect(redirectUrl.toString());
239+
});
240+
241+
app.post('/token', (req: Request, res: Response) => {
242+
this.checks.push({
243+
id: 'token-request',
244+
name: 'TokenRequest',
245+
description: 'Client requested access token from fallback endpoint',
246+
status: 'SUCCESS',
247+
timestamp: new Date().toISOString(),
248+
specReferences: [
249+
{
250+
id: 'MCP-2025-03-26-Authorization',
251+
url: 'https://modelcontextprotocol.io/specification/2025-03-26/authorization'
252+
}
253+
],
254+
details: {
255+
endpoint: '/token',
256+
grantType: req.body.grant_type
257+
}
258+
});
259+
260+
res.json({
261+
access_token: 'test-token',
262+
token_type: 'Bearer',
263+
expires_in: 3600
264+
});
265+
});
266+
267+
app.post('/register', (req: Request, res: Response) => {
268+
this.checks.push({
269+
id: 'client-registration',
270+
name: 'ClientRegistration',
271+
description:
272+
'Client registered with authorization server at fallback endpoint',
273+
status: 'SUCCESS',
274+
timestamp: new Date().toISOString(),
275+
specReferences: [
276+
{
277+
id: 'MCP-2025-03-26-Authorization',
278+
url: 'https://modelcontextprotocol.io/specification/2025-03-26/authorization'
279+
}
280+
],
281+
details: {
282+
endpoint: '/register',
283+
clientName: req.body.client_name
284+
}
285+
});
286+
287+
res.status(201).json({
288+
client_id: 'test-client-id',
289+
client_secret: 'test-client-secret',
290+
client_name: req.body.client_name || 'test-client',
291+
redirect_uris: req.body.redirect_uris || []
292+
});
293+
});
294+
295+
await this.server.start(app);
296+
297+
return { serverUrl: `${this.server.getUrl()}/mcp` };
298+
}
299+
300+
async stop() {
301+
await this.server.stop();
302+
}
303+
304+
getChecks(): ConformanceCheck[] {
305+
const expectedSlugs = [
306+
'client-registration',
307+
'authorization-request',
308+
'token-request'
309+
];
310+
311+
for (const slug of expectedSlugs) {
312+
if (!this.checks.find((c) => c.id === slug)) {
313+
this.checks.push({
314+
id: slug,
315+
name: `Expected Check Missing: ${slug}`,
316+
description: `Expected Check Missing: ${slug}`,
317+
status: 'FAILURE',
318+
timestamp: new Date().toISOString()
319+
});
320+
}
321+
}
322+
323+
return this.checks;
324+
}
325+
}

src/scenarios/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { InitializeScenario } from './client/initialize.js';
33
import { ToolsCallScenario } from './client/tools_call.js';
44
import { AuthBasicDCRScenario } from './client/auth/basic-dcr.js';
55
import { AuthBasicMetadataVar1Scenario } from './client/auth/basic-metadata-var1.js';
6-
import { AuthMarchSpecBackcompatScenario } from './client/auth/march-spec-backcompat.js';
6+
import {
7+
Auth20250326OAuthMetadataBackcompatScenario,
8+
Auth20250326OEndpointFallbackScenario
9+
} from './client/auth/march-spec-backcompat.js';
710
import { ElicitationClientDefaultsScenario } from './client/elicitation-defaults.js';
811

912
// Import all new server test scenarios
@@ -118,7 +121,8 @@ const scenariosList: Scenario[] = [
118121
new ToolsCallScenario(),
119122
new AuthBasicDCRScenario(),
120123
new AuthBasicMetadataVar1Scenario(),
121-
new AuthMarchSpecBackcompatScenario(),
124+
new Auth20250326OAuthMetadataBackcompatScenario(),
125+
new Auth20250326OEndpointFallbackScenario(),
122126
new ElicitationClientDefaultsScenario()
123127
];
124128

src/scenarios/request-logger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export function createRequestLogger(
1616
let requestDescription = `Received ${req.method} request for ${req.path}`;
1717
const requestDetails: any = {
1818
method: req.method,
19-
path: req.path
19+
path: req.path,
20+
body: req.body
2021
};
2122

2223
// Add query parameters to details if they exist

0 commit comments

Comments
 (0)