mcp-proxy 5.8.0 → 5.9.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.
@@ -8,7 +8,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
8
8
  import http from "http";
9
9
  import { randomUUID } from "node:crypto";
10
10
 
11
- import { AuthenticationMiddleware } from "./authentication.js";
11
+ import { AuthConfig, AuthenticationMiddleware } from "./authentication.js";
12
12
  import { InMemoryEventStore } from "./InMemoryEventStore.js";
13
13
 
14
14
  export type SSEServer = {
@@ -49,6 +49,17 @@ const createJsonRpcErrorResponse = (code: number, message: string) => {
49
49
  });
50
50
  };
51
51
 
52
+ // Helper function to get WWW-Authenticate header value
53
+ const getWWWAuthenticateHeader = (
54
+ oauth?: AuthConfig["oauth"],
55
+ ): string | undefined => {
56
+ if (!oauth?.protectedResource?.resource) {
57
+ return undefined;
58
+ }
59
+
60
+ return `Bearer resource_metadata="${oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`;
61
+ };
62
+
52
63
  // Helper function to handle Response errors and send appropriate HTTP response
53
64
  const handleResponseError = (
54
65
  error: unknown,
@@ -98,6 +109,7 @@ const handleStreamRequest = async <T extends ServerLike>({
98
109
  enableJsonResponse,
99
110
  endpoint,
100
111
  eventStore,
112
+ oauth,
101
113
  onClose,
102
114
  onConnect,
103
115
  req,
@@ -113,6 +125,7 @@ const handleStreamRequest = async <T extends ServerLike>({
113
125
  enableJsonResponse?: boolean;
114
126
  endpoint: string;
115
127
  eventStore?: EventStore;
128
+ oauth?: AuthConfig["oauth"];
116
129
  onClose?: (server: T) => Promise<void>;
117
130
  onConnect?: (server: T) => Promise<void>;
118
131
  req: http.IncomingMessage;
@@ -138,13 +151,28 @@ const handleStreamRequest = async <T extends ServerLike>({
138
151
  if (stateless && authenticate) {
139
152
  try {
140
153
  const authResult = await authenticate(req);
141
- if (!authResult) {
154
+
155
+ // Check for both falsy AND { authenticated: false } pattern
156
+ if (!authResult || (typeof authResult === 'object' && 'authenticated' in authResult && !authResult.authenticated)) {
157
+ // Extract error message if available
158
+ const errorMessage =
159
+ authResult && typeof authResult === 'object' && 'error' in authResult && typeof authResult.error === 'string'
160
+ ? authResult.error
161
+ : "Unauthorized: Authentication failed";
162
+
142
163
  res.setHeader("Content-Type", "application/json");
164
+
165
+ // Add WWW-Authenticate header if OAuth config is available
166
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
167
+ if (wwwAuthHeader) {
168
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
169
+ }
170
+
143
171
  res.writeHead(401).end(
144
172
  JSON.stringify({
145
173
  error: {
146
174
  code: -32000,
147
- message: "Unauthorized: Authentication failed"
175
+ message: errorMessage
148
176
  },
149
177
  id: (body as { id?: unknown })?.id ?? null,
150
178
  jsonrpc: "2.0"
@@ -153,13 +181,22 @@ const handleStreamRequest = async <T extends ServerLike>({
153
181
  return true;
154
182
  }
155
183
  } catch (error) {
184
+ // Extract error details from thrown errors
185
+ const errorMessage = error instanceof Error ? error.message : "Unauthorized: Authentication error";
156
186
  console.error("Authentication error:", error);
157
187
  res.setHeader("Content-Type", "application/json");
188
+
189
+ // Add WWW-Authenticate header if OAuth config is available
190
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
191
+ if (wwwAuthHeader) {
192
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
193
+ }
194
+
158
195
  res.writeHead(401).end(
159
196
  JSON.stringify({
160
197
  error: {
161
198
  code: -32000,
162
- message: "Unauthorized: Authentication error"
199
+ message: errorMessage
163
200
  },
164
201
  id: (body as { id?: unknown })?.id ?? null,
165
202
  jsonrpc: "2.0"
@@ -223,6 +260,33 @@ const handleStreamRequest = async <T extends ServerLike>({
223
260
  try {
224
261
  server = await createServer(req);
225
262
  } catch (error) {
263
+ // Detect authentication errors and return HTTP 401
264
+ const errorMessage = error instanceof Error ? error.message : String(error);
265
+ const isAuthError = errorMessage.includes('Authentication') ||
266
+ errorMessage.includes('Invalid JWT') ||
267
+ errorMessage.includes('Token') ||
268
+ errorMessage.includes('Unauthorized');
269
+
270
+ if (isAuthError) {
271
+ res.setHeader("Content-Type", "application/json");
272
+
273
+ // Add WWW-Authenticate header if OAuth config is available
274
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
275
+ if (wwwAuthHeader) {
276
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
277
+ }
278
+
279
+ res.writeHead(401).end(JSON.stringify({
280
+ error: {
281
+ code: -32000,
282
+ message: errorMessage
283
+ },
284
+ id: (body as { id?: unknown })?.id ?? null,
285
+ jsonrpc: "2.0"
286
+ }));
287
+ return true;
288
+ }
289
+
226
290
  if (handleResponseError(error, res)) {
227
291
  return true;
228
292
  }
@@ -255,6 +319,33 @@ const handleStreamRequest = async <T extends ServerLike>({
255
319
  try {
256
320
  server = await createServer(req);
257
321
  } catch (error) {
322
+ // Detect authentication errors and return HTTP 401
323
+ const errorMessage = error instanceof Error ? error.message : String(error);
324
+ const isAuthError = errorMessage.includes('Authentication') ||
325
+ errorMessage.includes('Invalid JWT') ||
326
+ errorMessage.includes('Token') ||
327
+ errorMessage.includes('Unauthorized');
328
+
329
+ if (isAuthError) {
330
+ res.setHeader("Content-Type", "application/json");
331
+
332
+ // Add WWW-Authenticate header if OAuth config is available
333
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
334
+ if (wwwAuthHeader) {
335
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
336
+ }
337
+
338
+ res.writeHead(401).end(JSON.stringify({
339
+ error: {
340
+ code: -32000,
341
+ message: errorMessage
342
+ },
343
+ id: (body as { id?: unknown })?.id ?? null,
344
+ jsonrpc: "2.0"
345
+ }));
346
+ return true;
347
+ }
348
+
258
349
  if (handleResponseError(error, res)) {
259
350
  return true;
260
351
  }
@@ -499,6 +590,7 @@ export const startHTTPServer = async <T extends ServerLike>({
499
590
  enableJsonResponse,
500
591
  eventStore,
501
592
  host = "::",
593
+ oauth,
502
594
  onClose,
503
595
  onConnect,
504
596
  onUnhandledRequest,
@@ -513,6 +605,7 @@ export const startHTTPServer = async <T extends ServerLike>({
513
605
  enableJsonResponse?: boolean;
514
606
  eventStore?: EventStore;
515
607
  host?: string;
608
+ oauth?: AuthConfig["oauth"];
516
609
  onClose?: (server: T) => Promise<void>;
517
610
  onConnect?: (server: T) => Promise<void>;
518
611
  onUnhandledRequest?: (
@@ -534,7 +627,7 @@ export const startHTTPServer = async <T extends ServerLike>({
534
627
  }
535
628
  > = {};
536
629
 
537
- const authMiddleware = new AuthenticationMiddleware({ apiKey });
630
+ const authMiddleware = new AuthenticationMiddleware({ apiKey, oauth });
538
631
 
539
632
  /**
540
633
  * @author https://dev.classmethod.jp/articles/mcp-sse/
@@ -597,6 +690,7 @@ export const startHTTPServer = async <T extends ServerLike>({
597
690
  enableJsonResponse,
598
691
  endpoint: streamEndpoint,
599
692
  eventStore,
693
+ oauth,
600
694
  onClose,
601
695
  onConnect,
602
696
  req,