mcp-proxy 5.8.1 → 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.
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.8.1"
6
+ "version": "5.9.0"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "5.8.1",
3
+ "version": "5.9.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsdown",
@@ -103,15 +103,90 @@ describe("AuthenticationMiddleware", () => {
103
103
  expect(body.id).toBe(null);
104
104
  });
105
105
 
106
- it("should have consistent format regardless of configuration", () => {
106
+ it("should have consistent body format regardless of configuration", () => {
107
107
  const middleware1 = new AuthenticationMiddleware({});
108
108
  const middleware2 = new AuthenticationMiddleware({ apiKey: "test" });
109
109
 
110
110
  const response1 = middleware1.getUnauthorizedResponse();
111
111
  const response2 = middleware2.getUnauthorizedResponse();
112
112
 
113
- expect(response1.headers).toEqual(response2.headers);
114
113
  expect(response1.body).toEqual(response2.body);
115
114
  });
115
+
116
+ it("should not include WWW-Authenticate header without OAuth config", () => {
117
+ const middleware = new AuthenticationMiddleware({ apiKey: "test" });
118
+ const response = middleware.getUnauthorizedResponse();
119
+
120
+ expect(response.headers["WWW-Authenticate"]).toBeUndefined();
121
+ });
122
+
123
+ it("should include WWW-Authenticate header with OAuth config", () => {
124
+ const middleware = new AuthenticationMiddleware({
125
+ apiKey: "test",
126
+ oauth: {
127
+ protectedResource: {
128
+ resource: "https://example.com",
129
+ },
130
+ },
131
+ });
132
+ const response = middleware.getUnauthorizedResponse();
133
+
134
+ expect(response.headers["WWW-Authenticate"]).toBe(
135
+ 'Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"',
136
+ );
137
+ });
138
+
139
+ it("should handle OAuth config with trailing slash in resource URL", () => {
140
+ const middleware = new AuthenticationMiddleware({
141
+ apiKey: "test",
142
+ oauth: {
143
+ protectedResource: {
144
+ resource: "https://example.com/",
145
+ },
146
+ },
147
+ });
148
+ const response = middleware.getUnauthorizedResponse();
149
+
150
+ expect(response.headers["WWW-Authenticate"]).toBe(
151
+ 'Bearer resource_metadata="https://example.com//.well-known/oauth-protected-resource"',
152
+ );
153
+ });
154
+
155
+ it("should not include WWW-Authenticate header when OAuth config is empty", () => {
156
+ const middleware = new AuthenticationMiddleware({
157
+ apiKey: "test",
158
+ oauth: {},
159
+ });
160
+ const response = middleware.getUnauthorizedResponse();
161
+
162
+ expect(response.headers["WWW-Authenticate"]).toBeUndefined();
163
+ });
164
+
165
+ it("should not include WWW-Authenticate header when protectedResource is empty", () => {
166
+ const middleware = new AuthenticationMiddleware({
167
+ apiKey: "test",
168
+ oauth: {
169
+ protectedResource: {},
170
+ },
171
+ });
172
+ const response = middleware.getUnauthorizedResponse();
173
+
174
+ expect(response.headers["WWW-Authenticate"]).toBeUndefined();
175
+ });
176
+
177
+ it("should include WWW-Authenticate header with OAuth config but no apiKey", () => {
178
+ const middleware = new AuthenticationMiddleware({
179
+ oauth: {
180
+ protectedResource: {
181
+ resource: "https://example.com",
182
+ },
183
+ },
184
+ });
185
+ const response = middleware.getUnauthorizedResponse();
186
+
187
+ expect(response.headers["WWW-Authenticate"]).toBe(
188
+ 'Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"',
189
+ );
190
+ });
116
191
  });
117
192
  });
@@ -2,12 +2,26 @@ import type { IncomingMessage } from "http";
2
2
 
3
3
  export interface AuthConfig {
4
4
  apiKey?: string;
5
+ oauth?: {
6
+ protectedResource?: {
7
+ resource?: string;
8
+ };
9
+ };
5
10
  }
