mcp-proxy 5.9.0 → 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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  A TypeScript streamable HTTP and SSE proxy for [MCP](https://modelcontextprotocol.io/) servers that use `stdio` transport.
4
4
 
5
5
  > [!NOTE]
6
- > CORS is enabled by default.
6
+ > CORS is enabled by default with configurable options. See [CORS Configuration](#cors-configuration) for details.
7
7
 
8
8
  > [!NOTE]
9
9
  > For a Python implementation, see [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy).
@@ -143,6 +143,152 @@ The following endpoints do not require authentication:
143
143
  - **Generate strong keys**: Use cryptographically secure random strings for API keys
144
144
  - **Rotate keys regularly**: Change API keys periodically for better security
145
145
 
146
+ ### CORS Configuration
147
+
148
+ MCP Proxy provides flexible CORS (Cross-Origin Resource Sharing) configuration to control how browsers can access your MCP server from different origins.
149
+
150
+ #### Default Behavior
151
+
152
+ By default, CORS is enabled with the following settings:
153
+ - **Origin**: `*` (allow all origins)
154
+ - **Methods**: `GET, POST, OPTIONS`
155
+ - **Headers**: `Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id`
156
+ - **Credentials**: `true`
157
+ - **Exposed Headers**: `Mcp-Session-Id`
158
+
159
+ #### Basic Configuration
160
+
161
+ ```typescript
162
+ import { startHTTPServer } from 'mcp-proxy';
163
+
164
+ // Use default CORS settings (backward compatible)
165
+ await startHTTPServer({
166
+ createServer: async () => { /* ... */ },
167
+ port: 3000,
168
+ });
169
+
170
+ // Explicitly enable default CORS
171
+ await startHTTPServer({
172
+ createServer: async () => { /* ... */ },
173
+ port: 3000,
174
+ cors: true,
175
+ });
176
+
177
+ // Disable CORS completely
178
+ await startHTTPServer({
179
+ createServer: async () => { /* ... */ },
180
+ port: 3000,
181
+ cors: false,
182
+ });
183
+ ```
184
+
185
+ #### Advanced CORS Configuration
186
+
187
+ For more control over CORS behavior, you can provide a detailed configuration:
188
+
189
+ ```typescript
190
+ import { startHTTPServer, CorsOptions } from 'mcp-proxy';
191
+
192
+ const corsOptions: CorsOptions = {
193
+ // Allow specific origins
194
+ origin: ['https://app.example.com', 'https://admin.example.com'],
195
+
196
+ // Or use a function for dynamic origin validation
197
+ origin: (origin: string) => origin.endsWith('.example.com'),
198
+
199
+ // Specify allowed methods
200
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
201
+
202
+ // Allow any headers (useful for browser clients with custom headers)
203
+ allowedHeaders: '*',
204
+
205
+ // Or specify exact headers
206
+ allowedHeaders: [
207
+ 'Content-Type',
208
+ 'Authorization',
209
+ 'Accept',
210
+ 'Mcp-Session-Id',
211
+ 'Last-Event-Id',
212
+ 'X-Custom-Header',
213
+ 'X-API-Key'
214
+ ],
215
+
216
+ // Headers to expose to the client
217
+ exposedHeaders: ['Mcp-Session-Id', 'X-Total-Count'],
218
+
219
+ // Allow credentials
220
+ credentials: true,
221
+
222
+ // Cache preflight requests for 24 hours
223
+ maxAge: 86400,
224
+ };
225
+
226
+ await startHTTPServer({
227
+ createServer: async () => { /* ... */ },
228
+ port: 3000,
229
+ cors: corsOptions,
230
+ });
231
+ ```
232
+
233
+ #### Common Use Cases
234
+
235
+ **Allow any custom headers (solves browser CORS issues):**
236
+ ```typescript
237
+ await startHTTPServer({
238
+ createServer: async () => { /* ... */ },
239
+ port: 3000,
240
+ cors: {
241
+ allowedHeaders: '*', // Allows X-Custom-Header, X-API-Key, etc.
242
+ },
243
+ });
244
+ ```
245
+
246
+ **Restrict to specific domains:**
247
+ ```typescript
248
+ await startHTTPServer({
249
+ createServer: async () => { /* ... */ },
250
+ port: 3000,
251
+ cors: {
252
+ origin: ['https://myapp.com', 'https://admin.myapp.com'],
253
+ allowedHeaders: '*',
254
+ },
255
+ });
256
+ ```
257
+
258
+ **Development-friendly settings:**
259
+ ```typescript
260
+ await startHTTPServer({
261
+ createServer: async () => { /* ... */ },
262
+ port: 3000,
263
+ cors: {
264
+ origin: ['http://localhost:3000', 'http://localhost:5173'], // Common dev ports
265
+ allowedHeaders: '*',
266
+ credentials: true,
267
+ },
268
+ });
269
+ ```
270
+
271
+ #### Migration from Older Versions
272
+
273
+ If you were using mcp-proxy 5.5.6 and want the same permissive behavior in 5.9.0+:
274
+
275
+ ```typescript
276
+ // Old behavior (5.5.6) - automatic wildcard headers
277
+ await startHTTPServer({
278
+ createServer: async () => { /* ... */ },
279
+ port: 3000,
280
+ });
281
+
282
+ // New equivalent (5.9.0+) - explicit wildcard headers
283
+ await startHTTPServer({
284
+ createServer: async () => { /* ... */ },
285
+ port: 3000,
286
+ cors: {
287
+ allowedHeaders: '*',
288
+ },
289
+ });
290
+ ```
291
+
146
292
  ### Node.js SDK
147
293
 
148
294
  The Node.js SDK provides several utilities that are used to create a proxy.
@@ -198,6 +344,7 @@ Options:
198
344
  - `streamEndpoint`: Streamable HTTP endpoint path (default: "/mcp", set to null to disable)
199
345
  - `stateless`: Enable stateless mode for HTTP streamable transport (default: false)
200
346
  - `apiKey`: API key for authenticating requests (optional)
347
+ - `cors`: CORS configuration (default: enabled with permissive settings, see CORS Configuration section)
201
348
  - `onConnect`: Callback when a server connects (optional)
202
349
  - `onClose`: Callback when a server disconnects (optional)
203
350
  - `onUnhandledRequest`: Callback for unhandled HTTP requests (optional)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Client, InMemoryEventStore, ReadBuffer, Server, __commonJS, __toESM, proxyServer, serializeMessage, startHTTPServer } from "../stdio-CsjPjeWC.js";
2
+ import { Client, InMemoryEventStore, ReadBuffer, Server, __commonJS, __toESM, proxyServer, serializeMessage, startHTTPServer } from "../stdio-DF5lH8jj.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
5
5
  import { format, inspect } from "util";
package/dist/index.d.ts CHANGED
@@ -72,6 +72,14 @@ declare const proxyServer: ({
72
72
  }) => Promise<void>;
73
73
  //#endregion
74
74
  //#region src/startHTTPServer.d.ts
75
+ interface CorsOptions {
76
+ allowedHeaders?: string | string[];
77
+ credentials?: boolean;
78
+ exposedHeaders?: string[];
79
+ maxAge?: number;
80
+ methods?: string[];
81
+ origin?: ((origin: string) => boolean) | string | string[];
82
+ }
75
83
  type SSEServer = {
76
84
  close: () => Promise<void>;
77
85
  };
@@ -82,6 +90,7 @@ type ServerLike = {
82
90
  declare const startHTTPServer: <T extends ServerLike>({
83
91
  apiKey,
84
92
  authenticate,
93
+ cors,
85
94
  createServer,
86
95
  enableJsonResponse,
87
96
  eventStore,
@@ -97,6 +106,7 @@ declare const startHTTPServer: <T extends ServerLike>({
97
106
  }: {
98
107
  apiKey?: string;
99
108
  authenticate?: (request: http.IncomingMessage) => Promise<unknown>;
109
+ cors?: boolean | CorsOptions;
100
110
  createServer: (request: http.IncomingMessage) => Promise<T>;
101
111
  enableJsonResponse?: boolean;
102
112
  eventStore?: EventStore;
@@ -149,5 +159,5 @@ type TransportEvent = {
149
159
  };
150
160
  declare const tapTransport: (transport: Transport, eventHandler: (event: TransportEvent) => void) => Transport;
151
161
  //#endregion
152
- export { type AuthConfig, AuthenticationMiddleware, InMemoryEventStore, ServerType, proxyServer, startHTTPServer, startStdioServer, tapTransport };
162
+ export { type AuthConfig, AuthenticationMiddleware, type CorsOptions, InMemoryEventStore, ServerType, proxyServer, startHTTPServer, startStdioServer, tapTransport };
153
163
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AuthenticationMiddleware, Client, InMemoryEventStore, JSONRPCMessageSchema, LATEST_PROTOCOL_VERSION, NEVER, ReadBuffer, Server, ZodIssueCode, anyType, arrayType, booleanType, isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, numberType, objectType, proxyServer, serializeMessage, startHTTPServer, stringType } from "./stdio-CsjPjeWC.js";
1
+ import { AuthenticationMiddleware, Client, InMemoryEventStore, JSONRPCMessageSchema, LATEST_PROTOCOL_VERSION, NEVER, ReadBuffer, Server, ZodIssueCode, anyType, arrayType, booleanType, isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, numberType, objectType, proxyServer, serializeMessage, startHTTPServer, stringType } from "./stdio-DF5lH8jj.js";
2
2
  import process from "node:process";
3
3
 
4
4
  //#region node_modules/.pnpm/eventsource-parser@3.0.6/node_modules/eventsource-parser/dist/index.js
@@ -15051,6 +15051,47 @@ const cleanupServer = async (server, onClose) => {
15051
15051
  console.error("[mcp-proxy] error closing server", error);
15052
15052
  }
15053
15053
  };
15054
+ const applyCorsHeaders = (req, res, corsOptions) => {
15055
+ if (!req.headers.origin) return;
15056
+ const defaultCorsOptions = {
15057
+ allowedHeaders: "Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id",
15058
+ credentials: true,
15059
+ exposedHeaders: ["Mcp-Session-Id"],
15060
+ methods: [
15061
+ "GET",
15062
+ "POST",
15063
+ "OPTIONS"
15064
+ ],
15065
+ origin: "*"
15066
+ };
15067
+ let finalCorsOptions;
15068
+ if (corsOptions === false) return;
15069
+ else if (corsOptions === true || corsOptions === void 0) finalCorsOptions = defaultCorsOptions;
15070
+ else finalCorsOptions = {
15071
+ ...defaultCorsOptions,
15072
+ ...corsOptions
15073
+ };
15074
+ try {
15075
+ const origin = new URL(req.headers.origin);
15076
+ let allowedOrigin = "*";
15077
+ if (finalCorsOptions.origin) {
15078
+ if (typeof finalCorsOptions.origin === "string") allowedOrigin = finalCorsOptions.origin;
15079
+ else if (Array.isArray(finalCorsOptions.origin)) allowedOrigin = finalCorsOptions.origin.includes(origin.origin) ? origin.origin : "false";
15080
+ else if (typeof finalCorsOptions.origin === "function") allowedOrigin = finalCorsOptions.origin(origin.origin) ? origin.origin : "false";
15081
+ }
15082
+ if (allowedOrigin !== "false") res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
15083
+ if (finalCorsOptions.credentials !== void 0) res.setHeader("Access-Control-Allow-Credentials", finalCorsOptions.credentials.toString());
15084
+ if (finalCorsOptions.methods) res.setHeader("Access-Control-Allow-Methods", finalCorsOptions.methods.join(", "));
15085
+ if (finalCorsOptions.allowedHeaders) {
15086
+ const allowedHeaders = typeof finalCorsOptions.allowedHeaders === "string" ? finalCorsOptions.allowedHeaders : finalCorsOptions.allowedHeaders.join(", ");
15087
+ res.setHeader("Access-Control-Allow-Headers", allowedHeaders);
15088
+ }
15089
+ if (finalCorsOptions.exposedHeaders) res.setHeader("Access-Control-Expose-Headers", finalCorsOptions.exposedHeaders.join(", "));
15090
+ if (finalCorsOptions.maxAge !== void 0) res.setHeader("Access-Control-Max-Age", finalCorsOptions.maxAge.toString());
15091
+ } catch (error) {
15092
+ console.error("[mcp-proxy] error parsing origin", error);
15093
+ }
15094
+ };
15054
15095
  const handleStreamRequest = async ({ activeTransports, authenticate, createServer, enableJsonResponse, endpoint, eventStore, oauth, onClose, onConnect, req, res, stateless }) => {
15055
15096
  if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) {
15056
15097
  try {
@@ -15289,7 +15330,7 @@ const handleSSERequest = async ({ activeTransports, createServer, endpoint, onCl
15289
15330
  }
15290
15331
  return false;
15291
15332
  };
15292
- const startHTTPServer = async ({ apiKey, authenticate, createServer, enableJsonResponse, eventStore, host = "::", oauth, onClose, onConnect, onUnhandledRequest, port, sseEndpoint = "/sse", stateless, streamEndpoint = "/mcp" }) => {
15333
+ const startHTTPServer = async ({ apiKey, authenticate, cors, createServer, enableJsonResponse, eventStore, host = "::", oauth, onClose, onConnect, onUnhandledRequest, port, sseEndpoint = "/sse", stateless, streamEndpoint = "/mcp" }) => {
15293
15334
  const activeSSETransports = {};
15294
15335
  const activeStreamTransports = {};
15295
15336
  const authMiddleware = new AuthenticationMiddleware({
@@ -15300,16 +15341,7 @@ const startHTTPServer = async ({ apiKey, authenticate, createServer, enableJsonR
15300
15341
  * @author https://dev.classmethod.jp/articles/mcp-sse/
15301
15342
  */
15302
15343
  const httpServer = http.createServer(async (req, res) => {
15303
- if (req.headers.origin) try {
15304
- const origin = new URL(req.headers.origin);
15305
- res.setHeader("Access-Control-Allow-Origin", origin.origin);
15306
- res.setHeader("Access-Control-Allow-Credentials", "true");
15307
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
15308
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id");
15309
- res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
15310
- } catch (error) {
15311
- console.error("[mcp-proxy] error parsing origin", error);
15312
- }
15344
+ applyCorsHeaders(req, res, cors);
15313
15345
  if (req.method === "OPTIONS") {
15314
15346
  res.writeHead(204);
15315
15347
  res.end();
@@ -21557,4 +21589,4 @@ function serializeMessage(message) {
21557
21589
 
21558
21590
  //#endregion
21559
21591
  export { AuthenticationMiddleware, Client, InMemoryEventStore, JSONRPCMessageSchema, LATEST_PROTOCOL_VERSION, NEVER, ReadBuffer, Server, ZodIssueCode, __commonJS, __toESM, anyType, arrayType, booleanType, isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, numberType, objectType, proxyServer, serializeMessage, startHTTPServer, stringType };
21560
- //# sourceMappingURL=stdio-CsjPjeWC.js.map
21592
+ //# sourceMappingURL=stdio-DF5lH8jj.js.map