mcp-proxy 5.12.0 → 5.12.2
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/README.md +71 -49
- package/dist/bin/{mcp-proxy.js → mcp-proxy.mjs} +44 -40
- package/dist/bin/mcp-proxy.mjs.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +1 -1
- package/dist/{index.js → index.mjs} +517 -296
- package/dist/index.mjs.map +1 -0
- package/dist/{stdio-CfAxSAGj.js → stdio-DBuYn6eo.mjs} +11591 -9307
- package/dist/stdio-DBuYn6eo.mjs.map +1 -0
- package/jsr.json +1 -1
- package/package.json +20 -20
- package/src/InMemoryEventStore.test.ts +58 -50
- package/src/InMemoryEventStore.ts +2 -3
- package/src/authentication.test.ts +91 -29
- package/src/authentication.ts +12 -5
- package/src/bin/mcp-proxy.ts +2 -1
- package/src/proxyServer.test.ts +40 -22
- package/src/startHTTPServer.test.ts +41 -23
- package/src/startHTTPServer.ts +93 -52
- package/src/startStdioServer.test.ts +8 -5
- package/dist/bin/mcp-proxy.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/stdio-CfAxSAGj.js.map +0 -1
- /package/dist/bin/{mcp-proxy.d.ts → mcp-proxy.d.mts} +0 -0
package/src/proxyServer.test.ts
CHANGED
|
@@ -28,33 +28,47 @@ interface TestEnvironment {
|
|
|
28
28
|
streamClient: Client;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async function createTestEnvironment(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
async function createTestEnvironment(
|
|
32
|
+
config: TestConfig = {},
|
|
33
|
+
): Promise<TestEnvironment> {
|
|
34
|
+
const {
|
|
35
|
+
requestTimeout,
|
|
36
|
+
serverDelay,
|
|
37
|
+
serverFixture = "simple-stdio-server.ts",
|
|
36
38
|
} = config;
|
|
37
|
-
|
|
39
|
+
|
|
38
40
|
const stdioTransport = new StdioClientTransport({
|
|
39
41
|
args: [`src/fixtures/${serverFixture}`],
|
|
40
42
|
command: "tsx",
|
|
41
|
-
env: serverDelay
|
|
43
|
+
env: serverDelay
|
|
44
|
+
? ({ ...process.env, RESPONSE_DELAY: serverDelay } as Record<
|
|
45
|
+
string,
|
|
46
|
+
string
|
|
47
|
+
>)
|
|
48
|
+
: (process.env as Record<string, string>),
|
|
42
49
|
});
|
|
43
50
|
|
|
44
51
|
const stdioClient = new Client(
|
|
45
52
|
{ name: "mcp-proxy-test", version: "1.0.0" },
|
|
46
|
-
{ capabilities: {} }
|
|
53
|
+
{ capabilities: {} },
|
|
47
54
|
);
|
|
48
55
|
|
|
49
56
|
await stdioClient.connect(stdioTransport);
|
|
50
57
|
|
|
51
|
-
const serverVersion = stdioClient.getServerVersion() as {
|
|
52
|
-
|
|
58
|
+
const serverVersion = stdioClient.getServerVersion() as {
|
|
59
|
+
name: string;
|
|
60
|
+
version: string;
|
|
61
|
+
};
|
|
62
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
63
|
+
capabilities: Record<string, unknown>;
|
|
64
|
+
};
|
|
53
65
|
const port = await getRandomPort();
|
|
54
66
|
|
|
55
67
|
const httpServer = await startHTTPServer({
|
|
56
68
|
createServer: async () => {
|
|
57
|
-
const mcpServer = new Server(serverVersion, {
|
|
69
|
+
const mcpServer = new Server(serverVersion, {
|
|
70
|
+
capabilities: serverCapabilities,
|
|
71
|
+
});
|
|
58
72
|
await proxyServer({
|
|
59
73
|
client: stdioClient,
|
|
60
74
|
requestTimeout,
|
|
@@ -68,20 +82,22 @@ async function createTestEnvironment(config: TestConfig = {}): Promise<TestEnvir
|
|
|
68
82
|
|
|
69
83
|
const streamClient = new Client(
|
|
70
84
|
{ name: "stream-client", version: "1.0.0" },
|
|
71
|
-
{ capabilities: {} }
|
|
85
|
+
{ capabilities: {} },
|
|
72
86
|
);
|
|
73
87
|
|
|
74
|
-
const transport = new StreamableHTTPClientTransport(
|
|
88
|
+
const transport = new StreamableHTTPClientTransport(
|
|
89
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
90
|
+
);
|
|
75
91
|
await streamClient.connect(transport);
|
|
76
92
|
|
|
77
|
-
return {
|
|
93
|
+
return {
|
|
78
94
|
cleanup: async () => {
|
|
79
95
|
await streamClient.close();
|
|
80
96
|
await stdioClient.close();
|
|
81
|
-
},
|
|
82
|
-
httpServer,
|
|
83
|
-
stdioClient,
|
|
84
|
-
streamClient
|
|
97
|
+
},
|
|
98
|
+
httpServer,
|
|
99
|
+
stdioClient,
|
|
100
|
+
streamClient,
|
|
85
101
|
};
|
|
86
102
|
}
|
|
87
103
|
|
|
@@ -90,7 +106,7 @@ describe("proxyServer timeout functionality", () => {
|
|
|
90
106
|
const { cleanup, streamClient } = await createTestEnvironment({
|
|
91
107
|
requestTimeout: 1000,
|
|
92
108
|
serverDelay: "500",
|
|
93
|
-
serverFixture: "slow-stdio-server.ts"
|
|
109
|
+
serverFixture: "slow-stdio-server.ts",
|
|
94
110
|
});
|
|
95
111
|
|
|
96
112
|
// This should succeed as timeout (1s) > delay (500ms)
|
|
@@ -105,7 +121,7 @@ describe("proxyServer timeout functionality", () => {
|
|
|
105
121
|
const { cleanup, streamClient } = await createTestEnvironment({
|
|
106
122
|
requestTimeout: 500,
|
|
107
123
|
serverDelay: "1000",
|
|
108
|
-
serverFixture: "slow-stdio-server.ts"
|
|
124
|
+
serverFixture: "slow-stdio-server.ts",
|
|
109
125
|
});
|
|
110
126
|
|
|
111
127
|
// This should throw a timeout error as delay (1s) > timeout (500ms)
|
|
@@ -128,7 +144,7 @@ describe("proxyServer timeout functionality", () => {
|
|
|
128
144
|
const { cleanup, streamClient } = await createTestEnvironment({
|
|
129
145
|
requestTimeout: 600,
|
|
130
146
|
serverDelay: "300",
|
|
131
|
-
serverFixture: "slow-stdio-server.ts"
|
|
147
|
+
serverFixture: "slow-stdio-server.ts",
|
|
132
148
|
});
|
|
133
149
|
|
|
134
150
|
// First get the resources
|
|
@@ -141,7 +157,9 @@ describe("proxyServer timeout functionality", () => {
|
|
|
141
157
|
});
|
|
142
158
|
|
|
143
159
|
expect(resourceContent.contents).toBeDefined();
|
|
144
|
-
expect(resourceContent.contents[0].text).toContain(
|
|
160
|
+
expect((resourceContent.contents[0] as { text: string }).text).toContain(
|
|
161
|
+
"300ms delay",
|
|
162
|
+
);
|
|
145
163
|
|
|
146
164
|
await cleanup();
|
|
147
165
|
}, 10000);
|
|
@@ -1339,8 +1339,8 @@ it("returns 401 with custom error message when { authenticated: false, error: '.
|
|
|
1339
1339
|
},
|
|
1340
1340
|
}),
|
|
1341
1341
|
headers: {
|
|
1342
|
-
|
|
1343
|
-
|
|
1342
|
+
Accept: "application/json, text/event-stream",
|
|
1343
|
+
Authorization: "Bearer expired-token",
|
|
1344
1344
|
"Content-Type": "application/json",
|
|
1345
1345
|
},
|
|
1346
1346
|
method: "POST",
|
|
@@ -1408,8 +1408,8 @@ it("returns 401 when createServer throws authentication error", async () => {
|
|
|
1408
1408
|
},
|
|
1409
1409
|
}),
|
|
1410
1410
|
headers: {
|
|
1411
|
-
|
|
1412
|
-
|
|
1411
|
+
Accept: "application/json, text/event-stream",
|
|
1412
|
+
Authorization: "Bearer test-token",
|
|
1413
1413
|
"Content-Type": "application/json",
|
|
1414
1414
|
},
|
|
1415
1415
|
method: "POST",
|
|
@@ -1451,7 +1451,7 @@ it("returns 401 when createServer throws JWT-related error", async () => {
|
|
|
1451
1451
|
},
|
|
1452
1452
|
}),
|
|
1453
1453
|
headers: {
|
|
1454
|
-
|
|
1454
|
+
Accept: "application/json, text/event-stream",
|
|
1455
1455
|
"Content-Type": "application/json",
|
|
1456
1456
|
},
|
|
1457
1457
|
method: "POST",
|
|
@@ -1492,7 +1492,7 @@ it("returns 401 when createServer throws Token-related error", async () => {
|
|
|
1492
1492
|
},
|
|
1493
1493
|
}),
|
|
1494
1494
|
headers: {
|
|
1495
|
-
|
|
1495
|
+
Accept: "application/json, text/event-stream",
|
|
1496
1496
|
"Content-Type": "application/json",
|
|
1497
1497
|
},
|
|
1498
1498
|
method: "POST",
|
|
@@ -1533,7 +1533,7 @@ it("returns 401 when createServer throws Unauthorized error", async () => {
|
|
|
1533
1533
|
},
|
|
1534
1534
|
}),
|
|
1535
1535
|
headers: {
|
|
1536
|
-
|
|
1536
|
+
Accept: "application/json, text/event-stream",
|
|
1537
1537
|
"Content-Type": "application/json",
|
|
1538
1538
|
},
|
|
1539
1539
|
method: "POST",
|
|
@@ -1574,7 +1574,7 @@ it("returns 500 when createServer throws non-auth error", async () => {
|
|
|
1574
1574
|
},
|
|
1575
1575
|
}),
|
|
1576
1576
|
headers: {
|
|
1577
|
-
|
|
1577
|
+
Accept: "application/json, text/event-stream",
|
|
1578
1578
|
"Content-Type": "application/json",
|
|
1579
1579
|
},
|
|
1580
1580
|
method: "POST",
|
|
@@ -1614,7 +1614,7 @@ it("includes WWW-Authenticate header in 401 response with OAuth config", async (
|
|
|
1614
1614
|
},
|
|
1615
1615
|
}),
|
|
1616
1616
|
headers: {
|
|
1617
|
-
|
|
1617
|
+
Accept: "application/json, text/event-stream",
|
|
1618
1618
|
"Content-Type": "application/json",
|
|
1619
1619
|
},
|
|
1620
1620
|
method: "POST",
|
|
@@ -1624,9 +1624,11 @@ it("includes WWW-Authenticate header in 401 response with OAuth config", async (
|
|
|
1624
1624
|
|
|
1625
1625
|
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
1626
1626
|
expect(wwwAuthHeader).toBeTruthy();
|
|
1627
|
-
expect(wwwAuthHeader).toContain(
|
|
1627
|
+
expect(wwwAuthHeader).toContain("Bearer");
|
|
1628
1628
|
expect(wwwAuthHeader).toContain('realm="mcp-server"');
|
|
1629
|
-
expect(wwwAuthHeader).toContain(
|
|
1629
|
+
expect(wwwAuthHeader).toContain(
|
|
1630
|
+
'resource_metadata="https://example.com/.well-known/oauth-protected-resource"',
|
|
1631
|
+
);
|
|
1630
1632
|
expect(wwwAuthHeader).toContain('error="invalid_token"');
|
|
1631
1633
|
expect(wwwAuthHeader).toContain('error_description="Invalid JWT token"');
|
|
1632
1634
|
|
|
@@ -1636,7 +1638,9 @@ it("includes WWW-Authenticate header in 401 response with OAuth config", async (
|
|
|
1636
1638
|
it("includes WWW-Authenticate header when authenticate callback fails with OAuth", async () => {
|
|
1637
1639
|
const port = await getRandomPort();
|
|
1638
1640
|
|
|
1639
|
-
const authenticate = vi
|
|
1641
|
+
const authenticate = vi
|
|
1642
|
+
.fn()
|
|
1643
|
+
.mockRejectedValue(new Error("Token signature verification failed"));
|
|
1640
1644
|
|
|
1641
1645
|
const httpServer = await startHTTPServer({
|
|
1642
1646
|
authenticate,
|
|
@@ -1670,8 +1674,8 @@ it("includes WWW-Authenticate header when authenticate callback fails with OAuth
|
|
|
1670
1674
|
},
|
|
1671
1675
|
}),
|
|
1672
1676
|
headers: {
|
|
1673
|
-
|
|
1674
|
-
|
|
1677
|
+
Accept: "application/json, text/event-stream",
|
|
1678
|
+
Authorization: "Bearer expired-token",
|
|
1675
1679
|
"Content-Type": "application/json",
|
|
1676
1680
|
},
|
|
1677
1681
|
method: "POST",
|
|
@@ -1682,12 +1686,18 @@ it("includes WWW-Authenticate header when authenticate callback fails with OAuth
|
|
|
1682
1686
|
|
|
1683
1687
|
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
1684
1688
|
expect(wwwAuthHeader).toBeTruthy();
|
|
1685
|
-
expect(wwwAuthHeader).toContain(
|
|
1689
|
+
expect(wwwAuthHeader).toContain("Bearer");
|
|
1686
1690
|
expect(wwwAuthHeader).toContain('realm="example-api"');
|
|
1687
|
-
expect(wwwAuthHeader).toContain(
|
|
1691
|
+
expect(wwwAuthHeader).toContain(
|
|
1692
|
+
'resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"',
|
|
1693
|
+
);
|
|
1688
1694
|
expect(wwwAuthHeader).toContain('error="invalid_token"');
|
|
1689
|
-
expect(wwwAuthHeader).toContain(
|
|
1690
|
-
|
|
1695
|
+
expect(wwwAuthHeader).toContain(
|
|
1696
|
+
'error_description="Token signature verification failed"',
|
|
1697
|
+
);
|
|
1698
|
+
expect(wwwAuthHeader).toContain(
|
|
1699
|
+
'error_uri="https://example.com/docs/errors"',
|
|
1700
|
+
);
|
|
1691
1701
|
|
|
1692
1702
|
await httpServer.close();
|
|
1693
1703
|
});
|
|
@@ -1715,7 +1725,7 @@ it("does not include WWW-Authenticate header in 401 response without OAuth confi
|
|
|
1715
1725
|
},
|
|
1716
1726
|
}),
|
|
1717
1727
|
headers: {
|
|
1718
|
-
|
|
1728
|
+
Accept: "application/json, text/event-stream",
|
|
1719
1729
|
"Content-Type": "application/json",
|
|
1720
1730
|
},
|
|
1721
1731
|
method: "POST",
|
|
@@ -1916,7 +1926,9 @@ it("supports origin validation with array", async () => {
|
|
|
1916
1926
|
});
|
|
1917
1927
|
|
|
1918
1928
|
expect(response1.status).toBe(204);
|
|
1919
|
-
expect(response1.headers.get("Access-Control-Allow-Origin")).toBe(
|
|
1929
|
+
expect(response1.headers.get("Access-Control-Allow-Origin")).toBe(
|
|
1930
|
+
"https://app.example.com",
|
|
1931
|
+
);
|
|
1920
1932
|
|
|
1921
1933
|
// Test with disallowed origin
|
|
1922
1934
|
const response2 = await fetch(`http://localhost:${port}/mcp`, {
|
|
@@ -1958,7 +1970,9 @@ it("supports origin validation with function", async () => {
|
|
|
1958
1970
|
});
|
|
1959
1971
|
|
|
1960
1972
|
expect(response1.status).toBe(204);
|
|
1961
|
-
expect(response1.headers.get("Access-Control-Allow-Origin")).toBe(
|
|
1973
|
+
expect(response1.headers.get("Access-Control-Allow-Origin")).toBe(
|
|
1974
|
+
"https://subdomain.example.com",
|
|
1975
|
+
);
|
|
1962
1976
|
|
|
1963
1977
|
// Test with disallowed origin
|
|
1964
1978
|
const response2 = await fetch(`http://localhost:${port}/mcp`, {
|
|
@@ -2029,7 +2043,9 @@ it("uses default CORS settings when cors: true", async () => {
|
|
|
2029
2043
|
|
|
2030
2044
|
expect(response.status).toBe(204);
|
|
2031
2045
|
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
|
2032
|
-
expect(response.headers.get("Access-Control-Allow-Headers")).toBe(
|
|
2046
|
+
expect(response.headers.get("Access-Control-Allow-Headers")).toBe(
|
|
2047
|
+
"Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id",
|
|
2048
|
+
);
|
|
2033
2049
|
expect(response.headers.get("Access-Control-Allow-Credentials")).toBe("true");
|
|
2034
2050
|
|
|
2035
2051
|
await httpServer.close();
|
|
@@ -2062,7 +2078,9 @@ it("supports custom methods and maxAge", async () => {
|
|
|
2062
2078
|
});
|
|
2063
2079
|
|
|
2064
2080
|
expect(response.status).toBe(204);
|
|
2065
|
-
expect(response.headers.get("Access-Control-Allow-Methods")).toBe(
|
|
2081
|
+
expect(response.headers.get("Access-Control-Allow-Methods")).toBe(
|
|
2082
|
+
"GET, POST, PUT, DELETE",
|
|
2083
|
+
);
|
|
2066
2084
|
expect(response.headers.get("Access-Control-Max-Age")).toBe("86400");
|
|
2067
2085
|
|
|
2068
2086
|
await httpServer.close();
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -81,7 +81,9 @@ const getWWWAuthenticateHeader = (
|
|
|
81
81
|
|
|
82
82
|
// Add resource_metadata if configured
|
|
83
83
|
if (oauth.protectedResource?.resource) {
|
|
84
|
-
params.push(
|
|
84
|
+
params.push(
|
|
85
|
+
`resource_metadata="${oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`,
|
|
86
|
+
);
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
// Add error from options or config (options takes precedence)
|
|
@@ -91,7 +93,8 @@ const getWWWAuthenticateHeader = (
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// Add error_description from options or config (options takes precedence)
|
|
94
|
-
const error_description =
|
|
96
|
+
const error_description =
|
|
97
|
+
options?.error_description || oauth.error_description;
|
|
95
98
|
if (error_description) {
|
|
96
99
|
// Escape quotes in error description
|
|
97
100
|
const escaped = error_description.replace(/"/g, '\\"');
|
|
@@ -119,7 +122,9 @@ const getWWWAuthenticateHeader = (
|
|
|
119
122
|
};
|
|
120
123
|
|
|
121
124
|
// Helper function to detect scope challenge errors
|
|
122
|
-
const isScopeChallengeError = (
|
|
125
|
+
const isScopeChallengeError = (
|
|
126
|
+
error: unknown,
|
|
127
|
+
): error is {
|
|
123
128
|
data: {
|
|
124
129
|
error: string;
|
|
125
130
|
errorDescription?: string;
|
|
@@ -147,11 +152,12 @@ const handleResponseError = async (
|
|
|
147
152
|
): Promise<boolean> => {
|
|
148
153
|
// Check if it's a Response-like object (duck typing)
|
|
149
154
|
// The instanceof check may fail due to different Response implementations across module boundaries
|
|
150
|
-
const isResponseLike =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
const isResponseLike =
|
|
156
|
+
error &&
|
|
157
|
+
typeof error === "object" &&
|
|
158
|
+
"status" in error &&
|
|
159
|
+
"headers" in error &&
|
|
160
|
+
"statusText" in error;
|
|
155
161
|
|
|
156
162
|
if (isResponseLike || error instanceof Response) {
|
|
157
163
|
const responseError = error as Response;
|
|
@@ -210,7 +216,8 @@ const applyCorsHeaders = (
|
|
|
210
216
|
|
|
211
217
|
// Default CORS configuration for backward compatibility
|
|
212
218
|
const defaultCorsOptions: CorsOptions = {
|
|
213
|
-
allowedHeaders:
|
|
219
|
+
allowedHeaders:
|
|
220
|
+
"Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id",
|
|
214
221
|
credentials: true,
|
|
215
222
|
exposedHeaders: ["Mcp-Session-Id"],
|
|
216
223
|
methods: ["GET", "POST", "OPTIONS"],
|
|
@@ -246,7 +253,9 @@ const applyCorsHeaders = (
|
|
|
246
253
|
? origin.origin
|
|
247
254
|
: "false";
|
|
248
255
|
} else if (typeof finalCorsOptions.origin === "function") {
|
|
249
|
-
allowedOrigin = finalCorsOptions.origin(origin.origin)
|
|
256
|
+
allowedOrigin = finalCorsOptions.origin(origin.origin)
|
|
257
|
+
? origin.origin
|
|
258
|
+
: "false";
|
|
250
259
|
}
|
|
251
260
|
}
|
|
252
261
|
|
|
@@ -256,30 +265,43 @@ const applyCorsHeaders = (
|
|
|
256
265
|
|
|
257
266
|
// Handle credentials
|
|
258
267
|
if (finalCorsOptions.credentials !== undefined) {
|
|
259
|
-
res.setHeader(
|
|
268
|
+
res.setHeader(
|
|
269
|
+
"Access-Control-Allow-Credentials",
|
|
270
|
+
finalCorsOptions.credentials.toString(),
|
|
271
|
+
);
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
// Handle methods
|
|
263
275
|
if (finalCorsOptions.methods) {
|
|
264
|
-
res.setHeader(
|
|
276
|
+
res.setHeader(
|
|
277
|
+
"Access-Control-Allow-Methods",
|
|
278
|
+
finalCorsOptions.methods.join(", "),
|
|
279
|
+
);
|
|
265
280
|
}
|
|
266
281
|
|
|
267
282
|
// Handle allowed headers
|
|
268
283
|
if (finalCorsOptions.allowedHeaders) {
|
|
269
|
-
const allowedHeaders =
|
|
270
|
-
|
|
271
|
-
|
|
284
|
+
const allowedHeaders =
|
|
285
|
+
typeof finalCorsOptions.allowedHeaders === "string"
|
|
286
|
+
? finalCorsOptions.allowedHeaders
|
|
287
|
+
: finalCorsOptions.allowedHeaders.join(", ");
|
|
272
288
|
res.setHeader("Access-Control-Allow-Headers", allowedHeaders);
|
|
273
289
|
}
|
|
274
290
|
|
|
275
291
|
// Handle exposed headers
|
|
276
292
|
if (finalCorsOptions.exposedHeaders) {
|
|
277
|
-
res.setHeader(
|
|
293
|
+
res.setHeader(
|
|
294
|
+
"Access-Control-Expose-Headers",
|
|
295
|
+
finalCorsOptions.exposedHeaders.join(", "),
|
|
296
|
+
);
|
|
278
297
|
}
|
|
279
298
|
|
|
280
299
|
// Handle max age
|
|
281
300
|
if (finalCorsOptions.maxAge !== undefined) {
|
|
282
|
-
res.setHeader(
|
|
301
|
+
res.setHeader(
|
|
302
|
+
"Access-Control-Max-Age",
|
|
303
|
+
finalCorsOptions.maxAge.toString(),
|
|
304
|
+
);
|
|
283
305
|
}
|
|
284
306
|
} catch (error) {
|
|
285
307
|
console.error("[mcp-proxy] error parsing origin", error);
|
|
@@ -340,10 +362,18 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
340
362
|
const authResult = await authenticate(req);
|
|
341
363
|
|
|
342
364
|
// Check for both falsy AND { authenticated: false } pattern
|
|
343
|
-
if (
|
|
365
|
+
if (
|
|
366
|
+
!authResult ||
|
|
367
|
+
(typeof authResult === "object" &&
|
|
368
|
+
"authenticated" in authResult &&
|
|
369
|
+
!authResult.authenticated)
|
|
370
|
+
) {
|
|
344
371
|
// Extract error message if available
|
|
345
372
|
const errorMessage =
|
|
346
|
-
authResult &&
|
|
373
|
+
authResult &&
|
|
374
|
+
typeof authResult === "object" &&
|
|
375
|
+
"error" in authResult &&
|
|
376
|
+
typeof authResult.error === "string"
|
|
347
377
|
? authResult.error
|
|
348
378
|
: "Unauthorized: Authentication failed";
|
|
349
379
|
|
|
@@ -362,11 +392,11 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
362
392
|
JSON.stringify({
|
|
363
393
|
error: {
|
|
364
394
|
code: -32000,
|
|
365
|
-
message: errorMessage
|
|
395
|
+
message: errorMessage,
|
|
366
396
|
},
|
|
367
397
|
id: (body as { id?: unknown })?.id ?? null,
|
|
368
|
-
jsonrpc: "2.0"
|
|
369
|
-
})
|
|
398
|
+
jsonrpc: "2.0",
|
|
399
|
+
}),
|
|
370
400
|
);
|
|
371
401
|
return true;
|
|
372
402
|
}
|
|
@@ -377,7 +407,10 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
377
407
|
}
|
|
378
408
|
|
|
379
409
|
// Extract error details from thrown errors
|
|
380
|
-
const errorMessage =
|
|
410
|
+
const errorMessage =
|
|
411
|
+
error instanceof Error
|
|
412
|
+
? error.message
|
|
413
|
+
: "Unauthorized: Authentication error";
|
|
381
414
|
console.error("Authentication error:", error);
|
|
382
415
|
res.setHeader("Content-Type", "application/json");
|
|
383
416
|
|
|
@@ -394,11 +427,11 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
394
427
|
JSON.stringify({
|
|
395
428
|
error: {
|
|
396
429
|
code: -32000,
|
|
397
|
-
message: errorMessage
|
|
430
|
+
message: errorMessage,
|
|
398
431
|
},
|
|
399
432
|
id: (body as { id?: unknown })?.id ?? null,
|
|
400
|
-
jsonrpc: "2.0"
|
|
401
|
-
})
|
|
433
|
+
jsonrpc: "2.0",
|
|
434
|
+
}),
|
|
402
435
|
);
|
|
403
436
|
return true;
|
|
404
437
|
}
|
|
@@ -464,11 +497,13 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
464
497
|
}
|
|
465
498
|
|
|
466
499
|
// Detect authentication errors and return HTTP 401
|
|
467
|
-
const errorMessage =
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
500
|
+
const errorMessage =
|
|
501
|
+
error instanceof Error ? error.message : String(error);
|
|
502
|
+
const isAuthError =
|
|
503
|
+
errorMessage.includes("Authentication") ||
|
|
504
|
+
errorMessage.includes("Invalid JWT") ||
|
|
505
|
+
errorMessage.includes("Token") ||
|
|
506
|
+
errorMessage.includes("Unauthorized");
|
|
472
507
|
|
|
473
508
|
if (isAuthError) {
|
|
474
509
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -482,14 +517,16 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
482
517
|
res.setHeader("WWW-Authenticate", wwwAuthHeader);
|
|
483
518
|
}
|
|
484
519
|
|
|
485
|
-
res.writeHead(401).end(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
520
|
+
res.writeHead(401).end(
|
|
521
|
+
JSON.stringify({
|
|
522
|
+
error: {
|
|
523
|
+
code: -32000,
|
|
524
|
+
message: errorMessage,
|
|
525
|
+
},
|
|
526
|
+
id: (body as { id?: unknown })?.id ?? null,
|
|
527
|
+
jsonrpc: "2.0",
|
|
528
|
+
}),
|
|
529
|
+
);
|
|
493
530
|
return true;
|
|
494
531
|
}
|
|
495
532
|
|
|
@@ -527,11 +564,13 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
527
564
|
}
|
|
528
565
|
|
|
529
566
|
// Detect authentication errors and return HTTP 401
|
|
530
|
-
const errorMessage =
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
567
|
+
const errorMessage =
|
|
568
|
+
error instanceof Error ? error.message : String(error);
|
|
569
|
+
const isAuthError =
|
|
570
|
+
errorMessage.includes("Authentication") ||
|
|
571
|
+
errorMessage.includes("Invalid JWT") ||
|
|
572
|
+
errorMessage.includes("Token") ||
|
|
573
|
+
errorMessage.includes("Unauthorized");
|
|
535
574
|
|
|
536
575
|
if (isAuthError) {
|
|
537
576
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -545,14 +584,16 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
545
584
|
res.setHeader("WWW-Authenticate", wwwAuthHeader);
|
|
546
585
|
}
|
|
547
586
|
|
|
548
|
-
res.writeHead(401).end(
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
587
|
+
res.writeHead(401).end(
|
|
588
|
+
JSON.stringify({
|
|
589
|
+
error: {
|
|
590
|
+
code: -32000,
|
|
591
|
+
message: errorMessage,
|
|
592
|
+
},
|
|
593
|
+
id: (body as { id?: unknown })?.id ?? null,
|
|
594
|
+
jsonrpc: "2.0",
|
|
595
|
+
}),
|
|
596
|
+
);
|
|
556
597
|
return true;
|
|
557
598
|
}
|
|
558
599
|
|
|
@@ -19,9 +19,8 @@ describe("startStdioServer.test.ts", () => {
|
|
|
19
19
|
let proc: ChildProcess;
|
|
20
20
|
|
|
21
21
|
beforeEach(async () => {
|
|
22
|
-
const serverPath =
|
|
23
|
-
"@modelcontextprotocol/sdk/examples/server/sseAndStreamableHttpCompatibleServer.js"
|
|
24
|
-
);
|
|
22
|
+
const serverPath =
|
|
23
|
+
require.resolve("@modelcontextprotocol/sdk/examples/server/sseAndStreamableHttpCompatibleServer.js");
|
|
25
24
|
proc = fork(serverPath, [], {
|
|
26
25
|
stdio: "pipe",
|
|
27
26
|
});
|
|
@@ -83,9 +82,11 @@ describe("startStdioServer.test.ts", () => {
|
|
|
83
82
|
{
|
|
84
83
|
description:
|
|
85
84
|
"Starts sending periodic notifications for testing resumability",
|
|
85
|
+
execution: {
|
|
86
|
+
taskSupport: "forbidden",
|
|
87
|
+
},
|
|
86
88
|
inputSchema: {
|
|
87
89
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
88
|
-
additionalProperties: false,
|
|
89
90
|
properties: {
|
|
90
91
|
count: {
|
|
91
92
|
default: 50,
|
|
@@ -177,9 +178,11 @@ describe("startStdioServer.test.ts", () => {
|
|
|
177
178
|
{
|
|
178
179
|
description:
|
|
179
180
|
"Starts sending periodic notifications for testing resumability",
|
|
181
|
+
execution: {
|
|
182
|
+
taskSupport: "forbidden",
|
|
183
|
+
},
|
|
180
184
|
inputSchema: {
|
|
181
185
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
182
|
-
additionalProperties: false,
|
|
183
186
|
properties: {
|
|
184
187
|
count: {
|
|
185
188
|
default: 50,
|