|
1 | | -import type { AuthorizationServerMetadata, OAuthTokens } from '@modelcontextprotocol/core'; |
| 1 | +import type { AuthorizationServerMetadata, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/core'; |
2 | 2 | import { LATEST_PROTOCOL_VERSION, OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core'; |
3 | 3 | import type { Mock } from 'vitest'; |
4 | 4 | import { expect, vi } from 'vitest'; |
@@ -1885,6 +1885,43 @@ describe('OAuth Authorization', () => { |
1885 | 1885 | ); |
1886 | 1886 | }); |
1887 | 1887 |
|
| 1888 | + it('includes scope in registration body when provided, overriding clientMetadata.scope', async () => { |
| 1889 | + const clientMetadataWithScope: OAuthClientMetadata = { |
| 1890 | + ...validClientMetadata, |
| 1891 | + scope: 'should-be-overridden' |
| 1892 | + }; |
| 1893 | + |
| 1894 | + const expectedClientInfo = { |
| 1895 | + ...validClientInfo, |
| 1896 | + scope: 'openid profile' |
| 1897 | + }; |
| 1898 | + |
| 1899 | + mockFetch.mockResolvedValueOnce({ |
| 1900 | + ok: true, |
| 1901 | + status: 200, |
| 1902 | + json: async () => expectedClientInfo |
| 1903 | + }); |
| 1904 | + |
| 1905 | + const clientInfo = await registerClient('https://auth.example.com', { |
| 1906 | + clientMetadata: clientMetadataWithScope, |
| 1907 | + scope: 'openid profile' |
| 1908 | + }); |
| 1909 | + |
| 1910 | + expect(clientInfo).toEqual(expectedClientInfo); |
| 1911 | + expect(mockFetch).toHaveBeenCalledWith( |
| 1912 | + expect.objectContaining({ |
| 1913 | + href: 'https://auth.example.com/register' |
| 1914 | + }), |
| 1915 | + expect.objectContaining({ |
| 1916 | + method: 'POST', |
| 1917 | + headers: { |
| 1918 | + 'Content-Type': 'application/json' |
| 1919 | + }, |
| 1920 | + body: JSON.stringify({ ...validClientMetadata, scope: 'openid profile' }) |
| 1921 | + }) |
| 1922 | + ); |
| 1923 | + }); |
| 1924 | + |
1888 | 1925 | it('validates client information response schema', async () => { |
1889 | 1926 | mockFetch.mockResolvedValueOnce({ |
1890 | 1927 | ok: true, |
@@ -2761,6 +2798,12 @@ describe('OAuth Authorization', () => { |
2761 | 2798 | const redirectCall = (mockProvider.redirectToAuthorization as Mock).mock.calls[0]!; |
2762 | 2799 | const authUrl: URL = redirectCall[0]; |
2763 | 2800 | expect(authUrl?.searchParams.get('scope')).toBe('mcp:read mcp:write mcp:admin'); |
| 2801 | + |
| 2802 | + // Verify the same scope was also used in the DCR request body |
| 2803 | + const registerCall = mockFetch.mock.calls.find(call => call[0].toString().includes('/register')); |
| 2804 | + expect(registerCall).toBeDefined(); |
| 2805 | + const registerBody = JSON.parse(registerCall![1].body as string); |
| 2806 | + expect(registerBody.scope).toBe('mcp:read mcp:write mcp:admin'); |
2764 | 2807 | }); |
2765 | 2808 |
|
2766 | 2809 | it('prefers explicit scope parameter over scopes_supported from PRM', async () => { |
|
0 commit comments