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/dist/bin/mcp-proxy.js +1 -1
- package/dist/index.d.ts +23 -3
- package/dist/index.js +2 -2
- package/dist/{stdio-9KZaSDCW.js → stdio-CsjPjeWC.js} +50 -32
- package/dist/stdio-CsjPjeWC.js.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/authentication.test.ts +77 -2
- package/src/authentication.ts +16 -4
- package/src/index.ts +2 -0
- package/src/startHTTPServer.ts +46 -2
- package/dist/stdio-9KZaSDCW.js.map +0 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -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
|
});
|
package/src/authentication.ts
CHANGED
|
@@ -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";
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -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,
|