6
11
 
7
12
  export class AuthenticationMiddleware {
8
13
  constructor(private config: AuthConfig = {}) {}
9
14
 
10
- getUnauthorizedResponse() {
15
+ getUnauthorizedResponse(): { body: string; headers: Record<string, string> } {
16
+ const headers: Record<string, string> = {
17
+ "Content-Type": "application/json",
18
+ };
19
+
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"`;
23
+ }
24
+
11
25
  return {
12
26
  body: JSON.stringify({
13
27
  error: {
@@ -17,9 +31,7 @@ export class AuthenticationMiddleware {
17
31
  id: null,
18
32
  jsonrpc: "2.0",
19
33
  }),
20
- headers: {
21
- "Content-Type": "application/json",
22
- },
34
+ headers,
23
35
  };
24
36
  }
25
37
 
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export type { AuthConfig } from "./authentication.js";
2
+ export { AuthenticationMiddleware } from "./authentication.js";
1
3
  export { InMemoryEventStore } from "./InMemoryEventStore.js";
2
4
  export { proxyServer } from "./proxyServer.js";
3
5
  export { startHTTPServer } from "./startHTTPServer.js";
@@ -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;
@@ -148,6 +161,13 @@ const handleStreamRequest = async <T extends ServerLike>({
148
161
  : "Unauthorized: Authentication failed";
149
162
 
150
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
+
151
171
  res.writeHead(401).end(
152
172
  JSON.stringify({
153
173
  error: {
@@ -165,6 +185,13 @@ const handleStreamRequest = async <T extends ServerLike>({
165
185
  const errorMessage = error instanceof Error ? error.message : "Unauthorized: Authentication error";
166
186
  console.error("Authentication error:", error);
167
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
+
168
195
  res.writeHead(401).end(
169
196
  JSON.stringify({
170
197
  error: {
@@ -242,6 +269,13 @@ const handleStreamRequest = async <T extends ServerLike>({
242
269
 
243
270
  if (isAuthError) {
244
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
+
245
279
  res.writeHead(401).end(JSON.stringify({
246
280
  error: {
247
281
  code: -32000,
@@ -294,6 +328,13 @@ const handleStreamRequest = async <T extends ServerLike>({
294
328
 
295
329
  if (isAuthError) {
296
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
+
297
338
  res.writeHead(401).end(JSON.stringify({
298
339
  error: {
299
340
  code: -32000,
@@ -549,6 +590,7 @@ export const startHTTPServer = async <T extends ServerLike>({
549
590
  enableJsonResponse,
550
591
  eventStore,
551
592
  host = "::",
593
+ oauth,
552
594
  onClose,
553
595
  onConnect,
554
596
  onUnhandledRequest,
@@ -563,6 +605,7 @@ export const startHTTPServer = async <T extends ServerLike>({
563
605
  enableJsonResponse?: boolean;
564
606
  eventStore?: EventStore;
565
607
  host?: string;
608
+ oauth?: AuthConfig["oauth"];
566
609
  onClose?: (server: T) => Promise<void>;
567
610
  onConnect?: (server: T) => Promise<void>;
568
611
  onUnhandledRequest?: (
@@ -584,7 +627,7 @@ export const startHTTPServer = async <T extends ServerLike>({
584
627
  }
585
628
  > = {};
586
629
 
587
- const authMiddleware = new AuthenticationMiddleware({ apiKey });
630
+ const authMiddleware = new AuthenticationMiddleware({ apiKey, oauth });
588
631
 
589
632
  /**
590
633
  * @author https://dev.classmethod.jp/articles/mcp-sse/
@@ -647,6 +690,7 @@ export const startHTTPServer = async <T extends ServerLike>({
647
690
  enableJsonResponse,
648
691
  endpoint: streamEndpoint,
649
692
  eventStore,
693
+ oauth,
650
694
  onClose,
651
695
  onConnect,
652
696
  req,