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 +148 -1
- package/dist/bin/mcp-proxy.js +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/dist/{stdio-CsjPjeWC.js → stdio-DF5lH8jj.js} +44 -12
- package/dist/stdio-DF5lH8jj.js.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/startHTTPServer.test.ts +248 -0
- package/src/startHTTPServer.ts +101 -13
- package/dist/stdio-CsjPjeWC.js.map +0 -1
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)
|
package/dist/bin/mcp-proxy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Client, InMemoryEventStore, ReadBuffer, Server, __commonJS, __toESM, proxyServer, serializeMessage, startHTTPServer } from "../stdio-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
21592
|
+
//# sourceMappingURL=stdio-DF5lH8jj.js.map
|