| "use strict"; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.StreamableHTTPClientTransport = exports.StreamableHTTPError = void 0; |
| const types_js_1 = require("../types.js"); |
| const auth_js_1 = require("./auth.js"); |
| const stream_1 = require("eventsource-parser/stream"); |
| |
| const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = { |
| initialReconnectionDelay: 1000, |
| maxReconnectionDelay: 30000, |
| reconnectionDelayGrowFactor: 1.5, |
| maxRetries: 2, |
| }; |
| class StreamableHTTPError extends Error { |
| constructor(code, message) { |
| super(`Streamable HTTP error: ${message}`); |
| this.code = code; |
| } |
| } |
| exports.StreamableHTTPError = StreamableHTTPError; |
| |
| |
| |
| |
| |
| class StreamableHTTPClientTransport { |
| constructor(url, opts) { |
| var _a; |
| this._url = url; |
| this._requestInit = opts === null || opts === void 0 ? void 0 : opts.requestInit; |
| this._authProvider = opts === null || opts === void 0 ? void 0 : opts.authProvider; |
| this._sessionId = opts === null || opts === void 0 ? void 0 : opts.sessionId; |
| this._reconnectionOptions = (_a = opts === null || opts === void 0 ? void 0 : opts.reconnectionOptions) !== null && _a !== void 0 ? _a : DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS; |
| } |
| async _authThenStart() { |
| var _a; |
| if (!this._authProvider) { |
| throw new auth_js_1.UnauthorizedError("No auth provider"); |
| } |
| let result; |
| try { |
| result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url }); |
| } |
| catch (error) { |
| (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error); |
| throw error; |
| } |
| if (result !== "AUTHORIZED") { |
| throw new auth_js_1.UnauthorizedError(); |
| } |
| return await this._startOrAuthSse({ resumptionToken: undefined }); |
| } |
| async _commonHeaders() { |
| var _a; |
| const headers = {}; |
| if (this._authProvider) { |
| const tokens = await this._authProvider.tokens(); |
| if (tokens) { |
| headers["Authorization"] = `Bearer ${tokens.access_token}`; |
| } |
| } |
| if (this._sessionId) { |
| headers["mcp-session-id"] = this._sessionId; |
| } |
| return new Headers({ ...headers, ...(_a = this._requestInit) === null || _a === void 0 ? void 0 : _a.headers }); |
| } |
| async _startOrAuthSse(options) { |
| var _a, _b; |
| const { resumptionToken } = options; |
| try { |
| |
| |
| const headers = await this._commonHeaders(); |
| headers.set("Accept", "text/event-stream"); |
| |
| if (resumptionToken) { |
| headers.set("last-event-id", resumptionToken); |
| } |
| const response = await fetch(this._url, { |
| method: "GET", |
| headers, |
| signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal, |
| }); |
| if (!response.ok) { |
| if (response.status === 401 && this._authProvider) { |
| |
| return await this._authThenStart(); |
| } |
| |
| |
| if (response.status === 405) { |
| return; |
| } |
| throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`); |
| } |
| this._handleSseStream(response.body, options); |
| } |
| catch (error) { |
| (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error); |
| throw error; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| _getNextReconnectionDelay(attempt) { |
| |
| const initialDelay = this._reconnectionOptions.initialReconnectionDelay; |
| const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor; |
| const maxDelay = this._reconnectionOptions.maxReconnectionDelay; |
| |
| return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay); |
| } |
| |
| |
| |
| |
| |
| |
| _scheduleReconnection(options, attemptCount = 0) { |
| var _a; |
| |
| const maxRetries = this._reconnectionOptions.maxRetries; |
| |
| if (maxRetries > 0 && attemptCount >= maxRetries) { |
| (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`)); |
| return; |
| } |
| |
| const delay = this._getNextReconnectionDelay(attemptCount); |
| |
| setTimeout(() => { |
| |
| this._startOrAuthSse(options).catch(error => { |
| var _a; |
| (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`)); |
| |
| this._scheduleReconnection(options, attemptCount + 1); |
| }); |
| }, delay); |
| } |
| _handleSseStream(stream, options) { |
| if (!stream) { |
| return; |
| } |
| const { onresumptiontoken, replayMessageId } = options; |
| let lastEventId; |
| const processStream = async () => { |
| var _a, _b, _c, _d; |
| |
| |
| try { |
| |
| const reader = stream |
| .pipeThrough(new TextDecoderStream()) |
| .pipeThrough(new stream_1.EventSourceParserStream()) |
| .getReader(); |
| while (true) { |
| const { value: event, done } = await reader.read(); |
| if (done) { |
| break; |
| } |
| |
| if (event.id) { |
| lastEventId = event.id; |
| onresumptiontoken === null || onresumptiontoken === void 0 ? void 0 : onresumptiontoken(event.id); |
| } |
| if (!event.event || event.event === "message") { |
| try { |
| const message = types_js_1.JSONRPCMessageSchema.parse(JSON.parse(event.data)); |
| if (replayMessageId !== undefined && (0, types_js_1.isJSONRPCResponse)(message)) { |
| message.id = replayMessageId; |
| } |
| (_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message); |
| } |
| catch (error) { |
| (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error); |
| } |
| } |
| } |
| } |
| catch (error) { |
| |
| (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`SSE stream disconnected: ${error}`)); |
| |
| if (this._abortController && !this._abortController.signal.aborted) { |
| |
| if (lastEventId !== undefined) { |
| try { |
| this._scheduleReconnection({ |
| resumptionToken: lastEventId, |
| onresumptiontoken, |
| replayMessageId |
| }, 0); |
| } |
| catch (error) { |
| (_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`)); |
| } |
| } |
| } |
| } |
| }; |
| processStream(); |
| } |
| async start() { |
| if (this._abortController) { |
| throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically."); |
| } |
| this._abortController = new AbortController(); |
| } |
| |
| |
| |
| async finishAuth(authorizationCode) { |
| if (!this._authProvider) { |
| throw new auth_js_1.UnauthorizedError("No auth provider"); |
| } |
| const result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url, authorizationCode }); |
| if (result !== "AUTHORIZED") { |
| throw new auth_js_1.UnauthorizedError("Failed to authorize"); |
| } |
| } |
| async close() { |
| var _a, _b; |
| |
| (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.abort(); |
| (_b = this.onclose) === null || _b === void 0 ? void 0 : _b.call(this); |
| } |
| async send(message, options) { |
| var _a, _b, _c; |
| try { |
| const { resumptionToken, onresumptiontoken } = options || {}; |
| if (resumptionToken) { |
| |
| this._startOrAuthSse({ resumptionToken, replayMessageId: (0, types_js_1.isJSONRPCRequest)(message) ? message.id : undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); }); |
| return; |
| } |
| const headers = await this._commonHeaders(); |
| headers.set("content-type", "application/json"); |
| headers.set("accept", "application/json, text/event-stream"); |
| const init = { |
| ...this._requestInit, |
| method: "POST", |
| headers, |
| body: JSON.stringify(message), |
| signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal, |
| }; |
| const response = await fetch(this._url, init); |
| |
| const sessionId = response.headers.get("mcp-session-id"); |
| if (sessionId) { |
| this._sessionId = sessionId; |
| } |
| if (!response.ok) { |
| if (response.status === 401 && this._authProvider) { |
| const result = await (0, auth_js_1.auth)(this._authProvider, { serverUrl: this._url }); |
| if (result !== "AUTHORIZED") { |
| throw new auth_js_1.UnauthorizedError(); |
| } |
| |
| return this.send(message); |
| } |
| const text = await response.text().catch(() => null); |
| throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`); |
| } |
| |
| if (response.status === 202) { |
| |
| |
| if ((0, types_js_1.isInitializedNotification)(message)) { |
| |
| this._startOrAuthSse({ resumptionToken: undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); }); |
| } |
| return; |
| } |
| |
| const messages = Array.isArray(message) ? message : [message]; |
| const hasRequests = messages.filter(msg => "method" in msg && "id" in msg && msg.id !== undefined).length > 0; |
| |
| const contentType = response.headers.get("content-type"); |
| if (hasRequests) { |
| if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("text/event-stream")) { |
| |
| |
| |
| this._handleSseStream(response.body, { onresumptiontoken }); |
| } |
| else if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("application/json")) { |
| |
| const data = await response.json(); |
| const responseMessages = Array.isArray(data) |
| ? data.map(msg => types_js_1.JSONRPCMessageSchema.parse(msg)) |
| : [types_js_1.JSONRPCMessageSchema.parse(data)]; |
| for (const msg of responseMessages) { |
| (_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, msg); |
| } |
| } |
| else { |
| throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`); |
| } |
| } |
| } |
| catch (error) { |
| (_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error); |
| throw error; |
| } |
| } |
| get sessionId() { |
| return this._sessionId; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async terminateSession() { |
| var _a, _b; |
| if (!this._sessionId) { |
| return; |
| } |
| try { |
| const headers = await this._commonHeaders(); |
| const init = { |
| ...this._requestInit, |
| method: "DELETE", |
| headers, |
| signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal, |
| }; |
| const response = await fetch(this._url, init); |
| |
| |
| if (!response.ok && response.status !== 405) { |
| throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`); |
| } |
| this._sessionId = undefined; |
| } |
| catch (error) { |
| (_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error); |
| throw error; |
| } |
| } |
| } |
| exports.StreamableHTTPClientTransport = StreamableHTTPClientTransport; |
| |