mcp-proxy 5.10.0 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/jsr.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "include": ["src/index.ts", "src/bin/mcp-proxy.ts"],
4
4
  "license": "MIT",
5
5
  "name": "@punkpeye/mcp-proxy",
6
- "version": "5.10.0"
6
+ "version": "5.11.0"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "5.10.0",
3
+ "version": "5.11.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsdown",
@@ -132,7 +132,7 @@ describe("AuthenticationMiddleware", () => {
132
132
  const response = middleware.getUnauthorizedResponse();
133
133
 
134
134
  expect(response.headers["WWW-Authenticate"]).toBe(
135
- 'Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"',
135
+ 'Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource", error="invalid_token", error_description="Unauthorized: Invalid or missing API key"',
136
136
  );
137
137
  });
138
138
 
@@ -148,21 +148,24 @@ describe("AuthenticationMiddleware", () => {
148
148
  const response = middleware.getUnauthorizedResponse();
149
149
 
150
150
  expect(response.headers["WWW-Authenticate"]).toBe(
151
- 'Bearer resource_metadata="https://example.com//.well-known/oauth-protected-resource"',
151
+ 'Bearer resource_metadata="https://example.com//.well-known/oauth-protected-resource", error="invalid_token", error_description="Unauthorized: Invalid or missing API key"',
152
152
  );
153
153
  });
154
154
 
