mcp-proxy 5.8.1 → 5.10.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.10.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.10.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,5 +1,8 @@
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";
5
+ export type { CorsOptions } from "./startHTTPServer.js";
3
6
  export { startHTTPServer } from "./startHTTPServer.js";
4
7
  export { ServerType, startStdioServer } from "./startStdioServer.js";
5
8
  export { tapTransport } from "./tapTransport.js";
@@ -1675,3 +1675,251 @@ it("succeeds when authenticate returns { authenticated: true } in stateless mode
1675
1675
  await httpServer.close();
1676
1676
  await stdioClient.close();
1677
1677
  });
1678
+
1679
+ // CORS Configuration Tests
1680
+
1681
+ it("supports wildcard CORS headers", async () => {
1682
+ const port = await getRandomPort();
1683
+
1684
+ const httpServer = await startHTTPServer({
1685
+ cors: {
1686
+ allowedHeaders: "*",
1687
+ },
1688
+ createServer: async () => {
1689
+ const mcpServer = new Server(
1690
+ { name: "test", version: "1.0.0" },
1691
+ { capabilities: {} },
1692
+ );
1693
+ return mcpServer;
1694
+ },
1695
+ port,
1696
+ });
1697
+
1698
+ // Test OPTIONS request to verify CORS headers
1699
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1700
+ headers: {
1701
+ Origin: "https://example.com",
1702
+ },
1703
+ method: "OPTIONS",
1704
+ });
1705
+
1706
+ expect(response.status).toBe(204);
1707
+
1708
+ // Verify wildcard is used for allowed headers
1709
+ const allowedHeaders = response.headers.get("Access-Control-Allow-Headers");
1710
+ expect(allowedHeaders).toBe("*");
1711
+
1712
+ await httpServer.close();
1713
+ });
1714
+
1715
+ it("supports custom CORS headers array", async () => {
1716
+ const port = await getRandomPort();
1717
+
1718
+ const httpServer = await startHTTPServer({
1719
+ cors: {
1720
+ allowedHeaders: ["Content-Type", "X-Custom-Header", "X-API-Key"],
1721
+ },
1722
+ createServer: async () => {
1723
+ const mcpServer = new Server(
1724
+ { name: "test", version: "1.0.0" },
1725
+ { capabilities: {} },
1726
+ );
1727
+ return mcpServer;
1728
+ },
1729
+ port,
1730
+ });
1731
+
1732
+ // Test OPTIONS request to verify CORS headers
1733
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1734
+ headers: {
1735
+ Origin: "https://example.com",
1736
+ },
1737
+ method: "OPTIONS",
1738
+ });
1739
+
1740
+ expect(response.status).toBe(204);
1741
+
1742
+ // Verify custom headers are used
1743
+ const allowedHeaders = response.headers.get("Access-Control-Allow-Headers");
1744
+ expect(allowedHeaders).toBe("Content-Type, X-Custom-Header, X-API-Key");
1745
+
1746
+ await httpServer.close();
1747
+ });
1748
+
1749
+ it("supports origin validation with array", async () => {
1750
+ const port = await getRandomPort();
1751
+
1752
+ const httpServer = await startHTTPServer({
1753
+ cors: {
1754
+ origin: ["https://app.example.com", "https://admin.example.com"],
1755
+ },
1756
+ createServer: async () => {
1757
+ const mcpServer = new Server(
1758
+ { name: "test", version: "1.0.0" },
1759
+ { capabilities: {} },
1760
+ );
1761
+ return mcpServer;
1762
+ },
1763
+ port,
1764
+ });
1765
+
1766
+ // Test with allowed origin
1767
+ const response1 = await fetch(`http://localhost:${port}/mcp`, {
1768
+ headers: {
1769
+ Origin: "https://app.example.com",
1770
+ },
1771
+ method: "OPTIONS",
1772
+ });
1773
+
1774
+ expect(response1.status).toBe(204);
1775
+ expect(response1.headers.get("Access-Control-Allow-Origin")).toBe("https://app.example.com");
1776
+
1777
+ // Test with disallowed origin
1778
+ const response2 = await fetch(`http://localhost:${port}/mcp`, {
1779
+ headers: {
1780
+ Origin: "https://malicious.com",
1781
+ },
1782
+ method: "OPTIONS",
1783
+ });
1784
+
1785
+ expect(response2.status).toBe(204);
1786
+ expect(response2.headers.get("Access-Control-Allow-Origin")).toBeNull();
1787
+
1788
+ await httpServer.close();
1789
+ });
1790
+
1791
+ it("supports origin validation with function", async () => {
1792
+ const port = await getRandomPort();
1793
+
1794
+ const httpServer = await startHTTPServer({
1795
+ cors: {
1796
+ origin: (origin: string) => origin.endsWith(".example.com"),
1797
+ },
1798
+ createServer: async () => {
1799
+ const mcpServer = new Server(
1800
+ { name: "test", version: "1.0.0" },
1801
+ { capabilities: {} },
1802
+ );
1803
+ return mcpServer;
1804
+ },
1805
+ port,
1806
+ });
1807
+
1808
+ // Test with allowed origin
1809
+ const response1 = await fetch(`http://localhost:${port}/mcp`, {
1810
+ headers: {
1811
+ Origin: "https://subdomain.example.com",
1812
+ },
1813
+ method: "OPTIONS",
1814
+ });
1815
+
1816
+ expect(response1.status).toBe(204);
1817
+ expect(response1.headers.get("Access-Control-Allow-Origin")).toBe("https://subdomain.example.com");
1818
+
1819
+ // Test with disallowed origin
1820
+ const response2 = await fetch(`http://localhost:${port}/mcp`, {
1821
+ headers: {
1822
+ Origin: "https://malicious.com",
1823
+ },
1824
+ method: "OPTIONS",
1825
+ });
1826
+
1827
+ expect(response2.status).toBe(204);
1828
+ expect(response2.headers.get("Access-Control-Allow-Origin")).toBeNull();
1829
+
1830
+ await httpServer.close();
1831
+ });
1832
+
1833
+ it("disables CORS when cors: false", async () => {
1834
+ const port = await getRandomPort();
1835
+
1836
+ const httpServer = await startHTTPServer({
1837
+ cors: false,
1838
+ createServer: async () => {
1839
+ const mcpServer = new Server(
1840
+ { name: "test", version: "1.0.0" },
1841
+ { capabilities: {} },
1842
+ );
1843
+ return mcpServer;
1844
+ },
1845
+ port,
1846
+ });
1847
+
1848
+ // Test OPTIONS request - should not have CORS headers
1849
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1850
+ headers: {
1851
+ Origin: "https://example.com",
1852
+ },
1853
+ method: "OPTIONS",
1854
+ });
1855
+
1856
+ expect(response.status).toBe(204);
1857
+ expect(response.headers.get("Access-Control-Allow-Origin")).toBeNull();
1858
+ expect(response.headers.get("Access-Control-Allow-Headers")).toBeNull();
1859
+
1860
+ await httpServer.close();
1861
+ });
1862
+
1863
+ it("uses default CORS settings when cors: true", async () => {
1864
+ const port = await getRandomPort();
1865
+
1866
+ const httpServer = await startHTTPServer({
1867
+ cors: true,
1868
+ createServer: async () => {
1869
+ const mcpServer = new Server(
1870
+ { name: "test", version: "1.0.0" },
1871
+ { capabilities: {} },
1872
+ );
1873
+ return mcpServer;
1874
+ },
1875
+ port,
1876
+ });
1877
+
1878
+ // Test OPTIONS request to verify default CORS headers
1879
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1880
+ headers: {
1881
+ Origin: "https://example.com",
1882
+ },
1883
+ method: "OPTIONS",
1884
+ });
1885
+
1886
+ expect(response.status).toBe(204);
1887
+ expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
1888
+ expect(response.headers.get("Access-Control-Allow-Headers")).toBe("Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id");
1889
+ expect(response.headers.get("Access-Control-Allow-Credentials")).toBe("true");
1890
+
1891
+ await httpServer.close();
1892
+ });
1893
+
1894
+ it("supports custom methods and maxAge", async () => {
1895
+ const port = await getRandomPort();
1896
+
1897
+ const httpServer = await startHTTPServer({
1898
+ cors: {
1899
+ maxAge: 86400,
1900
+ methods: ["GET", "POST", "PUT", "DELETE"],
1901
+ },
1902
+ createServer: async () => {
1903
+ const mcpServer = new Server(
1904
+ { name: "test", version: "1.0.0" },
1905
+ { capabilities: {} },
1906
+ );
1907
+ return mcpServer;
1908
+ },
1909
+ port,
1910
+ });
1911
+
1912
+ // Test OPTIONS request to verify custom settings
1913
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1914
+ headers: {
1915
+ Origin: "https://example.com",
1916
+ },
1917
+ method: "OPTIONS",
1918
+ });
1919
+
1920
+ expect(response.status).toBe(204);
1921
+ expect(response.headers.get("Access-Control-Allow-Methods")).toBe("GET, POST, PUT, DELETE");
1922
+ expect(response.headers.get("Access-Control-Max-Age")).toBe("86400");
1923
+
1924
+ await httpServer.close();
1925
+ });
@@ -8,9 +8,18 @@ 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
+ export interface CorsOptions {
15
+ allowedHeaders?: string | string[]; // Allow string[] or '*' for wildcard
16
+ credentials?: boolean;
17
+ exposedHeaders?: string[];
18
+ maxAge?: number;
19
+ methods?: string[];
20
+ origin?: ((origin: string) => boolean) | string | string[];
21
+ }
22
+
14
23
  export type SSEServer = {
15
24
  close: () => Promise<void>;
16
25
  };
