Skip to content

Commit feb475f

Browse files
authored
fix: allow multi-segment issuer paths (#105)
1 parent 46f6dfd commit feb475f

File tree

2 files changed

+95
-2
lines changed

2 files changed

+95
-2
lines changed

src/Server/Registrar.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function oauthRoutes(string $oauthPrefix = 'oauth'): void
8989
'resource' => url('/'.$path),
9090
'authorization_servers' => [url('/'.$path)],
9191
'scopes_supported' => ['mcp:use'],
92-
]))->name('mcp.oauth.protected-resource');
92+
]))->where('path', '.*')->name('mcp.oauth.protected-resource');
9393

9494
Router::get('/.well-known/oauth-authorization-server/{path?}', fn (?string $path = '') => response()->json([
9595
'issuer' => url('/'.$path),
@@ -100,7 +100,7 @@ public function oauthRoutes(string $oauthPrefix = 'oauth'): void
100100
'code_challenge_methods_supported' => ['S256'],
101101
'scopes_supported' => ['mcp:use'],
102102
'grant_types_supported' => ['authorization_code', 'refresh_token'],
103-
]))->name('mcp.oauth.authorization-server');
103+
]))->where('path', '.*')->name('mcp.oauth.authorization-server');
104104

105105
Router::post($oauthPrefix.'/register', OAuthRegisterController::class);
106106
}

tests/Unit/Server/RegistrarTest.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,96 @@ public function createAuthorizationCodeGrantClient(string $name, array $redirect
269269

270270
$response->assertStatus(422);
271271
});
272+
273+
it('handles oauth discovery with multi-segment paths', function (): void {
274+
// Fake Passport's routes so the oauthRoutes() helper can resolve them.
275+
Route::get('/oauth/authorize')->name('passport.authorizations.authorize');
276+
Route::post('/oauth/token')->name('passport.token');
277+
278+
$registrar = new Registrar;
279+
$registrar->oauthRoutes();
280+
281+
// Test protected resource endpoint with multi-segment path
282+
$response = $this->getJson('/.well-known/oauth-protected-resource/mcp/weather');
283+
284+
$response->assertStatus(200);
285+
$response->assertJson([
286+
'resource' => url('/mcp/weather'),
287+
'authorization_servers' => [url('/mcp/weather')],
288+
'scopes_supported' => ['mcp:use'],
289+
]);
290+
291+
// Test authorization server endpoint with multi-segment path
292+
$response = $this->getJson('/.well-known/oauth-authorization-server/mcp/weather');
293+
294+
$response->assertStatus(200);
295+
$response->assertJsonStructure([
296+
'issuer',
297+
'authorization_endpoint',
298+
'token_endpoint',
299+
'registration_endpoint',
300+
'response_types_supported',
301+
'code_challenge_methods_supported',
302+
'scopes_supported',
303+
'grant_types_supported',
304+
]);
305+
$response->assertJson([
306+
'issuer' => url('/mcp/weather'),
307+
'scopes_supported' => ['mcp:use'],
308+
'response_types_supported' => ['code'],
309+
'code_challenge_methods_supported' => ['S256'],
310+
'grant_types_supported' => ['authorization_code', 'refresh_token'],
311+
]);
312+
});
313+
314+
it('handles oauth discovery with single segment paths', function (): void {
315+
// Fake Passport's routes so the oauthRoutes() helper can resolve them.
316+
Route::get('/oauth/authorize')->name('passport.authorizations.authorize');
317+
Route::post('/oauth/token')->name('passport.token');
318+
319+
$registrar = new Registrar;
320+
$registrar->oauthRoutes();
321+
322+
// Test backward compatibility with single-segment paths
323+
$response = $this->getJson('/.well-known/oauth-protected-resource/mcp');
324+
325+
$response->assertStatus(200);
326+
$response->assertJson([
327+
'resource' => url('/mcp'),
328+
'authorization_servers' => [url('/mcp')],
329+
'scopes_supported' => ['mcp:use'],
330+
]);
331+
332+
$response = $this->getJson('/.well-known/oauth-authorization-server/mcp');
333+
334+
$response->assertStatus(200);
335+
$response->assertJson([
336+
'issuer' => url('/mcp'),
337+
]);
338+
});
339+
340+
it('handles oauth discovery with no path', function (): void {
341+
// Fake Passport's routes so the oauthRoutes() helper can resolve them.
342+
Route::get('/oauth/authorize')->name('passport.authorizations.authorize');
343+
Route::post('/oauth/token')->name('passport.token');
344+
345+
$registrar = new Registrar;
346+
$registrar->oauthRoutes();
347+
348+
// Test with no path (root)
349+
$response = $this->getJson('/.well-known/oauth-protected-resource');
350+
351+
$response->assertStatus(200);
352+
$response->assertJson([
353+
'resource' => url('/'),
354+
'authorization_servers' => [url('/')],
355+
'scopes_supported' => ['mcp:use'],
356+
]);
357+
358+
$response = $this->getJson('/.well-known/oauth-authorization-server');
359+
360+
$response->assertStatus(200);
361+
$response->assertJson([
362+
'issuer' => url('/'),
363+
]);
364+
});

0 commit comments

Comments
 (0)