1
0

@modelcontextprotocol%2Fsdk@1.29.0.patch 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. diff --git a/dist/cjs/client/index.js b/dist/cjs/client/index.js
  2. index 6ac1da14dc7f6211ae70f7711c124b76098816d8..adb5b7bd45514a406a0f7e40b64631c101584c84 100644
  3. --- a/dist/cjs/client/index.js
  4. +++ b/dist/cjs/client/index.js
  5. @@ -288,41 +288,16 @@ class Client extends protocol_js_1.Protocol {
  6. }
  7. async connect(transport, options) {
  8. await super.connect(transport);
  9. + transport.onsessionexpired = async () => {
  10. + await this._initialize(transport);
  11. + };
  12. // When transport sessionId is already set this means we are trying to reconnect.
  13. // In this case we don't need to initialize again.
  14. if (transport.sessionId !== undefined) {
  15. return;
  16. }
  17. try {
  18. - const result = await this.request({
  19. - method: 'initialize',
  20. - params: {
  21. - protocolVersion: types_js_1.LATEST_PROTOCOL_VERSION,
  22. - capabilities: this._capabilities,
  23. - clientInfo: this._clientInfo
  24. - }
  25. - }, types_js_1.InitializeResultSchema, options);
  26. - if (result === undefined) {
  27. - throw new Error(`Server sent invalid initialize result: ${result}`);
  28. - }
  29. - if (!types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
  30. - throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
  31. - }
  32. - this._serverCapabilities = result.capabilities;
  33. - this._serverVersion = result.serverInfo;
  34. - // HTTP transports must set the protocol version in each header after initialization.
  35. - if (transport.setProtocolVersion) {
  36. - transport.setProtocolVersion(result.protocolVersion);
  37. - }
  38. - this._instructions = result.instructions;
  39. - await this.notification({
  40. - method: 'notifications/initialized'
  41. - });
  42. - // Set up list changed handlers now that we know server capabilities
  43. - if (this._pendingListChangedConfig) {
  44. - this._setupListChangedHandlers(this._pendingListChangedConfig);
  45. - this._pendingListChangedConfig = undefined;
  46. - }
  47. + await this._initialize(transport, options);
  48. }
  49. catch (error) {
  50. // Disconnect if initialization fails.
  51. @@ -330,6 +305,37 @@ class Client extends protocol_js_1.Protocol {
  52. throw error;
  53. }
  54. }
  55. + async _initialize(transport, options) {
  56. + const result = await this.request({
  57. + method: 'initialize',
  58. + params: {
  59. + protocolVersion: types_js_1.LATEST_PROTOCOL_VERSION,
  60. + capabilities: this._capabilities,
  61. + clientInfo: this._clientInfo
  62. + }
  63. + }, types_js_1.InitializeResultSchema, options);
  64. + if (result === undefined) {
  65. + throw new Error(`Server sent invalid initialize result: ${result}`);
  66. + }
  67. + if (!types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
  68. + throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
  69. + }
  70. + this._serverCapabilities = result.capabilities;
  71. + this._serverVersion = result.serverInfo;
  72. + // HTTP transports must set the protocol version in each header after initialization.
  73. + if (transport.setProtocolVersion) {
  74. + transport.setProtocolVersion(result.protocolVersion);
  75. + }
  76. + this._instructions = result.instructions;
  77. + await this.notification({
  78. + method: 'notifications/initialized'
  79. + });
  80. + // Set up list changed handlers now that we know server capabilities
  81. + if (this._pendingListChangedConfig) {
  82. + this._setupListChangedHandlers(this._pendingListChangedConfig);
  83. + this._pendingListChangedConfig = undefined;
  84. + }
  85. + }
  86. /**
  87. * After initialization has completed, this will be populated with the server's reported capabilities.
  88. */
  89. diff --git a/dist/cjs/client/streamableHttp.js b/dist/cjs/client/streamableHttp.js
  90. index a29a7d3a0f14d9cd800ef5b296485237350c666f..c362ae5fe6c62c8c8eae7e2e61de1eedff5443c9 100644
  91. --- a/dist/cjs/client/streamableHttp.js
  92. +++ b/dist/cjs/client/streamableHttp.js
  93. @@ -290,7 +290,38 @@ class StreamableHTTPClientTransport {
  94. this.onclose?.();
  95. }
  96. async send(message, options) {
  97. + return this._send(message, options, false);
  98. + }
  99. + async _recoverSession(expiredSessionId) {
  100. + if (this._sessionRecovery) {
  101. + await this._sessionRecovery;
  102. + return true;
  103. + }
  104. + if (this._sessionId !== expiredSessionId)
  105. + return true;
  106. + this._sessionId = undefined;
  107. + this._sessionRecovery = Promise.resolve().then(() => this.onsessionexpired?.());
  108. try {
  109. + await this._sessionRecovery;
  110. + }
  111. + catch (error) {
  112. + this._sessionId = undefined;
  113. + await this.close();
  114. + throw error;
  115. + }
  116. + finally {
  117. + this._sessionRecovery = undefined;
  118. + }
  119. + return true;
  120. + }
  121. + async _send(message, options, isSessionRetry) {
  122. + try {
  123. + if (this._sessionRecovery && !(0, types_js_1.isInitializeRequest)(message) && !(0, types_js_1.isInitializedNotification)(message)) {
  124. + await this._sessionRecovery;
  125. + if (options?.isRequestActive?.() === false) {
  126. + throw new Error('Request is no longer active');
  127. + }
  128. + }
  129. const { resumptionToken, onresumptiontoken } = options || {};
  130. if (resumptionToken) {
  131. // If we have at last event ID, we need to reconnect the SSE stream
  132. @@ -298,6 +329,7 @@ class StreamableHTTPClientTransport {
  133. return;
  134. }
  135. const headers = await this._commonHeaders();
  136. + const requestSessionId = headers.get('mcp-session-id') ?? undefined;
  137. headers.set('content-type', 'application/json');
  138. headers.set('accept', 'application/json, text/event-stream');
  139. const init = {
  140. @@ -310,11 +342,20 @@ class StreamableHTTPClientTransport {
  141. const response = await (this._fetch ?? fetch)(this._url, init);
  142. // Handle session ID received during initialization
  143. const sessionId = response.headers.get('mcp-session-id');
  144. - if (sessionId) {
  145. + if (sessionId && (requestSessionId === undefined || this._sessionId === requestSessionId)) {
  146. this._sessionId = sessionId;
  147. }
  148. if (!response.ok) {
  149. const text = await response.text().catch(() => null);
  150. + if (response.status === 404 && requestSessionId && !isSessionRetry && !(0, types_js_1.isInitializedNotification)(message)) {
  151. + const recovered = await this._recoverSession(requestSessionId);
  152. + if (options?.isRequestActive?.() === false) {
  153. + throw new Error('Request is no longer active');
  154. + }
  155. + if (recovered) {
  156. + return this._send(message, options, true);
  157. + }
  158. + }
  159. if (response.status === 401 && this._authProvider) {
  160. // Prevent infinite recursion when server returns 401 after successful auth
  161. if (this._hasCompletedAuthFlow) {
  162. @@ -335,7 +376,7 @@ class StreamableHTTPClientTransport {
  163. // Mark that we completed auth flow
  164. this._hasCompletedAuthFlow = true;
  165. // Purposely _not_ awaited, so we don't call onerror twice
  166. - return this.send(message);
  167. + return this._send(message, options, isSessionRetry);
  168. }
  169. if (response.status === 403 && this._authProvider) {
  170. const { resourceMetadataUrl, scope, error } = (0, auth_js_1.extractWWWAuthenticateParams)(response);
  171. @@ -362,7 +403,7 @@ class StreamableHTTPClientTransport {
  172. if (result !== 'AUTHORIZED') {
  173. throw new auth_js_1.UnauthorizedError();
  174. }
  175. - return this.send(message);
  176. + return this._send(message, options, isSessionRetry);
  177. }
  178. }
  179. throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
  180. diff --git a/dist/cjs/shared/protocol.js b/dist/cjs/shared/protocol.js
  181. index 3617e787f0ba70447c99501aee7aa67584d89758..4a96d6a0328fa348b96f3869ab7e0bb77538182b 100644
  182. --- a/dist/cjs/shared/protocol.js
  183. +++ b/dist/cjs/shared/protocol.js
  184. @@ -744,7 +744,12 @@ class Protocol {
  185. }
  186. else {
  187. // No related task - send through transport normally
  188. - this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch(error => {
  189. + this._transport.send(jsonrpcRequest, {
  190. + relatedRequestId,
  191. + resumptionToken,
  192. + onresumptiontoken,
  193. + isRequestActive: () => this._responseHandlers.has(messageId)
  194. + }).catch(error => {
  195. this._cleanupTimeout(messageId);
  196. reject(error);
  197. });
  198. diff --git a/dist/esm/client/index.js b/dist/esm/client/index.js
  199. index 49b12c6cd918c457420fef7ad5528a9443d1a191..2afe2e22e960f26c9d516ef135d89f8eb9e4caff 100644
  200. --- a/dist/esm/client/index.js
  201. +++ b/dist/esm/client/index.js
  202. @@ -284,41 +284,16 @@ export class Client extends Protocol {
  203. }
  204. async connect(transport, options) {
  205. await super.connect(transport);
  206. + transport.onsessionexpired = async () => {
  207. + await this._initialize(transport);
  208. + };
  209. // When transport sessionId is already set this means we are trying to reconnect.
  210. // In this case we don't need to initialize again.
  211. if (transport.sessionId !== undefined) {
  212. return;
  213. }
  214. try {
  215. - const result = await this.request({
  216. - method: 'initialize',
  217. - params: {
  218. - protocolVersion: LATEST_PROTOCOL_VERSION,
  219. - capabilities: this._capabilities,
  220. - clientInfo: this._clientInfo
  221. - }
  222. - }, InitializeResultSchema, options);
  223. - if (result === undefined) {
  224. - throw new Error(`Server sent invalid initialize result: ${result}`);
  225. - }
  226. - if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
  227. - throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
  228. - }
  229. - this._serverCapabilities = result.capabilities;
  230. - this._serverVersion = result.serverInfo;
  231. - // HTTP transports must set the protocol version in each header after initialization.
  232. - if (transport.setProtocolVersion) {
  233. - transport.setProtocolVersion(result.protocolVersion);
  234. - }
  235. - this._instructions = result.instructions;
  236. - await this.notification({
  237. - method: 'notifications/initialized'
  238. - });
  239. - // Set up list changed handlers now that we know server capabilities
  240. - if (this._pendingListChangedConfig) {
  241. - this._setupListChangedHandlers(this._pendingListChangedConfig);
  242. - this._pendingListChangedConfig = undefined;
  243. - }
  244. + await this._initialize(transport, options);
  245. }
  246. catch (error) {
  247. // Disconnect if initialization fails.
  248. @@ -326,6 +301,37 @@ export class Client extends Protocol {
  249. throw error;
  250. }
  251. }
  252. + async _initialize(transport, options) {
  253. + const result = await this.request({
  254. + method: 'initialize',
  255. + params: {
  256. + protocolVersion: LATEST_PROTOCOL_VERSION,
  257. + capabilities: this._capabilities,
  258. + clientInfo: this._clientInfo
  259. + }
  260. + }, InitializeResultSchema, options);
  261. + if (result === undefined) {
  262. + throw new Error(`Server sent invalid initialize result: ${result}`);
  263. + }
  264. + if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) {
  265. + throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
  266. + }
  267. + this._serverCapabilities = result.capabilities;
  268. + this._serverVersion = result.serverInfo;
  269. + // HTTP transports must set the protocol version in each header after initialization.
  270. + if (transport.setProtocolVersion) {
  271. + transport.setProtocolVersion(result.protocolVersion);
  272. + }
  273. + this._instructions = result.instructions;
  274. + await this.notification({
  275. + method: 'notifications/initialized'
  276. + });
  277. + // Set up list changed handlers now that we know server capabilities
  278. + if (this._pendingListChangedConfig) {
  279. + this._setupListChangedHandlers(this._pendingListChangedConfig);
  280. + this._pendingListChangedConfig = undefined;
  281. + }
  282. + }
  283. /**
  284. * After initialization has completed, this will be populated with the server's reported capabilities.
  285. */
  286. diff --git a/dist/esm/client/streamableHttp.js b/dist/esm/client/streamableHttp.js
  287. index 624172aa24ae255a67c083f9c19053343e4a0581..ac75b14545fda44aff7ff4d97cc5da884fcc627a 100644
  288. --- a/dist/esm/client/streamableHttp.js
  289. +++ b/dist/esm/client/streamableHttp.js
  290. @@ -1,5 +1,5 @@
  291. import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js';
  292. -import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
  293. +import { isInitializedNotification, isInitializeRequest, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
  294. import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
  295. import { EventSourceParserStream } from 'eventsource-parser/stream';
  296. // Default reconnection options for StreamableHTTP connections
  297. @@ -286,7 +286,38 @@ export class StreamableHTTPClientTransport {
  298. this.onclose?.();
  299. }
  300. async send(message, options) {
  301. + return this._send(message, options, false);
  302. + }
  303. + async _recoverSession(expiredSessionId) {
  304. + if (this._sessionRecovery) {
  305. + await this._sessionRecovery;
  306. + return true;
  307. + }
  308. + if (this._sessionId !== expiredSessionId)
  309. + return true;
  310. + this._sessionId = undefined;
  311. + this._sessionRecovery = Promise.resolve().then(() => this.onsessionexpired?.());
  312. try {
  313. + await this._sessionRecovery;
  314. + }
  315. + catch (error) {
  316. + this._sessionId = undefined;
  317. + await this.close();
  318. + throw error;
  319. + }
  320. + finally {
  321. + this._sessionRecovery = undefined;
  322. + }
  323. + return true;
  324. + }
  325. + async _send(message, options, isSessionRetry) {
  326. + try {
  327. + if (this._sessionRecovery && !isInitializeRequest(message) && !isInitializedNotification(message)) {
  328. + await this._sessionRecovery;
  329. + if (options?.isRequestActive?.() === false) {
  330. + throw new Error('Request is no longer active');
  331. + }
  332. + }
  333. const { resumptionToken, onresumptiontoken } = options || {};
  334. if (resumptionToken) {
  335. // If we have at last event ID, we need to reconnect the SSE stream
  336. @@ -294,6 +325,7 @@ export class StreamableHTTPClientTransport {
  337. return;
  338. }
  339. const headers = await this._commonHeaders();
  340. + const requestSessionId = headers.get('mcp-session-id') ?? undefined;
  341. headers.set('content-type', 'application/json');
  342. headers.set('accept', 'application/json, text/event-stream');
  343. const init = {
  344. @@ -306,11 +338,20 @@ export class StreamableHTTPClientTransport {
  345. const response = await (this._fetch ?? fetch)(this._url, init);
  346. // Handle session ID received during initialization
  347. const sessionId = response.headers.get('mcp-session-id');
  348. - if (sessionId) {
  349. + if (sessionId && (requestSessionId === undefined || this._sessionId === requestSessionId)) {
  350. this._sessionId = sessionId;
  351. }
  352. if (!response.ok) {
  353. const text = await response.text().catch(() => null);
  354. + if (response.status === 404 && requestSessionId && !isSessionRetry && !isInitializedNotification(message)) {
  355. + const recovered = await this._recoverSession(requestSessionId);
  356. + if (options?.isRequestActive?.() === false) {
  357. + throw new Error('Request is no longer active');
  358. + }
  359. + if (recovered) {
  360. + return this._send(message, options, true);
  361. + }
  362. + }
  363. if (response.status === 401 && this._authProvider) {
  364. // Prevent infinite recursion when server returns 401 after successful auth
  365. if (this._hasCompletedAuthFlow) {
  366. @@ -331,7 +372,7 @@ export class StreamableHTTPClientTransport {
  367. // Mark that we completed auth flow
  368. this._hasCompletedAuthFlow = true;
  369. // Purposely _not_ awaited, so we don't call onerror twice
  370. - return this.send(message);
  371. + return this._send(message, options, isSessionRetry);
  372. }
  373. if (response.status === 403 && this._authProvider) {
  374. const { resourceMetadataUrl, scope, error } = extractWWWAuthenticateParams(response);
  375. @@ -358,7 +399,7 @@ export class StreamableHTTPClientTransport {
  376. if (result !== 'AUTHORIZED') {
  377. throw new UnauthorizedError();
  378. }
  379. - return this.send(message);
  380. + return this._send(message, options, isSessionRetry);
  381. }
  382. }
  383. throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
  384. diff --git a/dist/esm/shared/protocol.js b/dist/esm/shared/protocol.js
  385. index bfa2b7120a0f50c569364ea5264e6f811076f44f..abd8dfd707c155f71dae7aeeeeaf7547368ac749 100644
  386. --- a/dist/esm/shared/protocol.js
  387. +++ b/dist/esm/shared/protocol.js
  388. @@ -740,7 +740,12 @@ export class Protocol {
  389. }
  390. else {
  391. // No related task - send through transport normally
  392. - this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch(error => {
  393. + this._transport.send(jsonrpcRequest, {
  394. + relatedRequestId,
  395. + resumptionToken,
  396. + onresumptiontoken,
  397. + isRequestActive: () => this._responseHandlers.has(messageId)
  398. + }).catch(error => {
  399. this._cleanupTimeout(messageId);
  400. reject(error);
  401. });