@@ -49,6 +58,17 @@ const createJsonRpcErrorResponse = (code: number, message: string) => {
49
58
  });
50
59
  };
51
60
 
61
+ // Helper function to get WWW-Authenticate header value
62
+ const getWWWAuthenticateHeader = (
63
+ oauth?: AuthConfig["oauth"],
64
+ ): string | undefined => {
65
+ if (!oauth?.protectedResource?.resource) {
66
+ return undefined;
67
+ }
68
+
69
+ return `Bearer resource_metadata="${oauth.protectedResource.resource}/.well-known/oauth-protected-resource"`;
70
+ };
71
+
52
72
  // Helper function to handle Response errors and send appropriate HTTP response
53
73
  const handleResponseError = (
54
74
  error: unknown,
@@ -91,6 +111,94 @@ const cleanupServer = async <T extends ServerLike>(
91
111
  }
92
112
  };
93
113
 
114
+ // Helper function to apply CORS headers
115
+ const applyCorsHeaders = (
116
+ req: http.IncomingMessage,
117
+ res: http.ServerResponse,
118
+ corsOptions?: boolean | CorsOptions,
119
+ ) => {
120
+ if (!req.headers.origin) {
121
+ return;
122
+ }
123
+
124
+ // Default CORS configuration for backward compatibility
125
+ const defaultCorsOptions: CorsOptions = {
126
+ allowedHeaders: "Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id",
127
+ credentials: true,
128
+ exposedHeaders: ["Mcp-Session-Id"],
129
+ methods: ["GET", "POST", "OPTIONS"],
130
+ origin: "*",
131
+ };
132
+
133
+ let finalCorsOptions: CorsOptions;
134
+
135
+ if (corsOptions === false) {
136
+ // CORS disabled
137
+ return;
138
+ } else if (corsOptions === true || corsOptions === undefined) {
139
+ // Use default CORS settings
140
+ finalCorsOptions = defaultCorsOptions;
141
+ } else {
142
+ // Merge user options with defaults
143
+ finalCorsOptions = {
144
+ ...defaultCorsOptions,
145
+ ...corsOptions,
146
+ };
147
+ }
148
+
149
+ try {
150
+ const origin = new URL(req.headers.origin);
151
+
152
+ // Handle origin
153
+ let allowedOrigin = "*";
154
+ if (finalCorsOptions.origin) {
155
+ if (typeof finalCorsOptions.origin === "string") {
156
+ allowedOrigin = finalCorsOptions.origin;
157
+ } else if (Array.isArray(finalCorsOptions.origin)) {
158
+ allowedOrigin = finalCorsOptions.origin.includes(origin.origin)
159
+ ? origin.origin
160
+ : "false";
161
+ } else if (typeof finalCorsOptions.origin === "function") {
162
+ allowedOrigin = finalCorsOptions.origin(origin.origin) ? origin.origin : "false";
163
+ }
164
+ }
165
+
166
+ if (allowedOrigin !== "false") {
167
+ res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
168
+ }
169
+
170
+ // Handle credentials
171
+ if (finalCorsOptions.credentials !== undefined) {
172
+ res.setHeader("Access-Control-Allow-Credentials", finalCorsOptions.credentials.toString());
173
+ }
174
+
175
+ // Handle methods
176
+ if (finalCorsOptions.methods) {
177
+ res.setHeader("Access-Control-Allow-Methods", finalCorsOptions.methods.join(", "));
178
+ }
179
+
180
+ // Handle allowed headers
181
+ if (finalCorsOptions.allowedHeaders) {
182
+ const allowedHeaders = typeof finalCorsOptions.allowedHeaders === "string"
183
+ ? finalCorsOptions.allowedHeaders
184
+ : finalCorsOptions.allowedHeaders.join(", ");
185
+ res.setHeader("Access-Control-Allow-Headers", allowedHeaders);
186
+ }
187
+
188
+ // Handle exposed headers
189
+ if (finalCorsOptions.exposedHeaders) {
190
+ res.setHeader("Access-Control-Expose-Headers", finalCorsOptions.exposedHeaders.join(", "));
191
+ }
192
+
193
+ // Handle max age
194
+ if (finalCorsOptions.maxAge !== undefined) {
195
+ res.setHeader("Access-Control-Max-Age", finalCorsOptions.maxAge.toString());
196
+ }
197
+ } catch (error) {
198
+ console.error("[mcp-proxy] error parsing origin", error);
199
+ }
200
+ };
201
+
94
202
  const handleStreamRequest = async <T extends ServerLike>({
95
203
  activeTransports,
96
204
  authenticate,
@@ -98,6 +206,7 @@ const handleStreamRequest = async <T extends ServerLike>({
98
206
  enableJsonResponse,
99
207
  endpoint,
100
208
  eventStore,
209
+ oauth,
101
210
  onClose,
102
211
  onConnect,
103
212
  req,
@@ -113,6 +222,7 @@ const handleStreamRequest = async <T extends ServerLike>({
113
222
  enableJsonResponse?: boolean;
114
223
  endpoint: string;
115
224
  eventStore?: EventStore;
225
+ oauth?: AuthConfig["oauth"];
116
226
  onClose?: (server: T) => Promise<void>;
117
227
  onConnect?: (server: T) => Promise<void>;
118
228
  req: http.IncomingMessage;
@@ -148,6 +258,13 @@ const handleStreamRequest = async <T extends ServerLike>({
148
258
  : "Unauthorized: Authentication failed";
149
259
 
150
260
  res.setHeader("Content-Type", "application/json");
261
+
262
+ // Add WWW-Authenticate header if OAuth config is available
263
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
264
+ if (wwwAuthHeader) {
265
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
266
+ }
267
+
151
268
  res.writeHead(401).end(
152
269
  JSON.stringify({
153
270
  error: {
@@ -165,6 +282,13 @@ const handleStreamRequest = async <T extends ServerLike>({
165
282
  const errorMessage = error instanceof Error ? error.message : "Unauthorized: Authentication error";
166
283
  console.error("Authentication error:", error);
167
284
  res.setHeader("Content-Type", "application/json");
285
+
286
+ // Add WWW-Authenticate header if OAuth config is available
287
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
288
+ if (wwwAuthHeader) {
289
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
290
+ }
291
+
168
292
  res.writeHead(401).end(
169
293
  JSON.stringify({
170
294
  error: {
@@ -242,6 +366,13 @@ const handleStreamRequest = async <T extends ServerLike>({
242
366
 
243
367
  if (isAuthError) {
244
368
  res.setHeader("Content-Type", "application/json");
369
+
370
+ // Add WWW-Authenticate header if OAuth config is available
371
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
372
+ if (wwwAuthHeader) {
373
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
374
+ }
375
+
245
376
  res.writeHead(401).end(JSON.stringify({
246
377
  error: {
247
378
  code: -32000,
@@ -294,6 +425,13 @@ const handleStreamRequest = async <T extends ServerLike>({
294
425
 
295
426
  if (isAuthError) {
296
427
  res.setHeader("Content-Type", "application/json");
428
+
429
+ // Add WWW-Authenticate header if OAuth config is available
430
+ const wwwAuthHeader = getWWWAuthenticateHeader(oauth);
431
+ if (wwwAuthHeader) {
432
+ res.setHeader("WWW-Authenticate", wwwAuthHeader);
433
+ }
434
+
297
435
  res.writeHead(401).end(JSON.stringify({
298
436
  error: {
299
437
  code: -32000,
@@ -545,10 +683,12 @@ const handleSSERequest = async <T extends ServerLike>({
545
683
  export const startHTTPServer = async <T extends ServerLike>({
546
684
  apiKey,
547
685
  authenticate,
686
+ cors,
548
687
  createServer,
549
688
  enableJsonResponse,
550
689
  eventStore,
551
690
  host = "::",
691
+ oauth,
552
692
  onClose,
553
693
  onConnect,
554
694
  onUnhandledRequest,
@@ -559,10 +699,12 @@ export const startHTTPServer = async <T extends ServerLike>({
559
699
  }: {
560
700
  apiKey?: string;
561
701
  authenticate?: (request: http.IncomingMessage) => Promise<unknown>;
702
+ cors?: boolean | CorsOptions;
562
703
  createServer: (request: http.IncomingMessage) => Promise<T>;
563
704
  enableJsonResponse?: boolean;
564
705
  eventStore?: EventStore;
565
706
  host?: string;
707
+ oauth?: AuthConfig["oauth"];
566
708
  onClose?: (server: T) => Promise<void>;
567
709
  onConnect?: (server: T) => Promise<void>;
568
710
  onUnhandledRequest?: (
@@ -584,25 +726,14 @@ export const startHTTPServer = async <T extends ServerLike>({
584
726
  }
585
727
  > = {};
586
728
 
587
- const authMiddleware = new AuthenticationMiddleware({ apiKey });
729
+ const authMiddleware = new AuthenticationMiddleware({ apiKey, oauth });
588
730
 
589
731
  /**
590
732
  * @author https://dev.classmethod.jp/articles/mcp-sse/
591
733
  */
592
734
  const httpServer = http.createServer(async (req, res) => {
593
- if (req.headers.origin) {
594
- try {
595
- const origin = new URL(req.headers.origin);
596
-
597
- res.setHeader("Access-Control-Allow-Origin", origin.origin);
598
- res.setHeader("Access-Control-Allow-Credentials", "true");
599
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
600
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id");
601
- res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
602
- } catch (error) {
603
- console.error("[mcp-proxy] error parsing origin", error);
604
- }
605
- }
735
+ // Apply CORS headers
736
+ applyCorsHeaders(req, res, cors);
606
737
 
607
738
  if (req.method === "OPTIONS") {
608
739
  res.writeHead(204);
@@ -647,6 +778,7 @@ export const startHTTPServer = async <T extends ServerLike>({
647
778
  enableJsonResponse,
648
779
  endpoint: streamEndpoint,
649
780
  eventStore,
781
+ oauth,
650
782
  onClose,
651
783
  onConnect,
652
784
  req,