mcp-proxy 6.5.3 → 6.5.5
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 +23 -2
- package/dist/bin/mcp-proxy.mjs +17 -2
- package/dist/bin/mcp-proxy.mjs.map +1 -1
- package/dist/index.d.mts +41 -2
- package/dist/index.mjs +1 -1
- package/dist/{stdio-DNR9B0BZ.mjs → stdio-Be0Fx2iV.mjs} +99 -5
- package/dist/stdio-Be0Fx2iV.mjs.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/InMemoryEventStore.test.ts +96 -0
- package/src/InMemoryEventStore.ts +51 -0
- package/src/bin/mcp-proxy.ts +21 -2
- package/src/index.ts +2 -1
- package/src/startHTTPServer.test.ts +205 -0
- package/src/startHTTPServer.ts +145 -10
- package/dist/stdio-DNR9B0BZ.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -42,15 +42,31 @@ declare class AuthenticationMiddleware {
|
|
|
42
42
|
}
|
|
43
43
|
//#endregion
|
|
44
44
|
//#region src/InMemoryEventStore.d.ts
|
|
45
|
+
interface InMemoryEventStoreOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Maximum number of events retained across all streams before the oldest
|
|
48
|
+
* are evicted (FIFO). Bounds memory use; older reconnect attempts beyond
|
|
49
|
+
* this window fall back to a fresh (non-resumed) stream instead of a replay.
|
|
50
|
+
*
|
|
51
|
+
* @default 1000
|
|
52
|
+
*/
|
|
53
|
+
maxEvents?: number;
|
|
54
|
+
}
|
|
45
55
|
/**
|
|
46
56
|
* Simple in-memory implementation of the EventStore interface for resumability
|
|
47
57
|
* This is primarily intended for examples and testing, not for production use
|
|
48
58
|
* where a persistent storage solution would be more appropriate.
|
|
49
59
|
*/
|
|
50
60
|
declare class InMemoryEventStore implements EventStore {
|
|
61
|
+
/**
|
|
62
|
+
* The number of events currently retained (for observability/testing).
|
|
63
|
+
*/
|
|
64
|
+
get size(): number;
|
|
51
65
|
private events;
|
|
52
66
|
private lastTimestamp;
|
|
53
67
|
private lastTimestampCounter;
|
|
68
|
+
private readonly maxEvents;
|
|
69
|
+
constructor(options?: InMemoryEventStoreOptions);
|
|
54
70
|
/**
|
|
55
71
|
* Replays events that occurred after a specific event ID
|
|
56
72
|
* Implements EventStore.replayEventsAfter
|
|
@@ -90,6 +106,13 @@ declare const proxyServer: ({
|
|
|
90
106
|
}) => Promise<void>;
|
|
91
107
|
//#endregion
|
|
92
108
|
//#region src/startHTTPServer.d.ts
|
|
109
|
+
/**
|
|
110
|
+
* `false` disables the resumability event store entirely (no replay-on-
|
|
111
|
+
* reconnect, no retained state). Omitted/`undefined` creates a fresh,
|
|
112
|
+
* bounded `InMemoryEventStore` per session - see `eventStoreMaxEvents`.
|
|
113
|
+
* Pass an `EventStore` instance to use a shared or custom-backed store.
|
|
114
|
+
*/
|
|
115
|
+
type EventStoreOption = EventStore | false;
|
|
93
116
|
interface CorsOptions {
|
|
94
117
|
allowedHeaders?: string | string[];
|
|
95
118
|
credentials?: boolean;
|
|
@@ -112,6 +135,7 @@ declare const startHTTPServer: <T extends ServerLike>({
|
|
|
112
135
|
createServer,
|
|
113
136
|
enableJsonResponse,
|
|
114
137
|
eventStore,
|
|
138
|
+
eventStoreMaxEvents,
|
|
115
139
|
host,
|
|
116
140
|
keepAliveTimeout,
|
|
117
141
|
oauth,
|
|
@@ -131,7 +155,22 @@ declare const startHTTPServer: <T extends ServerLike>({
|
|
|
131
155
|
cors?: boolean | CorsOptions;
|
|
132
156
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
133
157
|
enableJsonResponse?: boolean;
|
|
134
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Event store for the streamable HTTP transport's resumability support.
|
|
160
|
+
* Pass `false` to disable resumability entirely (recommended for
|
|
161
|
+
* request/response-only deployments that don't need replay-on-reconnect).
|
|
162
|
+
* Omit to get a fresh, bounded `InMemoryEventStore` per session - see
|
|
163
|
+
* `eventStoreMaxEvents`. Pass an `EventStore` instance to bring your own
|
|
164
|
+
* (e.g. a persistent, cross-process store); it will be shared across all
|
|
165
|
+
* sessions handled by this server.
|
|
166
|
+
*/
|
|
167
|
+
eventStore?: EventStoreOption;
|
|
168
|
+
/**
|
|
169
|
+
* Caps how many events the auto-created per-session `InMemoryEventStore`
|
|
170
|
+
* retains (oldest evicted first) before it's overridden by an explicit
|
|
171
|
+
* `eventStore`. Bounds memory for long-lived sessions. Default: 1000.
|
|
172
|
+
*/
|
|
173
|
+
eventStoreMaxEvents?: number;
|
|
135
174
|
host?: string;
|
|
136
175
|
keepAliveTimeout?: number;
|
|
137
176
|
oauth?: AuthConfig["oauth"];
|
|
@@ -185,5 +224,5 @@ type TransportEvent = {
|
|
|
185
224
|
};
|
|
186
225
|
declare const tapTransport: (transport: Transport, eventHandler: (event: TransportEvent) => void) => Transport;
|
|
187
226
|
//#endregion
|
|
188
|
-
export { type AuthConfig, AuthenticationMiddleware, type CorsOptions, InMemoryEventStore, ServerType, proxyServer, startHTTPServer, startStdioServer, tapTransport };
|
|
227
|
+
export { type AuthConfig, AuthenticationMiddleware, type CorsOptions, type EventStoreOption, InMemoryEventStore, type InMemoryEventStoreOptions, ServerType, proxyServer, startHTTPServer, startStdioServer, tapTransport };
|
|
189
228
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as NEVER, S as _coercedNumber, T as AuthenticationMiddleware, _ as looseObject, a as startHTTPServer, b as string, c as LATEST_PROTOCOL_VERSION, d as isJSONRPCResultResponse, f as ZodNumber, g as literal, h as boolean, i as Client, l as isInitializedNotification, m as array, n as serializeMessage, o as proxyServer, p as any, r as Server, s as JSONRPCMessageSchema, t as ReadBuffer, u as isJSONRPCRequest, v as number$1, w as InMemoryEventStore, x as url, y as object } from "./stdio-
|
|
1
|
+
import { C as NEVER, S as _coercedNumber, T as AuthenticationMiddleware, _ as looseObject, a as startHTTPServer, b as string, c as LATEST_PROTOCOL_VERSION, d as isJSONRPCResultResponse, f as ZodNumber, g as literal, h as boolean, i as Client, l as isInitializedNotification, m as array, n as serializeMessage, o as proxyServer, p as any, r as Server, s as JSONRPCMessageSchema, t as ReadBuffer, u as isJSONRPCRequest, v as number$1, w as InMemoryEventStore, x as url, y as object } from "./stdio-Be0Fx2iV.mjs";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
|
|
4
4
|
//#region node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/classic/compat.js
|
|
@@ -113,15 +113,28 @@ var AuthenticationMiddleware = class {
|
|
|
113
113
|
|
|
114
114
|
//#endregion
|
|
115
115
|
//#region src/InMemoryEventStore.ts
|
|
116
|
+
const DEFAULT_MAX_EVENTS = 1e3;
|
|
116
117
|
/**
|
|
117
118
|
* Simple in-memory implementation of the EventStore interface for resumability
|
|
118
119
|
* This is primarily intended for examples and testing, not for production use
|
|
119
120
|
* where a persistent storage solution would be more appropriate.
|
|
120
121
|
*/
|
|
121
122
|
var InMemoryEventStore = class {
|
|
123
|
+
/**
|
|
124
|
+
* The number of events currently retained (for observability/testing).
|
|
125
|
+
*/
|
|
126
|
+
get size() {
|
|
127
|
+
return this.events.size;
|
|
128
|
+
}
|
|
122
129
|
events = /* @__PURE__ */ new Map();
|
|
123
130
|
lastTimestamp = 0;
|
|
124
131
|
lastTimestampCounter = 0;
|
|
132
|
+
maxEvents;
|
|
133
|
+
constructor(options = {}) {
|
|
134
|
+
const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
|
|
135
|
+
if (!(maxEvents >= 1)) throw new Error("maxEvents must be at least 1");
|
|
136
|
+
this.maxEvents = maxEvents;
|
|
137
|
+
}
|
|
125
138
|
/**
|
|
126
139
|
* Replays events that occurred after a specific event ID
|
|
127
140
|
* Implements EventStore.replayEventsAfter
|
|
@@ -152,6 +165,11 @@ var InMemoryEventStore = class {
|
|
|
152
165
|
message,
|
|
153
166
|
streamId
|
|
154
167
|
});
|
|
168
|
+
while (this.events.size > this.maxEvents) {
|
|
169
|
+
const oldestEventId = this.events.keys().next().value;
|
|
170
|
+
if (oldestEventId === void 0) break;
|
|
171
|
+
this.events.delete(oldestEventId);
|
|
172
|
+
}
|
|
155
173
|
return eventId;
|
|
156
174
|
}
|
|
157
175
|
/**
|
|
@@ -15857,6 +15875,10 @@ var StreamableHTTPServerTransport = class {
|
|
|
15857
15875
|
//#endregion
|
|
15858
15876
|
//#region src/startHTTPServer.ts
|
|
15859
15877
|
const DEFAULT_KEEP_ALIVE_TIMEOUT = 3e5;
|
|
15878
|
+
const resolveEventStore = (eventStore, maxEvents) => {
|
|
15879
|
+
if (eventStore === false) return;
|
|
15880
|
+
return eventStore ?? new InMemoryEventStore({ maxEvents });
|
|
15881
|
+
};
|
|
15860
15882
|
const getBody = (request) => {
|
|
15861
15883
|
return new Promise((resolve$2) => {
|
|
15862
15884
|
const bodyParts = [];
|
|
@@ -15884,6 +15906,16 @@ const createJsonRpcErrorResponse = (code, message) => {
|
|
|
15884
15906
|
jsonrpc: "2.0"
|
|
15885
15907
|
});
|
|
15886
15908
|
};
|
|
15909
|
+
const getRequestId = (body) => {
|
|
15910
|
+
if (typeof body !== "object" || body === null || Array.isArray(body) || !("id" in body)) return null;
|
|
15911
|
+
return body.id;
|
|
15912
|
+
};
|
|
15913
|
+
const isJsonRpcMessage = (message) => {
|
|
15914
|
+
return JSONRPCMessageSchema.safeParse(message).success;
|
|
15915
|
+
};
|
|
15916
|
+
const isJsonRpcBody = (body) => {
|
|
15917
|
+
return Array.isArray(body) ? body.every(isJsonRpcMessage) : isJsonRpcMessage(body);
|
|
15918
|
+
};
|
|
15887
15919
|
const getWWWAuthenticateHeader = (oauth, options) => {
|
|
15888
15920
|
if (!oauth) return;
|
|
15889
15921
|
const params = [];
|
|
@@ -15903,6 +15935,23 @@ const getWWWAuthenticateHeader = (oauth, options) => {
|
|
|
15903
15935
|
if (params.length === 0) return;
|
|
15904
15936
|
return `Bearer ${params.join(", ")}`;
|
|
15905
15937
|
};
|
|
15938
|
+
const sendSessionUnauthorizedResponse = ({ body, oauth, res }) => {
|
|
15939
|
+
const message = "Unauthorized: No valid session ID provided";
|
|
15940
|
+
res.setHeader("Content-Type", "application/json");
|
|
15941
|
+
const wwwAuthHeader = getWWWAuthenticateHeader(oauth, {
|
|
15942
|
+
error: "invalid_token",
|
|
15943
|
+
error_description: message
|
|
15944
|
+
});
|
|
15945
|
+
if (wwwAuthHeader) res.setHeader("WWW-Authenticate", wwwAuthHeader);
|
|
15946
|
+
res.writeHead(401).end(JSON.stringify({
|
|
15947
|
+
error: {
|
|
15948
|
+
code: -32e3,
|
|
15949
|
+
message
|
|
15950
|
+
},
|
|
15951
|
+
id: getRequestId(body),
|
|
15952
|
+
jsonrpc: "2.0"
|
|
15953
|
+
}));
|
|
15954
|
+
};
|
|
15906
15955
|
const isScopeChallengeError = (error$1) => {
|
|
15907
15956
|
return typeof error$1 === "object" && error$1 !== null && "name" in error$1 && error$1.name === "InsufficientScopeError" && "data" in error$1 && typeof error$1.data === "object" && error$1.data !== null && "error" in error$1.data && error$1.data.error === "insufficient_scope";
|
|
15908
15957
|
};
|
|
@@ -15971,7 +16020,7 @@ const applyCorsHeaders = (req, res, corsOptions) => {
|
|
|
15971
16020
|
console.error("[mcp-proxy] error parsing origin", error$1);
|
|
15972
16021
|
}
|
|
15973
16022
|
};
|
|
15974
|
-
const handleStreamRequest = async ({ activeTransports, authenticate, authMiddleware, createServer, enableJsonResponse, endpoint, eventStore, oauth, onClose, onConnect, req, res, stateless }) => {
|
|
16023
|
+
const handleStreamRequest = async ({ activeTransports, authenticate, authMiddleware, createServer, enableJsonResponse, endpoint, eventStore, eventStoreMaxEvents, oauth, onClose, onConnect, req, res, stateless }) => {
|
|
15975
16024
|
if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
15976
16025
|
let body;
|
|
15977
16026
|
try {
|
|
@@ -16023,6 +16072,14 @@ const handleStreamRequest = async ({ activeTransports, authenticate, authMiddlew
|
|
|
16023
16072
|
if (sessionId) {
|
|
16024
16073
|
const activeTransport = activeTransports[sessionId];
|
|
16025
16074
|
if (!activeTransport) {
|
|
16075
|
+
if (authenticate && isJsonRpcBody(body)) {
|
|
16076
|
+
sendSessionUnauthorizedResponse({
|
|
16077
|
+
body,
|
|
16078
|
+
oauth,
|
|
16079
|
+
res
|
|
16080
|
+
});
|
|
16081
|
+
return true;
|
|
16082
|
+
}
|
|
16026
16083
|
res.setHeader("Content-Type", "application/json");
|
|
16027
16084
|
res.writeHead(404).end(createJsonRpcErrorResponse(-32001, "Session not found"));
|
|
16028
16085
|
return true;
|
|
@@ -16033,7 +16090,7 @@ const handleStreamRequest = async ({ activeTransports, authenticate, authMiddlew
|
|
|
16033
16090
|
} else if (!sessionId && isInitializeRequest(body)) {
|
|
16034
16091
|
transport = new StreamableHTTPServerTransport({
|
|
16035
16092
|
enableJsonResponse,
|
|
16036
|
-
eventStore: eventStore
|
|
16093
|
+
eventStore: resolveEventStore(eventStore, eventStoreMaxEvents),
|
|
16037
16094
|
onsessioninitialized: (_sessionId) => {
|
|
16038
16095
|
if (!stateless && _sessionId) activeTransports[_sessionId] = {
|
|
16039
16096
|
server,
|
|
@@ -16084,7 +16141,7 @@ const handleStreamRequest = async ({ activeTransports, authenticate, authMiddlew
|
|
|
16084
16141
|
} else if (stateless && !sessionId && !isInitializeRequest(body)) {
|
|
16085
16142
|
transport = new StreamableHTTPServerTransport({
|
|
16086
16143
|
enableJsonResponse,
|
|
16087
|
-
eventStore: eventStore
|
|
16144
|
+
eventStore: resolveEventStore(eventStore, eventStoreMaxEvents),
|
|
16088
16145
|
onsessioninitialized: () => {},
|
|
16089
16146
|
sessionIdGenerator: void 0
|
|
16090
16147
|
});
|
|
@@ -16118,6 +16175,14 @@ const handleStreamRequest = async ({ activeTransports, authenticate, authMiddlew
|
|
|
16118
16175
|
await transport.handleRequest(req, res, body);
|
|
16119
16176
|
return true;
|
|
16120
16177
|
} else {
|
|
16178
|
+
if (authenticate && isJsonRpcBody(body)) {
|
|
16179
|
+
sendSessionUnauthorizedResponse({
|
|
16180
|
+
body,
|
|
16181
|
+
oauth,
|
|
16182
|
+
res
|
|
16183
|
+
});
|
|
16184
|
+
return true;
|
|
16185
|
+
}
|
|
16121
16186
|
res.setHeader("Content-Type", "application/json");
|
|
16122
16187
|
res.writeHead(400).end(createJsonRpcErrorResponse(-32e3, "Bad Request: No valid session ID provided"));
|
|
16123
16188
|
return true;
|
|
@@ -16145,10 +16210,24 @@ const handleStreamRequest = async ({ activeTransports, authenticate, authMiddlew
|
|
|
16145
16210
|
res.writeHead(405, { Allow: "POST" }).end("Method Not Allowed");
|
|
16146
16211
|
return true;
|
|
16147
16212
|
}
|
|
16213
|
+
if (authenticate) {
|
|
16214
|
+
sendSessionUnauthorizedResponse({
|
|
16215
|
+
oauth,
|
|
16216
|
+
res
|
|
16217
|
+
});
|
|
16218
|
+
return true;
|
|
16219
|
+
}
|
|
16148
16220
|
res.writeHead(400).end("No sessionId");
|
|
16149
16221
|
return true;
|
|
16150
16222
|
}
|
|
16151
16223
|
if (!activeTransport) {
|
|
16224
|
+
if (authenticate) {
|
|
16225
|
+
sendSessionUnauthorizedResponse({
|
|
16226
|
+
oauth,
|
|
16227
|
+
res
|
|
16228
|
+
});
|
|
16229
|
+
return true;
|
|
16230
|
+
}
|
|
16152
16231
|
res.writeHead(400).end("No active transport");
|
|
16153
16232
|
return true;
|
|
16154
16233
|
}
|
|
@@ -16162,12 +16241,26 @@ const handleStreamRequest = async ({ activeTransports, authenticate, authMiddlew
|
|
|
16162
16241
|
console.log("[mcp-proxy] received delete request");
|
|
16163
16242
|
const sessionId = req.headers["mcp-session-id"];
|
|
16164
16243
|
if (!sessionId) {
|
|
16244
|
+
if (authenticate) {
|
|
16245
|
+
sendSessionUnauthorizedResponse({
|
|
16246
|
+
oauth,
|
|
16247
|
+
res
|
|
16248
|
+
});
|
|
16249
|
+
return true;
|
|
16250
|
+
}
|
|
16165
16251
|
res.writeHead(400).end("Invalid or missing sessionId");
|
|
16166
16252
|
return true;
|
|
16167
16253
|
}
|
|
16168
16254
|
console.log("[mcp-proxy] received delete request for session", sessionId);
|
|
16169
16255
|
const activeTransport = activeTransports[sessionId];
|
|
16170
16256
|
if (!activeTransport) {
|
|
16257
|
+
if (authenticate) {
|
|
16258
|
+
sendSessionUnauthorizedResponse({
|
|
16259
|
+
oauth,
|
|
16260
|
+
res
|
|
16261
|
+
});
|
|
16262
|
+
return true;
|
|
16263
|
+
}
|
|
16171
16264
|
res.writeHead(400).end("No active transport");
|
|
16172
16265
|
return true;
|
|
16173
16266
|
}
|
|
@@ -16237,7 +16330,7 @@ const handleSSERequest = async ({ activeTransports, createServer, endpoint, onCl
|
|
|
16237
16330
|
}
|
|
16238
16331
|
return false;
|
|
16239
16332
|
};
|
|
16240
|
-
const startHTTPServer = async ({ apiKey, authenticate, cors, createServer, enableJsonResponse, eventStore, host = "::", keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT, oauth, onClose, onConnect, onUnhandledRequest, port, sseEndpoint = "/sse", sslCa, sslCert, sslKey, stateless, streamEndpoint = "/mcp" }) => {
|
|
16333
|
+
const startHTTPServer = async ({ apiKey, authenticate, cors, createServer, enableJsonResponse, eventStore, eventStoreMaxEvents, host = "::", keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT, oauth, onClose, onConnect, onUnhandledRequest, port, sseEndpoint = "/sse", sslCa, sslCert, sslKey, stateless, streamEndpoint = "/mcp" }) => {
|
|
16241
16334
|
const activeSSETransports = {};
|
|
16242
16335
|
const activeStreamTransports = {};
|
|
16243
16336
|
const authMiddleware = new AuthenticationMiddleware({
|
|
@@ -16293,6 +16386,7 @@ const startHTTPServer = async ({ apiKey, authenticate, cors, createServer, enabl
|
|
|
16293
16386
|
enableJsonResponse,
|
|
16294
16387
|
endpoint: streamEndpoint,
|
|
16295
16388
|
eventStore,
|
|
16389
|
+
eventStoreMaxEvents,
|
|
16296
16390
|
oauth,
|
|
16297
16391
|
onClose,
|
|
16298
16392
|
onConnect,
|
|
@@ -24824,4 +24918,4 @@ function serializeMessage(message) {
|
|
|
24824
24918
|
|
|
24825
24919
|
//#endregion
|
|
24826
24920
|
export { NEVER as C, __toESM as D, __commonJSMin as E, _coercedNumber as S, AuthenticationMiddleware as T, looseObject as _, startHTTPServer as a, string as b, LATEST_PROTOCOL_VERSION as c, isJSONRPCResultResponse as d, ZodNumber as f, literal as g, boolean as h, Client as i, isInitializedNotification as l, array as m, serializeMessage as n, proxyServer as o, any as p, Server as r, JSONRPCMessageSchema as s, ReadBuffer as t, isJSONRPCRequest as u, number as v, InMemoryEventStore as w, url as x, object as y };
|
|
24827
|
-
//# sourceMappingURL=stdio-
|
|
24921
|
+
//# sourceMappingURL=stdio-Be0Fx2iV.mjs.map
|