feat: use scopes_supported from resource metadata by default (forward-port #757) (#1614)

Co-authored-by: Anthony Giniers <antogyn@gmail.com>
This commit is contained in:
Paul Carleton 2026-03-02 17:13:59 +00:00 • committed by GitHub
commit 1a78b0138f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,4 +1,4 @@
import type { AuthorizationServerMetadata, OAuthTokens } from '@modelcontextprotocol/core';
import type { AuthorizationServerMetadata, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core';
import { LATEST_PROTOCOL_VERSION, OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core';
import type { Mock } from 'vitest';
import { expect, vi } from 'vitest';
@ -1885,6 +1885,43 @@ describe('OAuth Authorization', () => {
);
});
it('includes scope in registration body when provided, overriding clientMetadata.scope', async () => {
const clientMetadataWithScope: OAuthClientMetadata = {
...validClientMetadata,
scope: 'should-be-overridden'
};
const expectedClientInfo = {
...validClientInfo,
scope: 'openid profile'
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => expectedClientInfo
});
const clientInfo = await registerClient('https://auth.example.com', {
clientMetadata: clientMetadataWithScope,
scope: 'openid profile'
});
expect(clientInfo).toEqual(expectedClientInfo);
expect(mockFetch).toHaveBeenCalledWith(
expect.objectContaining({
href: 'https://auth.example.com/register'
}),
expect.objectContaining({
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ...validClientMetadata, scope: 'openid profile' })
})
);
});
it('validates client information response schema', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
@ -2761,6 +2798,12 @@ describe('OAuth Authorization', () => {
const redirectCall = (mockProvider.redirectToAuthorization as Mock).mock.calls[0]!;
const authUrl: URL = redirectCall[0];
expect(authUrl?.searchParams.get('scope')).toBe('mcp:read mcp:write mcp:admin');
// Verify the same scope was also used in the DCR request body
const registerCall = mockFetch.mock.calls.find(call => call[0].toString().includes('/register'));
expect(registerCall).toBeDefined();
const registerBody = JSON.parse(registerCall![1].body as string);
expect(registerBody.scope).toBe('mcp:read mcp:write mcp:admin');
});
it('prefers explicit scope parameter over scopes_supported from PRM', async () => {