155
- it("should not include WWW-Authenticate header when OAuth config is empty", () => {
155
+ it("should include minimal WWW-Authenticate header when OAuth config is empty object", () => {
156
156
  const middleware = new AuthenticationMiddleware({
157
157
  apiKey: "test",
158
158
  oauth: {},
159
159
  });
160
160
  const response = middleware.getUnauthorizedResponse();
161
161
 
162
- expect(response.headers["WWW-Authenticate"]).toBeUndefined();
162
+ // Even with empty oauth object, default error and error_description are added
163
+ expect(response.headers["WWW-Authenticate"]).toBe(
164
+ 'Bearer error="invalid_token", error_description="Unauthorized: Invalid or missing API key"',
165
+ );
163
166
  });
164
167
 
165
- it("should not include WWW-Authenticate header when protectedResource is empty", () => {
168
+ it("should include minimal WWW-Authenticate header when protectedResource is empty", () => {
166
169
  const middleware = new AuthenticationMiddleware({
167
170
  apiKey: "test",
168
171
  oauth: {
@@ -171,7 +174,10 @@ describe("AuthenticationMiddleware", () => {
171
174
  });
172
175
  const response = middleware.getUnauthorizedResponse();
173
176
 
174
- expect(response.headers["WWW-Authenticate"]).toBeUndefined();
177
+ // Even without resource_metadata, default error and error_description are added
178
+ expect(response.headers["WWW-Authenticate"]).toBe(
179
+ 'Bearer error="invalid_token", error_description="Unauthorized: Invalid or missing API key"',
180
+ );
175
181
  });
176
182
 
177
183
  it("should include WWW-Authenticate header with OAuth config but no apiKey", () => {
@@ -185,8 +191,140 @@ describe("AuthenticationMiddleware", () => {
185
191
  const response = middleware.getUnauthorizedResponse();
186
192
 
187
193
  expect(response.headers["WWW-Authenticate"]).toBe(
188
- 'Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"',
194
+ 'Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource", error="invalid_token", error_description="Unauthorized: Invalid or missing API key"',
189
195
  );
190
196
  });
197
+
198
+ it("should include realm in WWW-Authenticate header when configured", () => {
199
+ const middleware = new AuthenticationMiddleware({
200
+ apiKey: "test",
201
+ oauth: {
202
+ protectedResource: {
203
+ resource: "https://example.com",
204
+ },
205
+ realm: "example-realm",
206
+ },
207
+ });
208
+ const response = middleware.getUnauthorizedResponse();
209
+
210
+ expect(response.headers["WWW-Authenticate"]).toContain('realm="example-realm"');
211
+ expect(response.headers["WWW-Authenticate"]).toContain('resource_metadata="https://example.com/.well-known/oauth-protected-resource"');
212
+ });
213
+
214
+ it("should include custom error in WWW-Authenticate header via options", () => {
215
+ const middleware = new AuthenticationMiddleware({
216
+ apiKey: "test",
217
+ oauth: {
218
+ protectedResource: {
219
+ resource: "https://example.com",
220
+ },
221
+ },
222
+ });
223
+ const response = middleware.getUnauthorizedResponse({
224
+ error: "insufficient_scope",
225
+ error_description: "The request requires higher privileges",
226
+ });
227
+
228
+ expect(response.headers["WWW-Authenticate"]).toContain('error="insufficient_scope"');
229
+ expect(response.headers["WWW-Authenticate"]).toContain('error_description="The request requires higher privileges"');
230
+ });
231
+
232
+ it("should include scope in WWW-Authenticate header", () => {
233
+ const middleware = new AuthenticationMiddleware({
234
+ apiKey: "test",
235
+ oauth: {
236
+ protectedResource: {
237
+ resource: "https://example.com",
238
+ },
239
+ scope: "read write",
240
+ },
241
+ });
242
+ const response = middleware.getUnauthorizedResponse();
243
+
244
+ expect(response.headers["WWW-Authenticate"]).toContain('scope="read write"');
245
+ });
246
+
247
+ it("should override config error with options error", () => {
248
+ const middleware = new AuthenticationMiddleware({
249
+ apiKey: "test",
250
+ oauth: {
251
+ error: "invalid_request",
252
+ error_description: "Config error description",
253
+ protectedResource: {
254
+ resource: "https://example.com",
255
+ },
256
+ },
257
+ });
258
+ const response = middleware.getUnauthorizedResponse({
259
+ error: "invalid_token",
260
+ error_description: "Options error description",
261
+ });
262
+
263
+ expect(response.headers["WWW-Authenticate"]).toContain('error="invalid_token"');
264
+ expect(response.headers["WWW-Authenticate"]).toContain('error_description="Options error description"');
265
+ expect(response.headers["WWW-Authenticate"]).not.toContain("invalid_request");
266
+ expect(response.headers["WWW-Authenticate"]).not.toContain("Config error description");
267
+ });
268
+
269
+ it("should include error_uri in WWW-Authenticate header", () => {
270
+ const middleware = new AuthenticationMiddleware({
271
+ apiKey: "test",
272
+ oauth: {
273
+ error_uri: "https://example.com/errors/auth",
274
+ protectedResource: {
275
+ resource: "https://example.com",
276
+ },
277
+ },
278
+ });
279
+ const response = middleware.getUnauthorizedResponse();
280
+
281
+ expect(response.headers["WWW-Authenticate"]).toContain('error_uri="https://example.com/errors/auth"');
282
+ });
283
+
284
+ it("should properly escape quotes in error_description", () => {
285
+ const middleware = new AuthenticationMiddleware({
286
+ apiKey: "test",
287
+ oauth: {
288
+ protectedResource: {
289
+ resource: "https://example.com",
290
+ },
291
+ },
292
+ });
293
+ const response = middleware.getUnauthorizedResponse({
294
+ error_description: 'Token "abc123" is invalid',
295
+ });
296
+
297
+ expect(response.headers["WWW-Authenticate"]).toContain('error_description="Token \\"abc123\\" is invalid"');
298
+ });
299
+
300
+ it("should include all parameters in correct order", () => {
301
+ const middleware = new AuthenticationMiddleware({
302
+ apiKey: "test",
303
+ oauth: {
304
+ error: "invalid_token",
305
+ error_description: "Token expired",
306
+ error_uri: "https://example.com/errors",
307
+ protectedResource: {
308
+ resource: "https://example.com",
309
+ },
310
+ realm: "my-realm",
311
+ scope: "read write",
312
+ },
313
+ });
314
+ const response = middleware.getUnauthorizedResponse();
315
+
316
+ const header = response.headers["WWW-Authenticate"];
317
+ expect(header).toContain('realm="my-realm"');
318
+ expect(header).toContain('resource_metadata="https://example.com/.well-known/oauth-protected-resource"');
319
+ expect(header).toContain('error="invalid_token"');
320
+ expect(header).toContain('error_description="Token expired"');
321
+ expect(header).toContain('error_uri="https://example.com/errors"');
322
+ expect(header).toContain('scope="read write"');
323
+
324
+ // Check order: realm, resource_metadata, error, error_description, error_uri, scope
325
+ expect(header.indexOf('realm=')).toBeLessThan(header.indexOf('resource_metadata='));
326
+ expect(header.indexOf('resource_metadata=')).toBeLessThan(header.indexOf('error='));
327
+ expect(header.indexOf('error=')).toBeLessThan(header.indexOf('error_description='));
328
+ });
191
329
  });
192
330
  });
@@ -3,30 +3,76 @@ import type { IncomingMessage } from "http";
3
3
  export interface AuthConfig {
4
4
  apiKey?: string;
5
5
  oauth?: {
6
+ error?: string;
7
+ error_description?: string;
8
+ error_uri?: string;
6
9
  protectedResource?: {
7
10
  resource?: string;
8
11
  };
12
+ realm?: string;
13
+ scope?: string;
9
14
  };
10
15
  }
11
16
 
12
17
  export class AuthenticationMiddleware {
13
18
  constructor(private config: AuthConfig = {}) {}
14
19
 
15
- getUnauthorizedResponse(): { body: string; headers: Record<string, string> } {
20
+ getUnauthorizedResponse(options?: {
21
+ error?: string;
22
+ error_description?: string;
23
+ error_uri?: string;
24
+ scope?: string;
25
+ }): { body: string; headers: Record<string, string> } {
16
26
  const headers: Record<string, string> = {
17
27
  "Content-Type": "application/json",
18
28
  };
19
29
 
20
- // Add WWW-Authenticate header if OAuth config is available
21
- if (this.config.oauth?.protectedResource?.resource) {
22
- headers["WWW-Authenticate"] = `Bearer resource_metadata="${this.config.oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`;
30
+ // Build WWW-Authenticate header if OAuth config is available
31
+ if (this.config.oauth) {
32
+ const params: string[] = [];
33
+
34
+ // Add realm if configured
35
+ if (this.config.oauth.realm) {
36
+ params.push(`realm="${this.config.oauth.realm}"`);
37
+ }
38
+
39
+ // Add resource_metadata if configured
40
+ if (this.config.oauth.protectedResource?.resource) {
41
+ params.push(`resource_metadata="${this.config.oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`);
42
+ }
43
+
44
+ // Add error from options or config (options takes precedence)
45
+ const error = options?.error || this.config.oauth.error || "invalid_token";
46
+ params.push(`error="${error}"`);
47
+
48
+ // Add error_description from options or config (options takes precedence)
49
+ const error_description = options?.error_description || this.config.oauth.error_description || "Unauthorized: Invalid or missing API key";
50
+ // Escape quotes in error description
51
+ const escaped = error_description.replace(/"/g, '\\"');
52
+ params.push(`error_description="${escaped}"`);
53
+
54
+ // Add error_uri from options or config (options takes precedence)
55
+ const error_uri = options?.error_uri || this.config.oauth.error_uri;
56
+ if (error_uri) {
57
+ params.push(`error_uri="${error_uri}"`);
58
+ }
59
+
60
+ // Add scope from options or config (options takes precedence)
61
+ const scope = options?.scope || this.config.oauth.scope;
62
+ if (scope) {
63
+ params.push(`scope="${scope}"`);
64
+ }
65
+
66
+ if (params.length > 0) {
67
+ headers["WWW-Authenticate"] = `Bearer ${params.join(", ")}`;
68
+ }
23
69
  }
24
70
 
25
71
  return {
26
72
  body: JSON.stringify({
27
73
  error: {
28
74
  code: 401,
29
- message: "Unauthorized: Invalid or missing API key",
75
+ message: options?.error_description || "Unauthorized: Invalid or missing API key",
30
76
  },
31
77
  id: null,
32
78
  jsonrpc: "2.0",
@@ -1585,6 +1585,150 @@ it("returns 500 when createServer throws non-auth error", async () => {
1585
1585
  await httpServer.close();
1586
1586
  });
1587
1587
 
1588
+ it("includes WWW-Authenticate header in 401 response with OAuth config", async () => {
1589
+ const port = await getRandomPort();
1590
+
1591
+ const httpServer = await startHTTPServer({
1592
+ createServer: async () => {
1593
+ throw new Error("Invalid JWT token");
1594
+ },
1595
+ oauth: {
1596
+ protectedResource: {
1597
+ resource: "https://example.com",
1598
+ },
1599
+ realm: "mcp-server",
1600
+ },
1601
+ port,
1602
+ stateless: true,
1603
+ });
1604
+
1605
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1606
+ body: JSON.stringify({
1607
+ id: 1,
1608
+ jsonrpc: "2.0",
1609
+ method: "initialize",
1610
+ params: {
1611
+ capabilities: {},
1612
+ clientInfo: { name: "test", version: "1.0.0" },
1613
+ protocolVersion: "2024-11-05",
1614
+ },
1615
+ }),
1616
+ headers: {
1617
+ "Accept": "application/json, text/event-stream",
1618
+ "Content-Type": "application/json",
1619
+ },
1620
+ method: "POST",
1621
+ });
1622
+
1623
+ expect(response.status).toBe(401);
1624
+
1625
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
1626
+ expect(wwwAuthHeader).toBeTruthy();
1627
+ expect(wwwAuthHeader).toContain('Bearer');
1628
+ expect(wwwAuthHeader).toContain('realm="mcp-server"');
1629
+ expect(wwwAuthHeader).toContain('resource_metadata="https://example.com/.well-known/oauth-protected-resource"');
1630
+ expect(wwwAuthHeader).toContain('error="invalid_token"');
1631
+ expect(wwwAuthHeader).toContain('error_description="Invalid JWT token"');
1632
+
1633
+ await httpServer.close();
1634
+ });
1635
+
1636
+ it("includes WWW-Authenticate header when authenticate callback fails with OAuth", async () => {
1637
+ const port = await getRandomPort();
1638
+
1639
+ const authenticate = vi.fn().mockRejectedValue(new Error("Token signature verification failed"));
1640
+
1641
+ const httpServer = await startHTTPServer({
1642
+ authenticate,
1643
+ createServer: async () => {
1644
+ const mcpServer = new Server(
1645
+ { name: "test", version: "1.0.0" },
1646
+ { capabilities: {} },
1647
+ );
1648
+ return mcpServer;
1649
+ },
1650
+ oauth: {
1651
+ error_uri: "https://example.com/docs/errors",
1652
+ protectedResource: {
1653
+ resource: "https://api.example.com",
1654
+ },
1655
+ realm: "example-api",
1656
+ },
1657
+ port,
1658
+ stateless: true,
1659
+ });
1660
+
1661
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1662
+ body: JSON.stringify({
1663
+ id: 1,
1664
+ jsonrpc: "2.0",
1665
+ method: "initialize",
1666
+ params: {
1667
+ capabilities: {},
1668
+ clientInfo: { name: "test", version: "1.0.0" },
1669
+ protocolVersion: "2024-11-05",
1670
+ },
1671
+ }),
1672
+ headers: {
1673
+ "Accept": "application/json, text/event-stream",
1674
+ "Authorization": "Bearer expired-token",
1675
+ "Content-Type": "application/json",
1676
+ },
1677
+ method: "POST",
1678
+ });
1679
+
1680
+ expect(response.status).toBe(401);
1681
+ expect(authenticate).toHaveBeenCalled();
1682
+
1683
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
1684
+ expect(wwwAuthHeader).toBeTruthy();
1685
+ expect(wwwAuthHeader).toContain('Bearer');
1686
+ expect(wwwAuthHeader).toContain('realm="example-api"');
1687
+ expect(wwwAuthHeader).toContain('resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"');
1688
+ expect(wwwAuthHeader).toContain('error="invalid_token"');
1689
+ expect(wwwAuthHeader).toContain('error_description="Token signature verification failed"');
1690
+ expect(wwwAuthHeader).toContain('error_uri="https://example.com/docs/errors"');
1691
+
1692
+ await httpServer.close();
1693
+ });
1694
+
1695
+ it("does not include WWW-Authenticate header in 401 response without OAuth config", async () => {
1696
+ const port = await getRandomPort();
1697
+
1698
+ const httpServer = await startHTTPServer({
1699
+ createServer: async () => {
1700
+ throw new Error("Authentication required");
1701
+ },
1702
+ port,
1703
+ stateless: true,
1704
+ });
1705
+
1706
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1707
+ body: JSON.stringify({
1708
+ id: 1,
1709
+ jsonrpc: "2.0",
1710
+ method: "initialize",
1711
+ params: {
1712
+ capabilities: {},
1713
+ clientInfo: { name: "test", version: "1.0.0" },
1714
+ protocolVersion: "2024-11-05",
1715
+ },
1716
+ }),
1717
+ headers: {
1718
+ "Accept": "application/json, text/event-stream",
1719
+ "Content-Type": "application/json",
1720
+ },
1721
+ method: "POST",
1722
+ });
1723
+
1724
+ expect(response.status).toBe(401);
1725
+
1726
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
1727
+ expect(wwwAuthHeader).toBeNull();
1728
+
1729
+ await httpServer.close();
1730
+ });
1731
+
1588
1732
  it("succeeds when authenticate returns { authenticated: true } in stateless mode", async () => {
1589
1733
  const stdioTransport = new StdioClientTransport({
1590
1734
  args: ["src/fixtures/simple-stdio-server.ts"],
@@ -61,22 +61,82 @@ const createJsonRpcErrorResponse = (code: number, message: string) => {
61
61
  // Helper function to get WWW-Authenticate header value
62
62
  const getWWWAuthenticateHeader = (
63
63
  oauth?: AuthConfig["oauth"],
64
+ options?: {
65
+ error?: string;
66
+ error_description?: string;
67
+ error_uri?: string;
68
+ scope?: string;
69
+ },
64
70
  ): string | undefined => {
65
- if (!oauth?.protectedResource?.resource) {
71
+ if (!oauth) {
66
72
  return undefined;
67
73
  }
68
74
 
69
- return `Bearer resource_metadata="${oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`;
75
+ const params: string[] = [];
76
+
77
+ // Add realm if configured
78
+ if (oauth.realm) {
79
+ params.push(`realm="${oauth.realm}"`);
80
+ }
81
+
82
+ // Add resource_metadata if configured
83
+ if (oauth.protectedResource?.resource) {
84
+ params.push(`resource_metadata="${oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`);
85
+ }
86
+
87
+ // Add error from options or config (options takes precedence)
88
+ const error = options?.error || oauth.error;
89
+ if (error) {
90
+ params.push(`error="${error}"`);
91
+ }
92
+
93
+ // Add error_description from options or config (options takes precedence)
94
+ const error_description = options?.error_description || oauth.error_description;
95
+ if (error_description) {
96
+ // Escape quotes in error description
97
+ const escaped = error_description.replace(/"/g, '\\"');
98
+ params.push(`error_description="${escaped}"`);
99
+ }
100
+
101
+ // Add error_uri from options or config (options takes precedence)
102
+ const error_uri = options?.error_uri || oauth.error_uri;
103
+ if (error_uri) {
104
+ params.push(`error_uri="${error_uri}"`);
105
+ }
106
+
107
+ // Add scope from options or config (options takes precedence)
108
+ const scope = options?.scope || oauth.scope;
109
+ if (scope) {
110
+ params.push(`scope="${scope}"`);
111
+ }
112
+
113
+ // Return undefined if no parameters were added
114
+ if (params.length === 0) {
115
+ return undefined;
116
+ }
117
+
118
+ return `Bearer ${params.join(", ")}`;
70
119
  };
71
120
 
72
121
  // Helper function to handle Response errors and send appropriate HTTP response
73
- const handleResponseError = (
122
+ const handleResponseError = async (
74
123
  error: unknown,
75
124
  res: http.ServerResponse,
76
- ): boolean => {
77
- if (error instanceof Response) {
125
+ ): Promise<boolean> => {
126
+ // Check if it's a Response-like object (duck typing)
127
+ // The instanceof check may fail due to different Response implementations across module boundaries
128
+ const isResponseLike = error &&
129
+ typeof error === 'object' &&
130
+ 'status' in error &&
131
+ 'headers' in error &&
132
+ 'statusText' in error;
133
+
134
+ if (isResponseLike || error instanceof Response) {
135
+ const responseError = error as Response;
136
+
137
+ // Convert Headers to http.OutgoingHttpHeaders format
78
138
  const fixedHeaders: http.OutgoingHttpHeaders = {};
79
- error.headers.forEach((value, key) => {
139
+ responseError.headers.forEach((value, key) => {
80
140
  if (fixedHeaders[key]) {
81
141
  if (Array.isArray(fixedHeaders[key])) {
82
142
  (fixedHeaders[key] as string[]).push(value);
@@ -87,11 +147,16 @@ const handleResponseError = (
87
147
  fixedHeaders[key] = value;
88
148
  }
89
149
  });
90
- res
91
- .writeHead(error.status, error.statusText, fixedHeaders)
92
- .end(error.statusText);
150
+
151
+ // Read the body from the Response object
152
+ const body = await responseError.text();
153
+
154
+ res.writeHead(responseError.status, responseError.statusText, fixedHeaders);
155
+ res.end(body);
156
+
93
157
  return true;
94
158
  }
159
+
95
160
  return false;
96
161
  };
97
162
 
@@ -260,7 +325,10 @@ const handleStreamRequest = async <T extends ServerLike>({
260
325
  res.setHeader("Content-Type", "application/json");
261
326
 
262
327
  // Add WWW-Authenticate header if OAuth config is available
263
- const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
328
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth, {
329
+ error: "invalid_token",
330
+ error_description: errorMessage,
331
+ });
264
332
  if (wwwAuthHeader) {
265
333
  res.setHeader("WWW-Authenticate", wwwAuthHeader);
266
334
  }
@@ -278,13 +346,21 @@ const handleStreamRequest = async <T extends ServerLike>({
278
346
  return true;
279
347
  }
280
348
  } catch (error) {
349
+ // Check if error is a Response object with headers already set
350
+ if (await handleResponseError(error, res)) {
351
+ return true;
352
+ }
353
+
281
354
  // Extract error details from thrown errors
282
355
  const errorMessage = error instanceof Error ? error.message : "Unauthorized: Authentication error";
283
356
  console.error("Authentication error:", error);
284
357
  res.setHeader("Content-Type", "application/json");
285
358
 
286
359
  // Add WWW-Authenticate header if OAuth config is available
287
- const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
360
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth, {
361
+ error: "invalid_token",
362
+ error_description: errorMessage,
363
+ });
288
364
  if (wwwAuthHeader) {
289
365
  res.setHeader("WWW-Authenticate", wwwAuthHeader);
290
366
  }
@@ -357,6 +433,11 @@ const handleStreamRequest = async <T extends ServerLike>({
357
433
  try {
358
434
  server = await createServer(req);
359
435
  } catch (error) {
436
+ // Check if error is a Response object with headers already set
437
+ if (await handleResponseError(error, res)) {
438
+ return true;
439
+ }
440
+
360
441
  // Detect authentication errors and return HTTP 401
361
442
  const errorMessage = error instanceof Error ? error.message : String(error);
362
443
  const isAuthError = errorMessage.includes('Authentication') ||
@@ -368,7 +449,10 @@ const handleStreamRequest = async <T extends ServerLike>({
368
449
  res.setHeader("Content-Type", "application/json");
369
450
 
370
451
  // Add WWW-Authenticate header if OAuth config is available
371
- const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
452
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth, {
453
+ error: "invalid_token",
454
+ error_description: errorMessage,
455
+ });
372
456
  if (wwwAuthHeader) {
373
457
  res.setHeader("WWW-Authenticate", wwwAuthHeader);
374
458
  }
@@ -384,10 +468,6 @@ const handleStreamRequest = async <T extends ServerLike>({
384
468
  return true;
385
469
  }
386
470
 
387
- if (handleResponseError(error, res)) {
388
- return true;
389
- }
390
-
391
471
  res.writeHead(500).end("Error creating server");
392
472
 
393
473
  return true;
@@ -416,6 +496,11 @@ const handleStreamRequest = async <T extends ServerLike>({
416
496
  try {
417
497
  server = await createServer(req);
418
498
  } catch (error) {
499
+ // Check if error is a Response object with headers already set
500
+ if (await handleResponseError(error, res)) {
501
+ return true;
502
+ }
503
+
419
504
  // Detect authentication errors and return HTTP 401
420
505
  const errorMessage = error instanceof Error ? error.message : String(error);
421
506
  const isAuthError = errorMessage.includes('Authentication') ||
@@ -427,7 +512,10 @@ const handleStreamRequest = async <T extends ServerLike>({
427
512
  res.setHeader("Content-Type", "application/json");
428
513
 
429
514
  // Add WWW-Authenticate header if OAuth config is available
430
- const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
515
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth, {
516
+ error: "invalid_token",
517
+ error_description: errorMessage,
518
+ });
431
519
  if (wwwAuthHeader) {
432
520
  res.setHeader("WWW-Authenticate", wwwAuthHeader);
433
521
  }
@@ -443,10 +531,6 @@ const handleStreamRequest = async <T extends ServerLike>({
443
531
  return true;
444
532
  }
445
533
 
446
- if (handleResponseError(error, res)) {
447
- return true;
448
- }
449
-
450
534
  res.writeHead(500).end("Error creating server");
451
535
 
452
536
  return true;
@@ -601,7 +685,7 @@ const handleSSERequest = async <T extends ServerLike>({
601
685
  try {
602
686
  server = await createServer(req);
603
687
  } catch (error) {
604
- if (handleResponseError(error, res)) {
688
+ if (await handleResponseError(error, res)) {
605
689
  return true;
606
690
  }
607
691