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/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
- eventStore?: EventStore;
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-DNR9B0BZ.mjs";
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 || new InMemoryEventStore(),
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 || new InMemoryEventStore(),
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-DNR9B0BZ.mjs.map
24921
+ //# sourceMappingURL=stdio-Be0Fx2iV.mjs.map