diff --git a/package-lock.json b/package-lock.json index cfd8a622..bc13aa0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/sdk", - "version": "1.5.0", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/sdk", - "version": "1.5.0", + "version": "1.6.0", "license": "MIT", "dependencies": { "content-type": "^1.0.5", diff --git a/src/server/auth/handlers/token.test.ts b/src/server/auth/handlers/token.test.ts index 0c4edbff..80997857 100644 --- a/src/server/auth/handlers/token.test.ts +++ b/src/server/auth/handlers/token.test.ts @@ -298,6 +298,7 @@ describe('Token Handler', () => { const proxyProvider = new ProxyOAuthServerProvider({ endpoints: { + authorizationUrl: 'https://example.com/authorize', tokenUrl: 'https://example.com/token' }, verifyAccessToken: async (token) => ({ diff --git a/src/server/auth/proxyProvider.test.ts b/src/server/auth/proxyProvider.test.ts index ab73d7b4..5f38d6e5 100644 --- a/src/server/auth/proxyProvider.test.ts +++ b/src/server/auth/proxyProvider.test.ts @@ -22,7 +22,7 @@ describe("Proxy OAuth Server Provider", () => { // Mock provider functions const mockVerifyToken = jest.fn(); const mockGetClient = jest.fn(); - + // Base provider options const baseOptions: ProxyOptions = { endpoints: { @@ -66,7 +66,7 @@ describe("Proxy OAuth Server Provider", () => { // Add helper function for failed responses const mockFailedResponse = () => { - (global.fetch as jest.Mock).mockImplementation(() => + (global.fetch as jest.Mock).mockImplementation(() => Promise.resolve({ ok: false, status: 400, @@ -103,20 +103,6 @@ describe("Proxy OAuth Server Provider", () => { expect(mockResponse.redirect).toHaveBeenCalledWith(expectedUrl.toString()); }); - - it("throws error when authorization endpoint is not configured", async () => { - const providerWithoutAuth = new ProxyOAuthServerProvider({ - ...baseOptions, - endpoints: { ...baseOptions.endpoints, authorizationUrl: undefined }, - }); - - await expect( - providerWithoutAuth.authorize(validClient, { - redirectUri: "https://example.com/callback", - codeChallenge: "test-challenge", - }, mockResponse) - ).rejects.toThrow("No authorization endpoint configured"); - }); }); describe("token exchange", () => { @@ -128,7 +114,7 @@ describe("Proxy OAuth Server Provider", () => { }; beforeEach(() => { - (global.fetch as jest.Mock).mockImplementation(() => + (global.fetch as jest.Mock).mockImplementation(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockTokenResponse), @@ -176,23 +162,6 @@ describe("Proxy OAuth Server Provider", () => { expect(tokens).toEqual(mockTokenResponse); }); - it("throws error when token endpoint is not configured", async () => { - const providerWithoutToken = new ProxyOAuthServerProvider({ - ...baseOptions, - endpoints: { ...baseOptions.endpoints, tokenUrl: undefined }, - }); - - await expect( - providerWithoutToken.exchangeAuthorizationCode(validClient, "test-code") - ).rejects.toThrow("No token endpoint configured"); - }); - - it("handles token exchange failure", async () => { - mockFailedResponse(); - await expect( - provider.exchangeAuthorizationCode(validClient, "invalid-code") - ).rejects.toThrow(ServerError); - }); }); describe("client registration", () => { @@ -202,7 +171,7 @@ describe("Proxy OAuth Server Provider", () => { redirect_uris: ["https://new-client.com/callback"], }; - (global.fetch as jest.Mock).mockImplementation(() => + (global.fetch as jest.Mock).mockImplementation(() => Promise.resolve({ ok: true, json: () => Promise.resolve(newClient), @@ -239,7 +208,7 @@ describe("Proxy OAuth Server Provider", () => { describe("token revocation", () => { it("revokes token", async () => { - (global.fetch as jest.Mock).mockImplementation(() => + (global.fetch as jest.Mock).mockImplementation(() => Promise.resolve({ ok: true, }) diff --git a/src/server/auth/proxyProvider.ts b/src/server/auth/proxyProvider.ts index 805b3d66..d9dd1b37 100644 --- a/src/server/auth/proxyProvider.ts +++ b/src/server/auth/proxyProvider.ts @@ -1,9 +1,9 @@ import { Response } from "express"; import { OAuthRegisteredClientsStore } from "./clients.js"; -import { - OAuthClientInformationFull, - OAuthClientInformationFullSchema, - OAuthTokenRevocationRequest, +import { + OAuthClientInformationFull, + OAuthClientInformationFullSchema, + OAuthTokenRevocationRequest, OAuthTokens, OAuthTokensSchema, } from "./../../shared/auth.js"; @@ -12,8 +12,8 @@ import { AuthorizationParams, OAuthServerProvider } from "./provider.js"; import { ServerError } from "./errors.js"; export type ProxyEndpoints = { - authorizationUrl?: string; - tokenUrl?: string; + authorizationUrl: string; + tokenUrl: string; revocationUrl?: string; registrationUrl?: string; }; @@ -24,14 +24,14 @@ export type ProxyOptions = { */ endpoints: ProxyEndpoints; - /** - * Function to verify access tokens and return auth info - */ - verifyAccessToken: (token: string) => Promise; + /** + * Function to verify access tokens and return auth info + */ + verifyAccessToken: (token: string) => Promise; - /** - * Function to fetch client information from the upstream server - */ + /** + * Function to fetch client information from the upstream server + */ getClient: (clientId: string) => Promise; }; @@ -45,7 +45,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { protected readonly _getClient: (clientId: string) => Promise; revokeToken?: ( - client: OAuthClientInformationFull, + client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest ) => Promise; @@ -55,15 +55,15 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { this._getClient = options.getClient; if (options.endpoints?.revocationUrl) { this.revokeToken = async ( - client: OAuthClientInformationFull, + client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest ) => { const revocationUrl = this._endpoints.revocationUrl; - + if (!revocationUrl) { throw new Error("No revocation endpoint configured"); } - + const params = new URLSearchParams(); params.set("token", request.token); params.set("client_id", client.client_id); @@ -73,7 +73,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { if (request.token_type_hint) { params.set("token_type_hint", request.token_type_hint); } - + const response = await fetch(revocationUrl, { method: "POST", headers: { @@ -81,7 +81,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { }, body: params.toString(), }); - + if (!response.ok) { throw new ServerError(`Token revocation failed: ${response.status}`); } @@ -115,18 +115,12 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { } async authorize( - client: OAuthClientInformationFull, - params: AuthorizationParams, + client: OAuthClientInformationFull, + params: AuthorizationParams, res: Response ): Promise { - const authorizationUrl = this._endpoints.authorizationUrl; - - if (!authorizationUrl) { - throw new Error("No authorization endpoint configured"); - } - // Start with required OAuth parameters - const targetUrl = new URL(authorizationUrl); + const targetUrl = new URL(this._endpoints.authorizationUrl); const searchParams = new URLSearchParams({ client_id: client.client_id, response_type: "code", @@ -144,7 +138,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { } async challengeForAuthorizationCode( - _client: OAuthClientInformationFull, + _client: OAuthClientInformationFull, _authorizationCode: string ): Promise { // In a proxy setup, we don't store the code challenge ourselves @@ -153,16 +147,10 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { } async exchangeAuthorizationCode( - client: OAuthClientInformationFull, + client: OAuthClientInformationFull, authorizationCode: string, codeVerifier?: string ): Promise { - const tokenUrl = this._endpoints.tokenUrl; - - if (!tokenUrl) { - throw new Error("No token endpoint configured"); - } - const params = new URLSearchParams({ grant_type: "authorization_code", client_id: client.client_id, @@ -177,7 +165,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { params.append("code_verifier", codeVerifier); } - const response = await fetch(tokenUrl, { + const response = await fetch(this._endpoints.tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", @@ -195,15 +183,10 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { } async exchangeRefreshToken( - client: OAuthClientInformationFull, + client: OAuthClientInformationFull, refreshToken: string, scopes?: string[] ): Promise { - const tokenUrl = this._endpoints.tokenUrl; - - if (!tokenUrl) { - throw new Error("No token endpoint configured"); - } const params = new URLSearchParams({ grant_type: "refresh_token", @@ -219,7 +202,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { params.set("scope", scopes.join(" ")); } - const response = await fetch(tokenUrl, { + const response = await fetch(this._endpoints.tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", @@ -237,5 +220,5 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider { async verifyAccessToken(token: string): Promise { return this._verifyAccessToken(token); - } + } } \ No newline at end of file