Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/client/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,10 +625,12 @@ export async function discoverOAuthProtectedResourceMetadata(
});

if (!response || response.status === 404) {
await response?.body?.cancel();
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
}

if (!response.ok) {
await response.body?.cancel();
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
}
return OAuthProtectedResourceMetadataSchema.parse(await response.json());
Expand Down Expand Up @@ -756,10 +758,12 @@ export async function discoverOAuthMetadata(
});

if (!response || response.status === 404) {
await response?.body?.cancel();
return undefined;
}

if (!response.ok) {
await response.body?.cancel();
throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
}

Expand Down Expand Up @@ -869,6 +873,7 @@ export async function discoverAuthorizationServerMetadata(
}

if (!response.ok) {
await response.body?.cancel();
// Continue looking for any 4xx response code.
if (response.status >= 400 && response.status < 500) {
continue; // Try next URL
Expand Down
3 changes: 2 additions & 1 deletion src/client/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ export class SSEClientTransport implements Transport {

const response = await (this._fetch ?? fetch)(this._endpoint, init);
if (!response.ok) {
const text = await response.text().catch(() => null);

if (response.status === 401 && this._authProvider) {
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
this._resourceMetadataUrl = resourceMetadataUrl;
Expand All @@ -272,7 +274,6 @@ export class SSEClientTransport implements Transport {
return this.send(message);
}

const text = await response.text().catch(() => null);
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
}
} catch (error) {
Expand Down
27 changes: 18 additions & 9 deletions src/client/streamableHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,13 @@ describe('StreamableHTTPClientTransport', () => {
ok: false,
status: 401,
statusText: 'Unauthorized',
headers: new Headers()
headers: new Headers(),
text: async () => Promise.reject('dont read my body')
})
.mockResolvedValue({
ok: false,
status: 404
status: 404,
text: async () => Promise.reject('dont read my body')
});

await expect(transport.send(message)).rejects.toThrow(UnauthorizedError);
Expand Down Expand Up @@ -883,7 +885,8 @@ describe('StreamableHTTPClientTransport', () => {
ok: false,
status: 401,
statusText: 'Unauthorized',
headers: new Headers()
headers: new Headers(),
text: async () => Promise.reject('dont read my body')
};
(global.fetch as Mock)
// Initial connection
Expand Down Expand Up @@ -936,7 +939,8 @@ describe('StreamableHTTPClientTransport', () => {
ok: false,
status: 401,
statusText: 'Unauthorized',
headers: new Headers()
headers: new Headers(),
text: async () => Promise.reject('dont read my body')
};
(global.fetch as Mock)
// Initial connection
Expand All @@ -962,7 +966,8 @@ describe('StreamableHTTPClientTransport', () => {
// Fallback should fail to complete the flow
.mockResolvedValue({
ok: false,
status: 404
status: 404,
text: async () => Promise.reject('dont read my body')
});

await expect(transport.send(message)).rejects.toThrow(UnauthorizedError);
Expand All @@ -987,7 +992,8 @@ describe('StreamableHTTPClientTransport', () => {
ok: false,
status: 401,
statusText: 'Unauthorized',
headers: new Headers()
headers: new Headers(),
text: async () => Promise.reject('dont read my body')
};
(global.fetch as Mock)
// Initial connection
Expand All @@ -1013,7 +1019,8 @@ describe('StreamableHTTPClientTransport', () => {
// Fallback should fail to complete the flow
.mockResolvedValue({
ok: false,
status: 404
status: 404,
text: async () => Promise.reject('dont read my body')
});

await expect(transport.send(message)).rejects.toThrow(UnauthorizedError);
Expand All @@ -1026,7 +1033,8 @@ describe('StreamableHTTPClientTransport', () => {
ok: false,
status: 401,
statusText: 'Unauthorized',
headers: new Headers()
headers: new Headers(),
text: async () => Promise.reject('dont read my body')
};

// Create custom fetch
Expand Down Expand Up @@ -1328,7 +1336,8 @@ describe('StreamableHTTPClientTransport', () => {
ok: false,
status: 401,
statusText: 'Unauthorized',
headers: new Headers()
headers: new Headers(),
text: async () => Promise.reject('dont read my body')
};

(global.fetch as Mock)
Expand Down
7 changes: 6 additions & 1 deletion src/client/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ export class StreamableHTTPClientTransport implements Transport {
});

if (!response.ok) {
await response.body?.cancel();

if (response.status === 401 && this._authProvider) {
// Need to authenticate
return await this._authThenStart();
Expand Down Expand Up @@ -463,6 +465,8 @@ export class StreamableHTTPClientTransport implements Transport {
}

if (!response.ok) {
const text = await response.text().catch(() => null);

if (response.status === 401 && this._authProvider) {
// Prevent infinite recursion when server returns 401 after successful auth
if (this._hasCompletedAuthFlow) {
Expand Down Expand Up @@ -525,7 +529,6 @@ export class StreamableHTTPClientTransport implements Transport {
}
}

const text = await response.text().catch(() => null);
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
}

Expand All @@ -535,6 +538,7 @@ export class StreamableHTTPClientTransport implements Transport {

// If the response is 202 Accepted, there's no body to process
if (response.status === 202) {
await response.body?.cancel();
// if the accepted notification is initialized, we start the SSE stream
// if it's supported by the server
if (isInitializedNotification(message)) {
Expand Down Expand Up @@ -609,6 +613,7 @@ export class StreamableHTTPClientTransport implements Transport {
};

const response = await (this._fetch ?? fetch)(this._url, init);
await response.body?.cancel();

// We specifically handle 405 as a valid response according to the spec,
// meaning the server does not support explicit session termination
Expand Down
3 changes: 2 additions & 1 deletion src/examples/server/elicitationUrlExample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ const tokenVerifier = {
});

if (!response.ok) {
throw new Error(`Invalid or expired token: ${await response.text()}`);
const text = await response.text().catch(() => null);
throw new Error(`Invalid or expired token: ${text}`);
}

const data = await response.json();
Expand Down
3 changes: 2 additions & 1 deletion src/examples/server/simpleStreamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,8 @@ if (useOAuth) {
});

if (!response.ok) {
throw new Error(`Invalid or expired token: ${await response.text()}`);
const text = await response.text().catch(() => null);
throw new Error(`Invalid or expired token: ${text}`);
}

const data = await response.json();
Expand Down
4 changes: 4 additions & 0 deletions src/server/auth/providers/proxyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
},
body: params.toString()
});
await response.body?.cancel();

if (!response.ok) {
throw new ServerError(`Token revocation failed: ${response.status}`);
Expand All @@ -107,6 +108,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
});

if (!response.ok) {
await response.body?.cancel();
throw new ServerError(`Client registration failed: ${response.status}`);
}

Expand Down Expand Up @@ -181,6 +183,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
});

if (!response.ok) {
await response.body?.cancel();
throw new ServerError(`Token exchange failed: ${response.status}`);
}

Expand Down Expand Up @@ -221,6 +224,7 @@ export class ProxyOAuthServerProvider implements OAuthServerProvider {
});

if (!response.ok) {
await response.body?.cancel();
throw new ServerError(`Token refresh failed: ${response.status}`);
}

Expand Down
Loading