sunpeak 0.16.1 → 0.16.3
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 +2 -1
- package/bin/commands/build.mjs +106 -2
- package/bin/commands/dev.mjs +57 -64
- package/bin/commands/start.mjs +215 -0
- package/bin/sunpeak.js +10 -1
- package/dist/chatgpt/index.cjs +2 -2
- package/dist/chatgpt/index.js +2 -2
- package/dist/claude/index.cjs +1 -1
- package/dist/claude/index.js +1 -1
- package/dist/{index-BvQ_ZuOO.cjs → index-Bll1bszc.cjs} +2 -2
- package/dist/{index-BvQ_ZuOO.cjs.map → index-Bll1bszc.cjs.map} +1 -1
- package/dist/{index-CTGEqlgk.js → index-CACtnwu2.js} +2 -2
- package/dist/{index-CTGEqlgk.js.map → index-CACtnwu2.js.map} +1 -1
- package/dist/{index-BjnAsaqp.js → index-CLcr8IyR.js} +2 -2
- package/dist/index-CLcr8IyR.js.map +1 -0
- package/dist/{index-C9CVbGFt.cjs → index-CaQmwZJc.cjs} +2 -2
- package/dist/index-CaQmwZJc.cjs.map +1 -0
- package/dist/index.cjs +5 -5
- package/dist/index.js +6 -6
- package/dist/mcp/index.cjs +1671 -82
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +1672 -83
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/production-server.d.ts +156 -0
- package/dist/platform/chatgpt/index.cjs +1 -1
- package/dist/platform/chatgpt/index.js +1 -1
- package/dist/{protocol-DFbsCx7E.js → protocol-BD5jDQEx.js} +8 -1
- package/dist/{protocol-DFbsCx7E.js.map → protocol-BD5jDQEx.js.map} +1 -1
- package/dist/{protocol-CL4_Npj5.cjs → protocol-BOjXuK6l.cjs} +8 -1
- package/dist/{protocol-CL4_Npj5.cjs.map → protocol-BOjXuK6l.cjs.map} +1 -1
- package/dist/simulator/index.cjs +1 -1
- package/dist/simulator/index.js +1 -1
- package/dist/{simulator-C0H_k092.js → simulator-B7rw83zP.js} +2 -2
- package/dist/{simulator-C0H_k092.js.map → simulator-B7rw83zP.js.map} +1 -1
- package/dist/{simulator-B56j5P8W.cjs → simulator-DjZNa1MI.cjs} +2 -2
- package/dist/{simulator-B56j5P8W.cjs.map → simulator-DjZNa1MI.cjs.map} +1 -1
- package/dist/{use-app-BuufpXTQ.cjs → use-app-BpAJqzdE.cjs} +2 -2
- package/dist/{use-app-BuufpXTQ.cjs.map → use-app-BpAJqzdE.cjs.map} +1 -1
- package/dist/{use-app-BThbgFFT.js → use-app-WOUdh1PR.js} +2 -2
- package/dist/{use-app-BThbgFFT.js.map → use-app-WOUdh1PR.js.map} +1 -1
- package/package.json +1 -1
- package/template/README.md +13 -12
- package/template/package.json +1 -0
- package/template/src/server.ts +7 -5
- package/dist/index-BjnAsaqp.js.map +0 -1
- package/dist/index-C9CVbGFt.cjs.map +0 -1
package/dist/mcp/index.cjs
CHANGED
|
@@ -4,9 +4,13 @@ const node_http = require("node:http");
|
|
|
4
4
|
const node_url = require("node:url");
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const path = require("node:path");
|
|
7
|
-
const protocol = require("../protocol-
|
|
7
|
+
const protocol = require("../protocol-BOjXuK6l.cjs");
|
|
8
8
|
const zod = require("zod");
|
|
9
|
-
|
|
9
|
+
require("http");
|
|
10
|
+
const http2 = require("http2");
|
|
11
|
+
const stream = require("stream");
|
|
12
|
+
const crypto$1 = require("crypto");
|
|
13
|
+
const node_crypto = require("node:crypto");
|
|
10
14
|
const FAVICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAOdEVYdFNvZnR3YXJlAEZpZ21hnrGWYwAAB3dJREFUeAHtnU9MFFccx78L/gPULkkTrESzok1ND7BGSWkvLgeSNj0IRxNT8aTpocDFHpqIeOm/g3BqPBUSTr0s3pr0wHJRG0xcOdVUcDEpkVbjVhG0SOnvN7AR11mYmS0zb+b3+ySTnZmdjZH3+f3eezPvzYvBFak48Keddpr4gDY6RgJKgCzngViWdmiLjQHVGSCTd/rrmLPLWlLA0gn6xzrpJ3EopjMIVPYBN3IbXbiBAFbE99JON5QwMriRCOsIYEX9j9AUH3ZytHUA41m7Lyvsf9PcRYU/Ci38KJCg7RbwgW0Wt8kAXPjoRwSJ1ywhvvMlbUvWfjG52W3Iz21B/lkloklFD/Dra2VbJEBzEpYt4SVR9wLJhgUk9rxAU8M87c9bBc7n3ZCb3b4iBMlwe7IG2clqZKeqrXPhprKV2gSZwtEaAVoSYUz7XLDtH+VxvPEJUo1zFNkvsZmwGNmpKoxN7EZmYpclRsjIAYtHqNdodRXXCNDMDb5OhIBU41Oc+PCxVfBuI/v/hoVgEYZ+edv6DAlUDYz38M6qAFb034PBJA/OW4Xe3fHnpke5Vwoy9A3Xh6CqWKkKVgUwN/o52ntP/WF9holCVhikzVAGKAt0kwBJutmz9TEMo7PtIRX8TOApvlw4K/QN7zVRBGoDLB4gAY7Svf2KNAwhKgVfjKEinCEBmrlf2IWACWuqdwuL0HHpkCm9hwG641H/JQLs+vENmR++mEb/ufsU9f8g6vA9iXOf/mX9X29P1QR902kHC8AZYAcCoJ1a9aPf30HL4TlIg3s13I39mwTgG0wB8ZyrgGX4DEf9ZYp4ru8VWO2CoLqOvguQPLiA9IXfI9fIKxduG7SeP+y7BL4KsNLQm4FSmp4r+3xtIAZSBSjmUAFFNCqAcFQA4agAwlEBhKMCCEcFEI4KIBwVQDgqgHBUAOFsQRnwkz2eeKEEB48lyE5WwSueBeARLfpYN3h4RNGBz5o8jyzyXAXw+D0t/OApDK7xiicBeDiTjuYxBy4Lr4NpPQmQvnAXillwRvaCawHYNk395sEZwEtWdi2ADukyFy9l40oAjX6z4bJxmwVcCaDRbz5uy8ixABr94YDLyE2PwLEAp9seQQkHbnoEjgTgu36pxidQwgFnAL5X4wRHAnjtYyrBwfMuneDoWcD07HaT33Sh2PDWziVH1+nMIOHoeADhqADCUQGEowIIRwUQjgogHBVAOCqAcFQA4agAwlEBhFPyYVB3x6ztujpK+OBJI/3pOtvvSj4MWv55HEp0iH3cbHvetgqI79TIjxqlsrm9AIYuyaJ4h5fLs8NWAAmvbZdGqayuvQAhuKICFDmoAMJRAYRjK0B+LqqLJyvF2AsQ2dWz5VJqJZISApT17ijFQPJz9mWqVYAQSmX1kqHec2W/PgyKCOtV6TozSDjaDRSOCiAcFUA4KoBwVADhqADCUQGEowIIRwUQjgogHBVAOI6e+/afve/4tWOKGfDz/77h+g2vcyQAjw/o6piFEh6cvtfRURUwci0OJVz0De91dJ0jAXhpsszEbijhIDOxi6qA7Y6uddwIdGqUEjxDLl7r61gAN1YpwZFz+V5nV91AzQLm47aMXAnAZmkWMJech7e6u74RpFnAXLyUjWsB2DDtEZhHzuOaDp5uBWsWMI+OS4fgBU8CcI9gSFcQMQaO/OxkNbzg+WFQ95X9OofQADj1l5ORy5oYwitTJRsWoARHdqrKc/QzOjNIODoeQDgqgHBUAOGoAMJRAYSjAghHBRCOCiAcFUA4KoBwVADh+PpGyOTBBVw+Ow2lNDybhx+3+4XvD4MSdS8w+t0d61N5BT/W5UEd5TzZ84LvVQD/R1vPv6cDStbAf4sjn7/ve+EzlUB9J336OveL5xqOXK/FNMnA1YLURar473Dy6wZ889M7eL4YSHMsXxBgDwKAp5xdvR5HLQnAg0skwcO4PvnqXfob1CBAbnAboJ92uhAwyYZ5pHvvRr5twCOqeQiXnw29dRjgDMDR346AefB4KwZG6iJbLXDbh1/AzZs5k2ti31IGSFL9v/UefG4HbERn20P0npoJfUYoDNocNLLRu1gbW9kxoxqwg0U43fYIqcYnCBOGpXo7BoHxM6sCHEtROhiFwXAmuEgZ4XjjU2OzArfqB9J11MOJB9Klc0flAWoD5mKvThylLFBhZBYoJkUScGYwQQZO8Vevxa1urcHRXswgRz/vrBGA2wLbbgHLCYQI7j6yECwD9yQ2WwiOci7oMdr41Tnhmy0dy1Ggt3L0W0evf2l+VbARLAD3IliGJpKDl73hc27F4ILlhZZ44sVtul/Bx9nJqihMjz9C0Z8tHMTe/P5YN52+jAjCq6Jz99JucWye5saLZVlbZFdNW+4BbvavPROzvzC6EsjlzcJnYqV/0Jykr9NhaxMoxXCd/y81+G5m7L5dZ3rvzANg31USoJYOklDCyABQcxK49lupC2JwREsCWLpIO6ehmE6etiEK3JFSUb8WhwIUSFFX8WmKuhG0oQkrmUFfIxosOawU+hht1LqvoYLP5J3++D/8aGq5otzXywAAAABJRU5ErkJggg==";
|
|
11
15
|
const FAVICON_BUFFER = Buffer.from(FAVICON_BASE64, "base64");
|
|
12
16
|
function getDefaultExportFromCjs(x2) {
|
|
@@ -4135,8 +4139,8 @@ function requireCore$1() {
|
|
|
4135
4139
|
return this;
|
|
4136
4140
|
}
|
|
4137
4141
|
case "object": {
|
|
4138
|
-
const
|
|
4139
|
-
this._cache.delete(
|
|
4142
|
+
const cacheKey2 = schemaKeyRef;
|
|
4143
|
+
this._cache.delete(cacheKey2);
|
|
4140
4144
|
let id2 = schemaKeyRef[this.opts.schemaId];
|
|
4141
4145
|
if (id2) {
|
|
4142
4146
|
id2 = (0, resolve_1.normalizeId)(id2);
|
|
@@ -8045,6 +8049,1215 @@ const EMPTY_COMPLETION_RESULT = {
|
|
|
8045
8049
|
hasMore: false
|
|
8046
8050
|
}
|
|
8047
8051
|
};
|
|
8052
|
+
var RequestError = class extends Error {
|
|
8053
|
+
constructor(message, options) {
|
|
8054
|
+
super(message, options);
|
|
8055
|
+
this.name = "RequestError";
|
|
8056
|
+
}
|
|
8057
|
+
};
|
|
8058
|
+
var toRequestError = (e) => {
|
|
8059
|
+
if (e instanceof RequestError) {
|
|
8060
|
+
return e;
|
|
8061
|
+
}
|
|
8062
|
+
return new RequestError(e.message, { cause: e });
|
|
8063
|
+
};
|
|
8064
|
+
var GlobalRequest = global.Request;
|
|
8065
|
+
var Request = class extends GlobalRequest {
|
|
8066
|
+
constructor(input, options) {
|
|
8067
|
+
if (typeof input === "object" && getRequestCache in input) {
|
|
8068
|
+
input = input[getRequestCache]();
|
|
8069
|
+
}
|
|
8070
|
+
if (typeof options?.body?.getReader !== "undefined") {
|
|
8071
|
+
options.duplex ??= "half";
|
|
8072
|
+
}
|
|
8073
|
+
super(input, options);
|
|
8074
|
+
}
|
|
8075
|
+
};
|
|
8076
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
8077
|
+
const headerRecord = [];
|
|
8078
|
+
const rawHeaders = incoming.rawHeaders;
|
|
8079
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
8080
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
8081
|
+
if (key.charCodeAt(0) !== /*:*/
|
|
8082
|
+
58) {
|
|
8083
|
+
headerRecord.push([key, value]);
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
8086
|
+
return new Headers(headerRecord);
|
|
8087
|
+
};
|
|
8088
|
+
var wrapBodyStream = /* @__PURE__ */ Symbol("wrapBodyStream");
|
|
8089
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
8090
|
+
const init = {
|
|
8091
|
+
method,
|
|
8092
|
+
headers,
|
|
8093
|
+
signal: abortController.signal
|
|
8094
|
+
};
|
|
8095
|
+
if (method === "TRACE") {
|
|
8096
|
+
init.method = "GET";
|
|
8097
|
+
const req = new Request(url, init);
|
|
8098
|
+
Object.defineProperty(req, "method", {
|
|
8099
|
+
get() {
|
|
8100
|
+
return "TRACE";
|
|
8101
|
+
}
|
|
8102
|
+
});
|
|
8103
|
+
return req;
|
|
8104
|
+
}
|
|
8105
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
8106
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
|
|
8107
|
+
init.body = new ReadableStream({
|
|
8108
|
+
start(controller) {
|
|
8109
|
+
controller.enqueue(incoming.rawBody);
|
|
8110
|
+
controller.close();
|
|
8111
|
+
}
|
|
8112
|
+
});
|
|
8113
|
+
} else if (incoming[wrapBodyStream]) {
|
|
8114
|
+
let reader;
|
|
8115
|
+
init.body = new ReadableStream({
|
|
8116
|
+
async pull(controller) {
|
|
8117
|
+
try {
|
|
8118
|
+
reader ||= stream.Readable.toWeb(incoming).getReader();
|
|
8119
|
+
const { done, value } = await reader.read();
|
|
8120
|
+
if (done) {
|
|
8121
|
+
controller.close();
|
|
8122
|
+
} else {
|
|
8123
|
+
controller.enqueue(value);
|
|
8124
|
+
}
|
|
8125
|
+
} catch (error) {
|
|
8126
|
+
controller.error(error);
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
});
|
|
8130
|
+
} else {
|
|
8131
|
+
init.body = stream.Readable.toWeb(incoming);
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
return new Request(url, init);
|
|
8135
|
+
};
|
|
8136
|
+
var getRequestCache = /* @__PURE__ */ Symbol("getRequestCache");
|
|
8137
|
+
var requestCache = /* @__PURE__ */ Symbol("requestCache");
|
|
8138
|
+
var incomingKey = /* @__PURE__ */ Symbol("incomingKey");
|
|
8139
|
+
var urlKey = /* @__PURE__ */ Symbol("urlKey");
|
|
8140
|
+
var headersKey = /* @__PURE__ */ Symbol("headersKey");
|
|
8141
|
+
var abortControllerKey = /* @__PURE__ */ Symbol("abortControllerKey");
|
|
8142
|
+
var getAbortController = /* @__PURE__ */ Symbol("getAbortController");
|
|
8143
|
+
var requestPrototype = {
|
|
8144
|
+
get method() {
|
|
8145
|
+
return this[incomingKey].method || "GET";
|
|
8146
|
+
},
|
|
8147
|
+
get url() {
|
|
8148
|
+
return this[urlKey];
|
|
8149
|
+
},
|
|
8150
|
+
get headers() {
|
|
8151
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
8152
|
+
},
|
|
8153
|
+
[getAbortController]() {
|
|
8154
|
+
this[getRequestCache]();
|
|
8155
|
+
return this[abortControllerKey];
|
|
8156
|
+
},
|
|
8157
|
+
[getRequestCache]() {
|
|
8158
|
+
this[abortControllerKey] ||= new AbortController();
|
|
8159
|
+
return this[requestCache] ||= newRequestFromIncoming(
|
|
8160
|
+
this.method,
|
|
8161
|
+
this[urlKey],
|
|
8162
|
+
this.headers,
|
|
8163
|
+
this[incomingKey],
|
|
8164
|
+
this[abortControllerKey]
|
|
8165
|
+
);
|
|
8166
|
+
}
|
|
8167
|
+
};
|
|
8168
|
+
[
|
|
8169
|
+
"body",
|
|
8170
|
+
"bodyUsed",
|
|
8171
|
+
"cache",
|
|
8172
|
+
"credentials",
|
|
8173
|
+
"destination",
|
|
8174
|
+
"integrity",
|
|
8175
|
+
"mode",
|
|
8176
|
+
"redirect",
|
|
8177
|
+
"referrer",
|
|
8178
|
+
"referrerPolicy",
|
|
8179
|
+
"signal",
|
|
8180
|
+
"keepalive"
|
|
8181
|
+
].forEach((k2) => {
|
|
8182
|
+
Object.defineProperty(requestPrototype, k2, {
|
|
8183
|
+
get() {
|
|
8184
|
+
return this[getRequestCache]()[k2];
|
|
8185
|
+
}
|
|
8186
|
+
});
|
|
8187
|
+
});
|
|
8188
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k2) => {
|
|
8189
|
+
Object.defineProperty(requestPrototype, k2, {
|
|
8190
|
+
value: function() {
|
|
8191
|
+
return this[getRequestCache]()[k2]();
|
|
8192
|
+
}
|
|
8193
|
+
});
|
|
8194
|
+
});
|
|
8195
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
8196
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
8197
|
+
const req = Object.create(requestPrototype);
|
|
8198
|
+
req[incomingKey] = incoming;
|
|
8199
|
+
const incomingUrl = incoming.url || "";
|
|
8200
|
+
if (incomingUrl[0] !== "/" && // short-circuit for performance. most requests are relative URL.
|
|
8201
|
+
(incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
8202
|
+
if (incoming instanceof http2.Http2ServerRequest) {
|
|
8203
|
+
throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
8204
|
+
}
|
|
8205
|
+
try {
|
|
8206
|
+
const url2 = new URL(incomingUrl);
|
|
8207
|
+
req[urlKey] = url2.href;
|
|
8208
|
+
} catch (e) {
|
|
8209
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
8210
|
+
}
|
|
8211
|
+
return req;
|
|
8212
|
+
}
|
|
8213
|
+
const host = (incoming instanceof http2.Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
8214
|
+
if (!host) {
|
|
8215
|
+
throw new RequestError("Missing host header");
|
|
8216
|
+
}
|
|
8217
|
+
let scheme;
|
|
8218
|
+
if (incoming instanceof http2.Http2ServerRequest) {
|
|
8219
|
+
scheme = incoming.scheme;
|
|
8220
|
+
if (!(scheme === "http" || scheme === "https")) {
|
|
8221
|
+
throw new RequestError("Unsupported scheme");
|
|
8222
|
+
}
|
|
8223
|
+
} else {
|
|
8224
|
+
scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
8225
|
+
}
|
|
8226
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
8227
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
|
|
8228
|
+
throw new RequestError("Invalid host header");
|
|
8229
|
+
}
|
|
8230
|
+
req[urlKey] = url.href;
|
|
8231
|
+
return req;
|
|
8232
|
+
};
|
|
8233
|
+
var responseCache = /* @__PURE__ */ Symbol("responseCache");
|
|
8234
|
+
var getResponseCache = /* @__PURE__ */ Symbol("getResponseCache");
|
|
8235
|
+
var cacheKey = /* @__PURE__ */ Symbol("cache");
|
|
8236
|
+
var GlobalResponse = global.Response;
|
|
8237
|
+
var Response2 = class _Response {
|
|
8238
|
+
#body;
|
|
8239
|
+
#init;
|
|
8240
|
+
[getResponseCache]() {
|
|
8241
|
+
delete this[cacheKey];
|
|
8242
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
8243
|
+
}
|
|
8244
|
+
constructor(body, init) {
|
|
8245
|
+
let headers;
|
|
8246
|
+
this.#body = body;
|
|
8247
|
+
if (init instanceof _Response) {
|
|
8248
|
+
const cachedGlobalResponse = init[responseCache];
|
|
8249
|
+
if (cachedGlobalResponse) {
|
|
8250
|
+
this.#init = cachedGlobalResponse;
|
|
8251
|
+
this[getResponseCache]();
|
|
8252
|
+
return;
|
|
8253
|
+
} else {
|
|
8254
|
+
this.#init = init.#init;
|
|
8255
|
+
headers = new Headers(init.#init.headers);
|
|
8256
|
+
}
|
|
8257
|
+
} else {
|
|
8258
|
+
this.#init = init;
|
|
8259
|
+
}
|
|
8260
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
8261
|
+
headers ||= init?.headers || { "content-type": "text/plain; charset=UTF-8" };
|
|
8262
|
+
this[cacheKey] = [init?.status || 200, body, headers];
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
get headers() {
|
|
8266
|
+
const cache = this[cacheKey];
|
|
8267
|
+
if (cache) {
|
|
8268
|
+
if (!(cache[2] instanceof Headers)) {
|
|
8269
|
+
cache[2] = new Headers(cache[2]);
|
|
8270
|
+
}
|
|
8271
|
+
return cache[2];
|
|
8272
|
+
}
|
|
8273
|
+
return this[getResponseCache]().headers;
|
|
8274
|
+
}
|
|
8275
|
+
get status() {
|
|
8276
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
8277
|
+
}
|
|
8278
|
+
get ok() {
|
|
8279
|
+
const status = this.status;
|
|
8280
|
+
return status >= 200 && status < 300;
|
|
8281
|
+
}
|
|
8282
|
+
};
|
|
8283
|
+
["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k2) => {
|
|
8284
|
+
Object.defineProperty(Response2.prototype, k2, {
|
|
8285
|
+
get() {
|
|
8286
|
+
return this[getResponseCache]()[k2];
|
|
8287
|
+
}
|
|
8288
|
+
});
|
|
8289
|
+
});
|
|
8290
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k2) => {
|
|
8291
|
+
Object.defineProperty(Response2.prototype, k2, {
|
|
8292
|
+
value: function() {
|
|
8293
|
+
return this[getResponseCache]()[k2]();
|
|
8294
|
+
}
|
|
8295
|
+
});
|
|
8296
|
+
});
|
|
8297
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
8298
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
8299
|
+
async function readWithoutBlocking(readPromise) {
|
|
8300
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
|
|
8301
|
+
}
|
|
8302
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
8303
|
+
const cancel = (error) => {
|
|
8304
|
+
reader.cancel(error).catch(() => {
|
|
8305
|
+
});
|
|
8306
|
+
};
|
|
8307
|
+
writable.on("close", cancel);
|
|
8308
|
+
writable.on("error", cancel);
|
|
8309
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
8310
|
+
return reader.closed.finally(() => {
|
|
8311
|
+
writable.off("close", cancel);
|
|
8312
|
+
writable.off("error", cancel);
|
|
8313
|
+
});
|
|
8314
|
+
function handleStreamError(error) {
|
|
8315
|
+
if (error) {
|
|
8316
|
+
writable.destroy(error);
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
function onDrain() {
|
|
8320
|
+
reader.read().then(flow, handleStreamError);
|
|
8321
|
+
}
|
|
8322
|
+
function flow({ done, value }) {
|
|
8323
|
+
try {
|
|
8324
|
+
if (done) {
|
|
8325
|
+
writable.end();
|
|
8326
|
+
} else if (!writable.write(value)) {
|
|
8327
|
+
writable.once("drain", onDrain);
|
|
8328
|
+
} else {
|
|
8329
|
+
return reader.read().then(flow, handleStreamError);
|
|
8330
|
+
}
|
|
8331
|
+
} catch (e) {
|
|
8332
|
+
handleStreamError(e);
|
|
8333
|
+
}
|
|
8334
|
+
}
|
|
8335
|
+
}
|
|
8336
|
+
function writeFromReadableStream(stream2, writable) {
|
|
8337
|
+
if (stream2.locked) {
|
|
8338
|
+
throw new TypeError("ReadableStream is locked.");
|
|
8339
|
+
} else if (writable.destroyed) {
|
|
8340
|
+
return;
|
|
8341
|
+
}
|
|
8342
|
+
return writeFromReadableStreamDefaultReader(stream2.getReader(), writable);
|
|
8343
|
+
}
|
|
8344
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
8345
|
+
const res = {};
|
|
8346
|
+
if (!(headers instanceof Headers)) {
|
|
8347
|
+
headers = new Headers(headers ?? void 0);
|
|
8348
|
+
}
|
|
8349
|
+
const cookies = [];
|
|
8350
|
+
for (const [k2, v] of headers) {
|
|
8351
|
+
if (k2 === "set-cookie") {
|
|
8352
|
+
cookies.push(v);
|
|
8353
|
+
} else {
|
|
8354
|
+
res[k2] = v;
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
if (cookies.length > 0) {
|
|
8358
|
+
res["set-cookie"] = cookies;
|
|
8359
|
+
}
|
|
8360
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
8361
|
+
return res;
|
|
8362
|
+
};
|
|
8363
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
8364
|
+
var webFetch = global.fetch;
|
|
8365
|
+
if (typeof global.crypto === "undefined") {
|
|
8366
|
+
global.crypto = crypto$1;
|
|
8367
|
+
}
|
|
8368
|
+
global.fetch = (info, init) => {
|
|
8369
|
+
init = {
|
|
8370
|
+
// Disable compression handling so people can return the result of a fetch
|
|
8371
|
+
// directly in the loader without messing with the Content-Encoding header.
|
|
8372
|
+
compress: false,
|
|
8373
|
+
...init
|
|
8374
|
+
};
|
|
8375
|
+
return webFetch(info, init);
|
|
8376
|
+
};
|
|
8377
|
+
var outgoingEnded = /* @__PURE__ */ Symbol("outgoingEnded");
|
|
8378
|
+
var handleRequestError = () => new Response(null, {
|
|
8379
|
+
status: 400
|
|
8380
|
+
});
|
|
8381
|
+
var handleFetchError = (e) => new Response(null, {
|
|
8382
|
+
status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
|
|
8383
|
+
});
|
|
8384
|
+
var handleResponseError = (e, outgoing) => {
|
|
8385
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
8386
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
8387
|
+
console.info("The user aborted a request.");
|
|
8388
|
+
} else {
|
|
8389
|
+
console.error(e);
|
|
8390
|
+
if (!outgoing.headersSent) {
|
|
8391
|
+
outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
8392
|
+
}
|
|
8393
|
+
outgoing.end(`Error: ${err.message}`);
|
|
8394
|
+
outgoing.destroy(err);
|
|
8395
|
+
}
|
|
8396
|
+
};
|
|
8397
|
+
var flushHeaders = (outgoing) => {
|
|
8398
|
+
if ("flushHeaders" in outgoing && outgoing.writable) {
|
|
8399
|
+
outgoing.flushHeaders();
|
|
8400
|
+
}
|
|
8401
|
+
};
|
|
8402
|
+
var responseViaCache = async (res, outgoing) => {
|
|
8403
|
+
let [status, body, header] = res[cacheKey];
|
|
8404
|
+
if (header instanceof Headers) {
|
|
8405
|
+
header = buildOutgoingHttpHeaders(header);
|
|
8406
|
+
}
|
|
8407
|
+
if (typeof body === "string") {
|
|
8408
|
+
header["Content-Length"] = Buffer.byteLength(body);
|
|
8409
|
+
} else if (body instanceof Uint8Array) {
|
|
8410
|
+
header["Content-Length"] = body.byteLength;
|
|
8411
|
+
} else if (body instanceof Blob) {
|
|
8412
|
+
header["Content-Length"] = body.size;
|
|
8413
|
+
}
|
|
8414
|
+
outgoing.writeHead(status, header);
|
|
8415
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
8416
|
+
outgoing.end(body);
|
|
8417
|
+
} else if (body instanceof Blob) {
|
|
8418
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
8419
|
+
} else {
|
|
8420
|
+
flushHeaders(outgoing);
|
|
8421
|
+
await writeFromReadableStream(body, outgoing)?.catch(
|
|
8422
|
+
(e) => handleResponseError(e, outgoing)
|
|
8423
|
+
);
|
|
8424
|
+
}
|
|
8425
|
+
outgoing[outgoingEnded]?.();
|
|
8426
|
+
};
|
|
8427
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
8428
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
8429
|
+
if (isPromise(res)) {
|
|
8430
|
+
if (options.errorHandler) {
|
|
8431
|
+
try {
|
|
8432
|
+
res = await res;
|
|
8433
|
+
} catch (err) {
|
|
8434
|
+
const errRes = await options.errorHandler(err);
|
|
8435
|
+
if (!errRes) {
|
|
8436
|
+
return;
|
|
8437
|
+
}
|
|
8438
|
+
res = errRes;
|
|
8439
|
+
}
|
|
8440
|
+
} else {
|
|
8441
|
+
res = await res.catch(handleFetchError);
|
|
8442
|
+
}
|
|
8443
|
+
}
|
|
8444
|
+
if (cacheKey in res) {
|
|
8445
|
+
return responseViaCache(res, outgoing);
|
|
8446
|
+
}
|
|
8447
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
8448
|
+
if (res.body) {
|
|
8449
|
+
const reader = res.body.getReader();
|
|
8450
|
+
const values = [];
|
|
8451
|
+
let done = false;
|
|
8452
|
+
let currentReadPromise = void 0;
|
|
8453
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
8454
|
+
let maxReadCount = 2;
|
|
8455
|
+
for (let i = 0; i < maxReadCount; i++) {
|
|
8456
|
+
currentReadPromise ||= reader.read();
|
|
8457
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
8458
|
+
console.error(e);
|
|
8459
|
+
done = true;
|
|
8460
|
+
});
|
|
8461
|
+
if (!chunk) {
|
|
8462
|
+
if (i === 1) {
|
|
8463
|
+
await new Promise((resolve2) => setTimeout(resolve2));
|
|
8464
|
+
maxReadCount = 3;
|
|
8465
|
+
continue;
|
|
8466
|
+
}
|
|
8467
|
+
break;
|
|
8468
|
+
}
|
|
8469
|
+
currentReadPromise = void 0;
|
|
8470
|
+
if (chunk.value) {
|
|
8471
|
+
values.push(chunk.value);
|
|
8472
|
+
}
|
|
8473
|
+
if (chunk.done) {
|
|
8474
|
+
done = true;
|
|
8475
|
+
break;
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
if (done && !("content-length" in resHeaderRecord)) {
|
|
8479
|
+
resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
8480
|
+
}
|
|
8481
|
+
}
|
|
8482
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
8483
|
+
values.forEach((value) => {
|
|
8484
|
+
outgoing.write(value);
|
|
8485
|
+
});
|
|
8486
|
+
if (done) {
|
|
8487
|
+
outgoing.end();
|
|
8488
|
+
} else {
|
|
8489
|
+
if (values.length === 0) {
|
|
8490
|
+
flushHeaders(outgoing);
|
|
8491
|
+
}
|
|
8492
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
8493
|
+
}
|
|
8494
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) ;
|
|
8495
|
+
else {
|
|
8496
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
8497
|
+
outgoing.end();
|
|
8498
|
+
}
|
|
8499
|
+
outgoing[outgoingEnded]?.();
|
|
8500
|
+
};
|
|
8501
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
8502
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
8503
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request) {
|
|
8504
|
+
Object.defineProperty(global, "Request", {
|
|
8505
|
+
value: Request
|
|
8506
|
+
});
|
|
8507
|
+
Object.defineProperty(global, "Response", {
|
|
8508
|
+
value: Response2
|
|
8509
|
+
});
|
|
8510
|
+
}
|
|
8511
|
+
return async (incoming, outgoing) => {
|
|
8512
|
+
let res, req;
|
|
8513
|
+
try {
|
|
8514
|
+
req = newRequest(incoming, options.hostname);
|
|
8515
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
8516
|
+
if (!incomingEnded) {
|
|
8517
|
+
;
|
|
8518
|
+
incoming[wrapBodyStream] = true;
|
|
8519
|
+
incoming.on("end", () => {
|
|
8520
|
+
incomingEnded = true;
|
|
8521
|
+
});
|
|
8522
|
+
if (incoming instanceof http2.Http2ServerRequest) {
|
|
8523
|
+
;
|
|
8524
|
+
outgoing[outgoingEnded] = () => {
|
|
8525
|
+
if (!incomingEnded) {
|
|
8526
|
+
setTimeout(() => {
|
|
8527
|
+
if (!incomingEnded) {
|
|
8528
|
+
setTimeout(() => {
|
|
8529
|
+
incoming.destroy();
|
|
8530
|
+
outgoing.destroy();
|
|
8531
|
+
});
|
|
8532
|
+
}
|
|
8533
|
+
});
|
|
8534
|
+
}
|
|
8535
|
+
};
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
outgoing.on("close", () => {
|
|
8539
|
+
const abortController = req[abortControllerKey];
|
|
8540
|
+
if (abortController) {
|
|
8541
|
+
if (incoming.errored) {
|
|
8542
|
+
req[abortControllerKey].abort(incoming.errored.toString());
|
|
8543
|
+
} else if (!outgoing.writableFinished) {
|
|
8544
|
+
req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
8545
|
+
}
|
|
8546
|
+
}
|
|
8547
|
+
if (!incomingEnded) {
|
|
8548
|
+
setTimeout(() => {
|
|
8549
|
+
if (!incomingEnded) {
|
|
8550
|
+
setTimeout(() => {
|
|
8551
|
+
incoming.destroy();
|
|
8552
|
+
});
|
|
8553
|
+
}
|
|
8554
|
+
});
|
|
8555
|
+
}
|
|
8556
|
+
});
|
|
8557
|
+
res = fetchCallback(req, { incoming, outgoing });
|
|
8558
|
+
if (cacheKey in res) {
|
|
8559
|
+
return responseViaCache(res, outgoing);
|
|
8560
|
+
}
|
|
8561
|
+
} catch (e) {
|
|
8562
|
+
if (!res) {
|
|
8563
|
+
if (options.errorHandler) {
|
|
8564
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
8565
|
+
if (!res) {
|
|
8566
|
+
return;
|
|
8567
|
+
}
|
|
8568
|
+
} else if (!req) {
|
|
8569
|
+
res = handleRequestError();
|
|
8570
|
+
} else {
|
|
8571
|
+
res = handleFetchError(e);
|
|
8572
|
+
}
|
|
8573
|
+
} else {
|
|
8574
|
+
return handleResponseError(e, outgoing);
|
|
8575
|
+
}
|
|
8576
|
+
}
|
|
8577
|
+
try {
|
|
8578
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
8579
|
+
} catch (e) {
|
|
8580
|
+
return handleResponseError(e, outgoing);
|
|
8581
|
+
}
|
|
8582
|
+
};
|
|
8583
|
+
};
|
|
8584
|
+
class WebStandardStreamableHTTPServerTransport {
|
|
8585
|
+
constructor(options = {}) {
|
|
8586
|
+
this._started = false;
|
|
8587
|
+
this._streamMapping = /* @__PURE__ */ new Map();
|
|
8588
|
+
this._requestToStreamMapping = /* @__PURE__ */ new Map();
|
|
8589
|
+
this._requestResponseMap = /* @__PURE__ */ new Map();
|
|
8590
|
+
this._initialized = false;
|
|
8591
|
+
this._enableJsonResponse = false;
|
|
8592
|
+
this._standaloneSseStreamId = "_GET_stream";
|
|
8593
|
+
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
8594
|
+
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
8595
|
+
this._eventStore = options.eventStore;
|
|
8596
|
+
this._onsessioninitialized = options.onsessioninitialized;
|
|
8597
|
+
this._onsessionclosed = options.onsessionclosed;
|
|
8598
|
+
this._allowedHosts = options.allowedHosts;
|
|
8599
|
+
this._allowedOrigins = options.allowedOrigins;
|
|
8600
|
+
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
8601
|
+
this._retryInterval = options.retryInterval;
|
|
8602
|
+
}
|
|
8603
|
+
/**
|
|
8604
|
+
* Starts the transport. This is required by the Transport interface but is a no-op
|
|
8605
|
+
* for the Streamable HTTP transport as connections are managed per-request.
|
|
8606
|
+
*/
|
|
8607
|
+
async start() {
|
|
8608
|
+
if (this._started) {
|
|
8609
|
+
throw new Error("Transport already started");
|
|
8610
|
+
}
|
|
8611
|
+
this._started = true;
|
|
8612
|
+
}
|
|
8613
|
+
/**
|
|
8614
|
+
* Helper to create a JSON error response
|
|
8615
|
+
*/
|
|
8616
|
+
createJsonErrorResponse(status, code2, message, options) {
|
|
8617
|
+
const error = { code: code2, message };
|
|
8618
|
+
if (options?.data !== void 0) {
|
|
8619
|
+
error.data = options.data;
|
|
8620
|
+
}
|
|
8621
|
+
return new Response(JSON.stringify({
|
|
8622
|
+
jsonrpc: "2.0",
|
|
8623
|
+
error,
|
|
8624
|
+
id: null
|
|
8625
|
+
}), {
|
|
8626
|
+
status,
|
|
8627
|
+
headers: {
|
|
8628
|
+
"Content-Type": "application/json",
|
|
8629
|
+
...options?.headers
|
|
8630
|
+
}
|
|
8631
|
+
});
|
|
8632
|
+
}
|
|
8633
|
+
/**
|
|
8634
|
+
* Validates request headers for DNS rebinding protection.
|
|
8635
|
+
* @returns Error response if validation fails, undefined if validation passes.
|
|
8636
|
+
*/
|
|
8637
|
+
validateRequestHeaders(req) {
|
|
8638
|
+
if (!this._enableDnsRebindingProtection) {
|
|
8639
|
+
return void 0;
|
|
8640
|
+
}
|
|
8641
|
+
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
8642
|
+
const hostHeader = req.headers.get("host");
|
|
8643
|
+
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
8644
|
+
const error = `Invalid Host header: ${hostHeader}`;
|
|
8645
|
+
this.onerror?.(new Error(error));
|
|
8646
|
+
return this.createJsonErrorResponse(403, -32e3, error);
|
|
8647
|
+
}
|
|
8648
|
+
}
|
|
8649
|
+
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
8650
|
+
const originHeader = req.headers.get("origin");
|
|
8651
|
+
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
8652
|
+
const error = `Invalid Origin header: ${originHeader}`;
|
|
8653
|
+
this.onerror?.(new Error(error));
|
|
8654
|
+
return this.createJsonErrorResponse(403, -32e3, error);
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
return void 0;
|
|
8658
|
+
}
|
|
8659
|
+
/**
|
|
8660
|
+
* Handles an incoming HTTP request, whether GET, POST, or DELETE
|
|
8661
|
+
* Returns a Response object (Web Standard)
|
|
8662
|
+
*/
|
|
8663
|
+
async handleRequest(req, options) {
|
|
8664
|
+
const validationError = this.validateRequestHeaders(req);
|
|
8665
|
+
if (validationError) {
|
|
8666
|
+
return validationError;
|
|
8667
|
+
}
|
|
8668
|
+
switch (req.method) {
|
|
8669
|
+
case "POST":
|
|
8670
|
+
return this.handlePostRequest(req, options);
|
|
8671
|
+
case "GET":
|
|
8672
|
+
return this.handleGetRequest(req);
|
|
8673
|
+
case "DELETE":
|
|
8674
|
+
return this.handleDeleteRequest(req);
|
|
8675
|
+
default:
|
|
8676
|
+
return this.handleUnsupportedRequest();
|
|
8677
|
+
}
|
|
8678
|
+
}
|
|
8679
|
+
/**
|
|
8680
|
+
* Writes a priming event to establish resumption capability.
|
|
8681
|
+
* Only sends if eventStore is configured (opt-in for resumability) and
|
|
8682
|
+
* the client's protocol version supports empty SSE data (>= 2025-11-25).
|
|
8683
|
+
*/
|
|
8684
|
+
async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
|
|
8685
|
+
if (!this._eventStore) {
|
|
8686
|
+
return;
|
|
8687
|
+
}
|
|
8688
|
+
if (protocolVersion < "2025-11-25") {
|
|
8689
|
+
return;
|
|
8690
|
+
}
|
|
8691
|
+
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
8692
|
+
let primingEvent = `id: ${primingEventId}
|
|
8693
|
+
data:
|
|
8694
|
+
|
|
8695
|
+
`;
|
|
8696
|
+
if (this._retryInterval !== void 0) {
|
|
8697
|
+
primingEvent = `id: ${primingEventId}
|
|
8698
|
+
retry: ${this._retryInterval}
|
|
8699
|
+
data:
|
|
8700
|
+
|
|
8701
|
+
`;
|
|
8702
|
+
}
|
|
8703
|
+
controller.enqueue(encoder.encode(primingEvent));
|
|
8704
|
+
}
|
|
8705
|
+
/**
|
|
8706
|
+
* Handles GET requests for SSE stream
|
|
8707
|
+
*/
|
|
8708
|
+
async handleGetRequest(req) {
|
|
8709
|
+
const acceptHeader = req.headers.get("accept");
|
|
8710
|
+
if (!acceptHeader?.includes("text/event-stream")) {
|
|
8711
|
+
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept text/event-stream");
|
|
8712
|
+
}
|
|
8713
|
+
const sessionError = this.validateSession(req);
|
|
8714
|
+
if (sessionError) {
|
|
8715
|
+
return sessionError;
|
|
8716
|
+
}
|
|
8717
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
8718
|
+
if (protocolError) {
|
|
8719
|
+
return protocolError;
|
|
8720
|
+
}
|
|
8721
|
+
if (this._eventStore) {
|
|
8722
|
+
const lastEventId = req.headers.get("last-event-id");
|
|
8723
|
+
if (lastEventId) {
|
|
8724
|
+
return this.replayEvents(lastEventId);
|
|
8725
|
+
}
|
|
8726
|
+
}
|
|
8727
|
+
if (this._streamMapping.get(this._standaloneSseStreamId) !== void 0) {
|
|
8728
|
+
return this.createJsonErrorResponse(409, -32e3, "Conflict: Only one SSE stream is allowed per session");
|
|
8729
|
+
}
|
|
8730
|
+
const encoder = new TextEncoder();
|
|
8731
|
+
let streamController;
|
|
8732
|
+
const readable = new ReadableStream({
|
|
8733
|
+
start: (controller) => {
|
|
8734
|
+
streamController = controller;
|
|
8735
|
+
},
|
|
8736
|
+
cancel: () => {
|
|
8737
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
8738
|
+
}
|
|
8739
|
+
});
|
|
8740
|
+
const headers = {
|
|
8741
|
+
"Content-Type": "text/event-stream",
|
|
8742
|
+
"Cache-Control": "no-cache, no-transform",
|
|
8743
|
+
Connection: "keep-alive"
|
|
8744
|
+
};
|
|
8745
|
+
if (this.sessionId !== void 0) {
|
|
8746
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
8747
|
+
}
|
|
8748
|
+
this._streamMapping.set(this._standaloneSseStreamId, {
|
|
8749
|
+
controller: streamController,
|
|
8750
|
+
encoder,
|
|
8751
|
+
cleanup: () => {
|
|
8752
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
8753
|
+
try {
|
|
8754
|
+
streamController.close();
|
|
8755
|
+
} catch {
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
});
|
|
8759
|
+
return new Response(readable, { headers });
|
|
8760
|
+
}
|
|
8761
|
+
/**
|
|
8762
|
+
* Replays events that would have been sent after the specified event ID
|
|
8763
|
+
* Only used when resumability is enabled
|
|
8764
|
+
*/
|
|
8765
|
+
async replayEvents(lastEventId) {
|
|
8766
|
+
if (!this._eventStore) {
|
|
8767
|
+
return this.createJsonErrorResponse(400, -32e3, "Event store not configured");
|
|
8768
|
+
}
|
|
8769
|
+
try {
|
|
8770
|
+
let streamId;
|
|
8771
|
+
if (this._eventStore.getStreamIdForEventId) {
|
|
8772
|
+
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
8773
|
+
if (!streamId) {
|
|
8774
|
+
return this.createJsonErrorResponse(400, -32e3, "Invalid event ID format");
|
|
8775
|
+
}
|
|
8776
|
+
if (this._streamMapping.get(streamId) !== void 0) {
|
|
8777
|
+
return this.createJsonErrorResponse(409, -32e3, "Conflict: Stream already has an active connection");
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
const headers = {
|
|
8781
|
+
"Content-Type": "text/event-stream",
|
|
8782
|
+
"Cache-Control": "no-cache, no-transform",
|
|
8783
|
+
Connection: "keep-alive"
|
|
8784
|
+
};
|
|
8785
|
+
if (this.sessionId !== void 0) {
|
|
8786
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
8787
|
+
}
|
|
8788
|
+
const encoder = new TextEncoder();
|
|
8789
|
+
let streamController;
|
|
8790
|
+
const readable = new ReadableStream({
|
|
8791
|
+
start: (controller) => {
|
|
8792
|
+
streamController = controller;
|
|
8793
|
+
},
|
|
8794
|
+
cancel: () => {
|
|
8795
|
+
}
|
|
8796
|
+
});
|
|
8797
|
+
const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
8798
|
+
send: async (eventId, message) => {
|
|
8799
|
+
const success = this.writeSSEEvent(streamController, encoder, message, eventId);
|
|
8800
|
+
if (!success) {
|
|
8801
|
+
this.onerror?.(new Error("Failed replay events"));
|
|
8802
|
+
try {
|
|
8803
|
+
streamController.close();
|
|
8804
|
+
} catch {
|
|
8805
|
+
}
|
|
8806
|
+
}
|
|
8807
|
+
}
|
|
8808
|
+
});
|
|
8809
|
+
this._streamMapping.set(replayedStreamId, {
|
|
8810
|
+
controller: streamController,
|
|
8811
|
+
encoder,
|
|
8812
|
+
cleanup: () => {
|
|
8813
|
+
this._streamMapping.delete(replayedStreamId);
|
|
8814
|
+
try {
|
|
8815
|
+
streamController.close();
|
|
8816
|
+
} catch {
|
|
8817
|
+
}
|
|
8818
|
+
}
|
|
8819
|
+
});
|
|
8820
|
+
return new Response(readable, { headers });
|
|
8821
|
+
} catch (error) {
|
|
8822
|
+
this.onerror?.(error);
|
|
8823
|
+
return this.createJsonErrorResponse(500, -32e3, "Error replaying events");
|
|
8824
|
+
}
|
|
8825
|
+
}
|
|
8826
|
+
/**
|
|
8827
|
+
* Writes an event to an SSE stream via controller with proper formatting
|
|
8828
|
+
*/
|
|
8829
|
+
writeSSEEvent(controller, encoder, message, eventId) {
|
|
8830
|
+
try {
|
|
8831
|
+
let eventData = `event: message
|
|
8832
|
+
`;
|
|
8833
|
+
if (eventId) {
|
|
8834
|
+
eventData += `id: ${eventId}
|
|
8835
|
+
`;
|
|
8836
|
+
}
|
|
8837
|
+
eventData += `data: ${JSON.stringify(message)}
|
|
8838
|
+
|
|
8839
|
+
`;
|
|
8840
|
+
controller.enqueue(encoder.encode(eventData));
|
|
8841
|
+
return true;
|
|
8842
|
+
} catch {
|
|
8843
|
+
return false;
|
|
8844
|
+
}
|
|
8845
|
+
}
|
|
8846
|
+
/**
|
|
8847
|
+
* Handles unsupported requests (PUT, PATCH, etc.)
|
|
8848
|
+
*/
|
|
8849
|
+
handleUnsupportedRequest() {
|
|
8850
|
+
return new Response(JSON.stringify({
|
|
8851
|
+
jsonrpc: "2.0",
|
|
8852
|
+
error: {
|
|
8853
|
+
code: -32e3,
|
|
8854
|
+
message: "Method not allowed."
|
|
8855
|
+
},
|
|
8856
|
+
id: null
|
|
8857
|
+
}), {
|
|
8858
|
+
status: 405,
|
|
8859
|
+
headers: {
|
|
8860
|
+
Allow: "GET, POST, DELETE",
|
|
8861
|
+
"Content-Type": "application/json"
|
|
8862
|
+
}
|
|
8863
|
+
});
|
|
8864
|
+
}
|
|
8865
|
+
/**
|
|
8866
|
+
* Handles POST requests containing JSON-RPC messages
|
|
8867
|
+
*/
|
|
8868
|
+
async handlePostRequest(req, options) {
|
|
8869
|
+
try {
|
|
8870
|
+
const acceptHeader = req.headers.get("accept");
|
|
8871
|
+
if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
|
|
8872
|
+
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
8873
|
+
}
|
|
8874
|
+
const ct2 = req.headers.get("content-type");
|
|
8875
|
+
if (!ct2 || !ct2.includes("application/json")) {
|
|
8876
|
+
return this.createJsonErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
|
|
8877
|
+
}
|
|
8878
|
+
const requestInfo = {
|
|
8879
|
+
headers: Object.fromEntries(req.headers.entries())
|
|
8880
|
+
};
|
|
8881
|
+
let rawMessage;
|
|
8882
|
+
if (options?.parsedBody !== void 0) {
|
|
8883
|
+
rawMessage = options.parsedBody;
|
|
8884
|
+
} else {
|
|
8885
|
+
try {
|
|
8886
|
+
rawMessage = await req.json();
|
|
8887
|
+
} catch {
|
|
8888
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
8889
|
+
}
|
|
8890
|
+
}
|
|
8891
|
+
let messages;
|
|
8892
|
+
try {
|
|
8893
|
+
if (Array.isArray(rawMessage)) {
|
|
8894
|
+
messages = rawMessage.map((msg) => protocol.JSONRPCMessageSchema.parse(msg));
|
|
8895
|
+
} else {
|
|
8896
|
+
messages = [protocol.JSONRPCMessageSchema.parse(rawMessage)];
|
|
8897
|
+
}
|
|
8898
|
+
} catch {
|
|
8899
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
8900
|
+
}
|
|
8901
|
+
const isInitializationRequest = messages.some(protocol.isInitializeRequest);
|
|
8902
|
+
if (isInitializationRequest) {
|
|
8903
|
+
if (this._initialized && this.sessionId !== void 0) {
|
|
8904
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
|
|
8905
|
+
}
|
|
8906
|
+
if (messages.length > 1) {
|
|
8907
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
|
|
8908
|
+
}
|
|
8909
|
+
this.sessionId = this.sessionIdGenerator?.();
|
|
8910
|
+
this._initialized = true;
|
|
8911
|
+
if (this.sessionId && this._onsessioninitialized) {
|
|
8912
|
+
await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
8913
|
+
}
|
|
8914
|
+
}
|
|
8915
|
+
if (!isInitializationRequest) {
|
|
8916
|
+
const sessionError = this.validateSession(req);
|
|
8917
|
+
if (sessionError) {
|
|
8918
|
+
return sessionError;
|
|
8919
|
+
}
|
|
8920
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
8921
|
+
if (protocolError) {
|
|
8922
|
+
return protocolError;
|
|
8923
|
+
}
|
|
8924
|
+
}
|
|
8925
|
+
const hasRequests = messages.some(protocol.isJSONRPCRequest);
|
|
8926
|
+
if (!hasRequests) {
|
|
8927
|
+
for (const message of messages) {
|
|
8928
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
8929
|
+
}
|
|
8930
|
+
return new Response(null, { status: 202 });
|
|
8931
|
+
}
|
|
8932
|
+
const streamId = crypto.randomUUID();
|
|
8933
|
+
const initRequest = messages.find((m2) => protocol.isInitializeRequest(m2));
|
|
8934
|
+
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? protocol.DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
8935
|
+
if (this._enableJsonResponse) {
|
|
8936
|
+
return new Promise((resolve2) => {
|
|
8937
|
+
this._streamMapping.set(streamId, {
|
|
8938
|
+
resolveJson: resolve2,
|
|
8939
|
+
cleanup: () => {
|
|
8940
|
+
this._streamMapping.delete(streamId);
|
|
8941
|
+
}
|
|
8942
|
+
});
|
|
8943
|
+
for (const message of messages) {
|
|
8944
|
+
if (protocol.isJSONRPCRequest(message)) {
|
|
8945
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
8946
|
+
}
|
|
8947
|
+
}
|
|
8948
|
+
for (const message of messages) {
|
|
8949
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
8950
|
+
}
|
|
8951
|
+
});
|
|
8952
|
+
}
|
|
8953
|
+
const encoder = new TextEncoder();
|
|
8954
|
+
let streamController;
|
|
8955
|
+
const readable = new ReadableStream({
|
|
8956
|
+
start: (controller) => {
|
|
8957
|
+
streamController = controller;
|
|
8958
|
+
},
|
|
8959
|
+
cancel: () => {
|
|
8960
|
+
this._streamMapping.delete(streamId);
|
|
8961
|
+
}
|
|
8962
|
+
});
|
|
8963
|
+
const headers = {
|
|
8964
|
+
"Content-Type": "text/event-stream",
|
|
8965
|
+
"Cache-Control": "no-cache",
|
|
8966
|
+
Connection: "keep-alive"
|
|
8967
|
+
};
|
|
8968
|
+
if (this.sessionId !== void 0) {
|
|
8969
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
8970
|
+
}
|
|
8971
|
+
for (const message of messages) {
|
|
8972
|
+
if (protocol.isJSONRPCRequest(message)) {
|
|
8973
|
+
this._streamMapping.set(streamId, {
|
|
8974
|
+
controller: streamController,
|
|
8975
|
+
encoder,
|
|
8976
|
+
cleanup: () => {
|
|
8977
|
+
this._streamMapping.delete(streamId);
|
|
8978
|
+
try {
|
|
8979
|
+
streamController.close();
|
|
8980
|
+
} catch {
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
});
|
|
8984
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
8985
|
+
}
|
|
8986
|
+
}
|
|
8987
|
+
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
8988
|
+
for (const message of messages) {
|
|
8989
|
+
let closeSSEStream;
|
|
8990
|
+
let closeStandaloneSSEStream;
|
|
8991
|
+
if (protocol.isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
|
|
8992
|
+
closeSSEStream = () => {
|
|
8993
|
+
this.closeSSEStream(message.id);
|
|
8994
|
+
};
|
|
8995
|
+
closeStandaloneSSEStream = () => {
|
|
8996
|
+
this.closeStandaloneSSEStream();
|
|
8997
|
+
};
|
|
8998
|
+
}
|
|
8999
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
9000
|
+
}
|
|
9001
|
+
return new Response(readable, { status: 200, headers });
|
|
9002
|
+
} catch (error) {
|
|
9003
|
+
this.onerror?.(error);
|
|
9004
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error) });
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
9007
|
+
/**
|
|
9008
|
+
* Handles DELETE requests to terminate sessions
|
|
9009
|
+
*/
|
|
9010
|
+
async handleDeleteRequest(req) {
|
|
9011
|
+
const sessionError = this.validateSession(req);
|
|
9012
|
+
if (sessionError) {
|
|
9013
|
+
return sessionError;
|
|
9014
|
+
}
|
|
9015
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
9016
|
+
if (protocolError) {
|
|
9017
|
+
return protocolError;
|
|
9018
|
+
}
|
|
9019
|
+
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
9020
|
+
await this.close();
|
|
9021
|
+
return new Response(null, { status: 200 });
|
|
9022
|
+
}
|
|
9023
|
+
/**
|
|
9024
|
+
* Validates session ID for non-initialization requests.
|
|
9025
|
+
* Returns Response error if invalid, undefined otherwise
|
|
9026
|
+
*/
|
|
9027
|
+
validateSession(req) {
|
|
9028
|
+
if (this.sessionIdGenerator === void 0) {
|
|
9029
|
+
return void 0;
|
|
9030
|
+
}
|
|
9031
|
+
if (!this._initialized) {
|
|
9032
|
+
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Server not initialized");
|
|
9033
|
+
}
|
|
9034
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
9035
|
+
if (!sessionId) {
|
|
9036
|
+
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Mcp-Session-Id header is required");
|
|
9037
|
+
}
|
|
9038
|
+
if (sessionId !== this.sessionId) {
|
|
9039
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
9040
|
+
}
|
|
9041
|
+
return void 0;
|
|
9042
|
+
}
|
|
9043
|
+
/**
|
|
9044
|
+
* Validates the MCP-Protocol-Version header on incoming requests.
|
|
9045
|
+
*
|
|
9046
|
+
* For initialization: Version negotiation handles unknown versions gracefully
|
|
9047
|
+
* (server responds with its supported version).
|
|
9048
|
+
*
|
|
9049
|
+
* For subsequent requests with MCP-Protocol-Version header:
|
|
9050
|
+
* - Accept if in supported list
|
|
9051
|
+
* - 400 if unsupported
|
|
9052
|
+
*
|
|
9053
|
+
* For HTTP requests without the MCP-Protocol-Version header:
|
|
9054
|
+
* - Accept and default to the version negotiated at initialization
|
|
9055
|
+
*/
|
|
9056
|
+
validateProtocolVersion(req) {
|
|
9057
|
+
const protocolVersion = req.headers.get("mcp-protocol-version");
|
|
9058
|
+
if (protocolVersion !== null && !protocol.SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
|
|
9059
|
+
return this.createJsonErrorResponse(400, -32e3, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${protocol.SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
|
|
9060
|
+
}
|
|
9061
|
+
return void 0;
|
|
9062
|
+
}
|
|
9063
|
+
async close() {
|
|
9064
|
+
this._streamMapping.forEach(({ cleanup }) => {
|
|
9065
|
+
cleanup();
|
|
9066
|
+
});
|
|
9067
|
+
this._streamMapping.clear();
|
|
9068
|
+
this._requestResponseMap.clear();
|
|
9069
|
+
this.onclose?.();
|
|
9070
|
+
}
|
|
9071
|
+
/**
|
|
9072
|
+
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
9073
|
+
* Use this to implement polling behavior during long-running operations -
|
|
9074
|
+
* client will reconnect after the retry interval specified in the priming event.
|
|
9075
|
+
*/
|
|
9076
|
+
closeSSEStream(requestId) {
|
|
9077
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
9078
|
+
if (!streamId)
|
|
9079
|
+
return;
|
|
9080
|
+
const stream2 = this._streamMapping.get(streamId);
|
|
9081
|
+
if (stream2) {
|
|
9082
|
+
stream2.cleanup();
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
/**
|
|
9086
|
+
* Close the standalone GET SSE stream, triggering client reconnection.
|
|
9087
|
+
* Use this to implement polling behavior for server-initiated notifications.
|
|
9088
|
+
*/
|
|
9089
|
+
closeStandaloneSSEStream() {
|
|
9090
|
+
const stream2 = this._streamMapping.get(this._standaloneSseStreamId);
|
|
9091
|
+
if (stream2) {
|
|
9092
|
+
stream2.cleanup();
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
async send(message, options) {
|
|
9096
|
+
let requestId = options?.relatedRequestId;
|
|
9097
|
+
if (protocol.isJSONRPCResultResponse(message) || protocol.isJSONRPCErrorResponse(message)) {
|
|
9098
|
+
requestId = message.id;
|
|
9099
|
+
}
|
|
9100
|
+
if (requestId === void 0) {
|
|
9101
|
+
if (protocol.isJSONRPCResultResponse(message) || protocol.isJSONRPCErrorResponse(message)) {
|
|
9102
|
+
throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
|
|
9103
|
+
}
|
|
9104
|
+
let eventId;
|
|
9105
|
+
if (this._eventStore) {
|
|
9106
|
+
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
9107
|
+
}
|
|
9108
|
+
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
9109
|
+
if (standaloneSse === void 0) {
|
|
9110
|
+
return;
|
|
9111
|
+
}
|
|
9112
|
+
if (standaloneSse.controller && standaloneSse.encoder) {
|
|
9113
|
+
this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
9114
|
+
}
|
|
9115
|
+
return;
|
|
9116
|
+
}
|
|
9117
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
9118
|
+
if (!streamId) {
|
|
9119
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
9120
|
+
}
|
|
9121
|
+
const stream2 = this._streamMapping.get(streamId);
|
|
9122
|
+
if (!this._enableJsonResponse && stream2?.controller && stream2?.encoder) {
|
|
9123
|
+
let eventId;
|
|
9124
|
+
if (this._eventStore) {
|
|
9125
|
+
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
9126
|
+
}
|
|
9127
|
+
this.writeSSEEvent(stream2.controller, stream2.encoder, message, eventId);
|
|
9128
|
+
}
|
|
9129
|
+
if (protocol.isJSONRPCResultResponse(message) || protocol.isJSONRPCErrorResponse(message)) {
|
|
9130
|
+
this._requestResponseMap.set(requestId, message);
|
|
9131
|
+
const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id2]) => id2);
|
|
9132
|
+
const allResponsesReady = relatedIds.every((id2) => this._requestResponseMap.has(id2));
|
|
9133
|
+
if (allResponsesReady) {
|
|
9134
|
+
if (!stream2) {
|
|
9135
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
9136
|
+
}
|
|
9137
|
+
if (this._enableJsonResponse && stream2.resolveJson) {
|
|
9138
|
+
const headers = {
|
|
9139
|
+
"Content-Type": "application/json"
|
|
9140
|
+
};
|
|
9141
|
+
if (this.sessionId !== void 0) {
|
|
9142
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
9143
|
+
}
|
|
9144
|
+
const responses = relatedIds.map((id2) => this._requestResponseMap.get(id2));
|
|
9145
|
+
if (responses.length === 1) {
|
|
9146
|
+
stream2.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
|
|
9147
|
+
} else {
|
|
9148
|
+
stream2.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
|
|
9149
|
+
}
|
|
9150
|
+
} else {
|
|
9151
|
+
stream2.cleanup();
|
|
9152
|
+
}
|
|
9153
|
+
for (const id2 of relatedIds) {
|
|
9154
|
+
this._requestResponseMap.delete(id2);
|
|
9155
|
+
this._requestToStreamMapping.delete(id2);
|
|
9156
|
+
}
|
|
9157
|
+
}
|
|
9158
|
+
}
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
class StreamableHTTPServerTransport {
|
|
9162
|
+
constructor(options = {}) {
|
|
9163
|
+
this._requestContext = /* @__PURE__ */ new WeakMap();
|
|
9164
|
+
this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);
|
|
9165
|
+
this._requestListener = getRequestListener(async (webRequest) => {
|
|
9166
|
+
const context = this._requestContext.get(webRequest);
|
|
9167
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
9168
|
+
authInfo: context?.authInfo,
|
|
9169
|
+
parsedBody: context?.parsedBody
|
|
9170
|
+
});
|
|
9171
|
+
});
|
|
9172
|
+
}
|
|
9173
|
+
/**
|
|
9174
|
+
* Gets the session ID for this transport instance.
|
|
9175
|
+
*/
|
|
9176
|
+
get sessionId() {
|
|
9177
|
+
return this._webStandardTransport.sessionId;
|
|
9178
|
+
}
|
|
9179
|
+
/**
|
|
9180
|
+
* Sets callback for when the transport is closed.
|
|
9181
|
+
*/
|
|
9182
|
+
set onclose(handler) {
|
|
9183
|
+
this._webStandardTransport.onclose = handler;
|
|
9184
|
+
}
|
|
9185
|
+
get onclose() {
|
|
9186
|
+
return this._webStandardTransport.onclose;
|
|
9187
|
+
}
|
|
9188
|
+
/**
|
|
9189
|
+
* Sets callback for transport errors.
|
|
9190
|
+
*/
|
|
9191
|
+
set onerror(handler) {
|
|
9192
|
+
this._webStandardTransport.onerror = handler;
|
|
9193
|
+
}
|
|
9194
|
+
get onerror() {
|
|
9195
|
+
return this._webStandardTransport.onerror;
|
|
9196
|
+
}
|
|
9197
|
+
/**
|
|
9198
|
+
* Sets callback for incoming messages.
|
|
9199
|
+
*/
|
|
9200
|
+
set onmessage(handler) {
|
|
9201
|
+
this._webStandardTransport.onmessage = handler;
|
|
9202
|
+
}
|
|
9203
|
+
get onmessage() {
|
|
9204
|
+
return this._webStandardTransport.onmessage;
|
|
9205
|
+
}
|
|
9206
|
+
/**
|
|
9207
|
+
* Starts the transport. This is required by the Transport interface but is a no-op
|
|
9208
|
+
* for the Streamable HTTP transport as connections are managed per-request.
|
|
9209
|
+
*/
|
|
9210
|
+
async start() {
|
|
9211
|
+
return this._webStandardTransport.start();
|
|
9212
|
+
}
|
|
9213
|
+
/**
|
|
9214
|
+
* Closes the transport and all active connections.
|
|
9215
|
+
*/
|
|
9216
|
+
async close() {
|
|
9217
|
+
return this._webStandardTransport.close();
|
|
9218
|
+
}
|
|
9219
|
+
/**
|
|
9220
|
+
* Sends a JSON-RPC message through the transport.
|
|
9221
|
+
*/
|
|
9222
|
+
async send(message, options) {
|
|
9223
|
+
return this._webStandardTransport.send(message, options);
|
|
9224
|
+
}
|
|
9225
|
+
/**
|
|
9226
|
+
* Handles an incoming HTTP request, whether GET or POST.
|
|
9227
|
+
*
|
|
9228
|
+
* This method converts Node.js HTTP objects to Web Standard Request/Response
|
|
9229
|
+
* and delegates to the underlying WebStandardStreamableHTTPServerTransport.
|
|
9230
|
+
*
|
|
9231
|
+
* @param req - Node.js IncomingMessage, optionally with auth property from middleware
|
|
9232
|
+
* @param res - Node.js ServerResponse
|
|
9233
|
+
* @param parsedBody - Optional pre-parsed body from body-parser middleware
|
|
9234
|
+
*/
|
|
9235
|
+
async handleRequest(req, res, parsedBody) {
|
|
9236
|
+
const authInfo = req.auth;
|
|
9237
|
+
const handler = getRequestListener(async (webRequest) => {
|
|
9238
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
9239
|
+
authInfo,
|
|
9240
|
+
parsedBody
|
|
9241
|
+
});
|
|
9242
|
+
});
|
|
9243
|
+
await handler(req, res);
|
|
9244
|
+
}
|
|
9245
|
+
/**
|
|
9246
|
+
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
9247
|
+
* Use this to implement polling behavior during long-running operations -
|
|
9248
|
+
* client will reconnect after the retry interval specified in the priming event.
|
|
9249
|
+
*/
|
|
9250
|
+
closeSSEStream(requestId) {
|
|
9251
|
+
this._webStandardTransport.closeSSEStream(requestId);
|
|
9252
|
+
}
|
|
9253
|
+
/**
|
|
9254
|
+
* Close the standalone GET SSE stream, triggering client reconnection.
|
|
9255
|
+
* Use this to implement polling behavior for server-initiated notifications.
|
|
9256
|
+
*/
|
|
9257
|
+
closeStandaloneSSEStream() {
|
|
9258
|
+
this._webStandardTransport.closeStandaloneSSEStream();
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
8048
9261
|
var QI = Object.defineProperty;
|
|
8049
9262
|
var s = (r, i) => {
|
|
8050
9263
|
for (var o in i) QI(r, o, { get: i[o], enumerable: true, configurable: true, set: (t) => i[o] = () => t });
|
|
@@ -14396,81 +15609,105 @@ function createAppServer(config, simulations, viteMode) {
|
|
|
14396
15609
|
);
|
|
14397
15610
|
return mcpServer;
|
|
14398
15611
|
}
|
|
15612
|
+
const SESSION_IDLE_TIMEOUT_MS$1 = 5 * 60 * 1e3;
|
|
14399
15613
|
function isLocalConnection(req) {
|
|
14400
15614
|
const addr = req.socket.remoteAddress;
|
|
14401
15615
|
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
14402
15616
|
}
|
|
14403
15617
|
const sessions = /* @__PURE__ */ new Map();
|
|
14404
|
-
const
|
|
14405
|
-
const
|
|
14406
|
-
|
|
14407
|
-
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
const
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
15618
|
+
const MCP_PATH$1 = "/mcp";
|
|
15619
|
+
const CORS_HEADERS$1 = {
|
|
15620
|
+
"Access-Control-Allow-Origin": "*",
|
|
15621
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
15622
|
+
"Access-Control-Allow-Headers": "content-type, accept, authorization, mcp-session-id, ngrok-skip-browser-warning",
|
|
15623
|
+
"Access-Control-Expose-Headers": "mcp-session-id"
|
|
15624
|
+
};
|
|
15625
|
+
const cleanupInterval = setInterval(() => {
|
|
15626
|
+
const now = Date.now();
|
|
15627
|
+
for (const [id2, session] of sessions) {
|
|
15628
|
+
if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS$1) {
|
|
15629
|
+
sessions.delete(id2);
|
|
15630
|
+
session.transport.close?.();
|
|
15631
|
+
session.server.close();
|
|
15632
|
+
console.log(`[MCP] Session expired: ${id2.substring(0, 8)}... (${sessions.size} active)`);
|
|
15633
|
+
}
|
|
15634
|
+
}
|
|
15635
|
+
}, 6e4);
|
|
15636
|
+
cleanupInterval.unref();
|
|
15637
|
+
async function handleMcpRequest(req, res, config, simulations, viteMode) {
|
|
15638
|
+
for (const [key, value] of Object.entries(CORS_HEADERS$1)) {
|
|
15639
|
+
res.setHeader(key, value);
|
|
15640
|
+
}
|
|
15641
|
+
let parsedBody;
|
|
15642
|
+
if (req.method === "POST") {
|
|
15643
|
+
const chunks = [];
|
|
15644
|
+
await new Promise((resolve2, reject) => {
|
|
15645
|
+
req.on("data", (chunk) => {
|
|
15646
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
15647
|
+
});
|
|
15648
|
+
req.on("end", resolve2);
|
|
15649
|
+
req.on("error", reject);
|
|
15650
|
+
});
|
|
15651
|
+
const rawBody = Buffer.concat(chunks).toString("utf8");
|
|
15652
|
+
try {
|
|
15653
|
+
parsedBody = JSON.parse(rawBody);
|
|
15654
|
+
const parsed = parsedBody;
|
|
15655
|
+
if (parsed.method) {
|
|
15656
|
+
const sid = req.headers["mcp-session-id"];
|
|
15657
|
+
const sidStr = sid ? ` (${sid.substring(0, 8)}...)` : "";
|
|
15658
|
+
const extra = parsed.method === "resources/read" ? ` uri=${JSON.stringify(parsed.params?.uri)}` : "";
|
|
15659
|
+
console.log(`[MCP] ← ${parsed.method}${extra}${sidStr}`);
|
|
15660
|
+
}
|
|
15661
|
+
} catch {
|
|
15662
|
+
res.writeHead(400).end("Invalid JSON");
|
|
15663
|
+
return;
|
|
14433
15664
|
}
|
|
14434
15665
|
}
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
const session = sessions.get(sessionId);
|
|
14445
|
-
if (!session) {
|
|
14446
|
-
res.writeHead(404).end("Unknown session");
|
|
15666
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
15667
|
+
if (sessionId) {
|
|
15668
|
+
const session = sessions.get(sessionId);
|
|
15669
|
+
if (!session) {
|
|
15670
|
+
res.writeHead(404).end("Unknown session");
|
|
15671
|
+
return;
|
|
15672
|
+
}
|
|
15673
|
+
session.lastActivity = Date.now();
|
|
15674
|
+
await session.transport.handleRequest(req, res, parsedBody);
|
|
14447
15675
|
return;
|
|
14448
15676
|
}
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
15677
|
+
if (req.method === "POST") {
|
|
15678
|
+
const isLocal = isLocalConnection(req);
|
|
15679
|
+
const server = createAppServer(config, simulations, viteMode);
|
|
15680
|
+
const transport = new StreamableHTTPServerTransport({
|
|
15681
|
+
sessionIdGenerator: () => node_crypto.randomUUID(),
|
|
15682
|
+
onsessioninitialized: (id2) => {
|
|
15683
|
+
sessions.set(id2, { server, transport, isLocal, lastActivity: Date.now() });
|
|
15684
|
+
const origin = isLocal ? "local" : "tunnel";
|
|
15685
|
+
console.log(
|
|
15686
|
+
`[MCP] Session started: ${id2.substring(0, 8)}... (${origin}, ${sessions.size} active)`
|
|
15687
|
+
);
|
|
15688
|
+
},
|
|
15689
|
+
onsessionclosed: (id2) => {
|
|
15690
|
+
sessions.delete(id2);
|
|
15691
|
+
console.log(`[MCP] Session closed: ${id2.substring(0, 8)}... (${sessions.size} active)`);
|
|
15692
|
+
}
|
|
14453
15693
|
});
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
await
|
|
14468
|
-
|
|
14469
|
-
console.error("Failed to process message", error);
|
|
14470
|
-
if (!res.headersSent) {
|
|
14471
|
-
res.writeHead(500).end("Failed to process message");
|
|
14472
|
-
}
|
|
15694
|
+
transport.onerror = (error) => {
|
|
15695
|
+
const id2 = transport.sessionId;
|
|
15696
|
+
console.error(`[MCP] Transport error${id2 ? ` (${id2.substring(0, 8)}...)` : ""}:`, error);
|
|
15697
|
+
};
|
|
15698
|
+
transport.onclose = async () => {
|
|
15699
|
+
const id2 = transport.sessionId;
|
|
15700
|
+
if (id2 && sessions.has(id2)) {
|
|
15701
|
+
sessions.delete(id2);
|
|
15702
|
+
console.log(`[MCP] Session closed: ${id2.substring(0, 8)}... (${sessions.size} active)`);
|
|
15703
|
+
}
|
|
15704
|
+
await server.close();
|
|
15705
|
+
};
|
|
15706
|
+
await server.connect(transport);
|
|
15707
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
15708
|
+
return;
|
|
14473
15709
|
}
|
|
15710
|
+
res.writeHead(400).end("Bad Request: session ID required");
|
|
14474
15711
|
}
|
|
14475
15712
|
function runMCPServer(config) {
|
|
14476
15713
|
const portEnv = Number(process.env.PORT ?? 8e3);
|
|
@@ -14485,11 +15722,7 @@ function runMCPServer(config) {
|
|
|
14485
15722
|
}
|
|
14486
15723
|
const url = new node_url.URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
14487
15724
|
if (req.method === "OPTIONS") {
|
|
14488
|
-
res.writeHead(204,
|
|
14489
|
-
"Access-Control-Allow-Origin": "*",
|
|
14490
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
14491
|
-
"Access-Control-Allow-Headers": "content-type, ngrok-skip-browser-warning"
|
|
14492
|
-
});
|
|
15725
|
+
res.writeHead(204, CORS_HEADERS$1);
|
|
14493
15726
|
res.end();
|
|
14494
15727
|
return;
|
|
14495
15728
|
}
|
|
@@ -14519,14 +15752,8 @@ function runMCPServer(config) {
|
|
|
14519
15752
|
res.end(FAVICON_BUFFER);
|
|
14520
15753
|
return;
|
|
14521
15754
|
}
|
|
14522
|
-
if (
|
|
14523
|
-
|
|
14524
|
-
await handleSseRequest(req, res, config, simulations, viteMode);
|
|
14525
|
-
return;
|
|
14526
|
-
}
|
|
14527
|
-
if (req.method === "POST" && url.pathname === postPath) {
|
|
14528
|
-
console.log(`[HTTP] ${req.method} ${url.pathname}`);
|
|
14529
|
-
await handlePostMessage(req, res, url);
|
|
15755
|
+
if (url.pathname === MCP_PATH$1) {
|
|
15756
|
+
await handleMcpRequest(req, res, config, simulations, viteMode);
|
|
14530
15757
|
return;
|
|
14531
15758
|
}
|
|
14532
15759
|
if (viteServer) {
|
|
@@ -14549,7 +15776,7 @@ function runMCPServer(config) {
|
|
|
14549
15776
|
});
|
|
14550
15777
|
httpServer.listen(port, () => {
|
|
14551
15778
|
console.log(`Sunpeak MCP server listening on http://localhost:${port}`);
|
|
14552
|
-
console.log(`
|
|
15779
|
+
console.log(` MCP endpoint: http://localhost:${port}${MCP_PATH$1}`);
|
|
14553
15780
|
if (viteMode) {
|
|
14554
15781
|
console.log(` Vite HMR: enabled (source files served with hot reload)`);
|
|
14555
15782
|
}
|
|
@@ -14582,13 +15809,375 @@ function runMCPServer(config) {
|
|
|
14582
15809
|
}
|
|
14583
15810
|
};
|
|
14584
15811
|
}
|
|
15812
|
+
function createProductionMcpServer(config) {
|
|
15813
|
+
const { name = "sunpeak-app", version = "0.1.0", tools, resources } = config;
|
|
15814
|
+
const mcpServer = new McpServer(
|
|
15815
|
+
{
|
|
15816
|
+
name,
|
|
15817
|
+
version,
|
|
15818
|
+
icons: [
|
|
15819
|
+
{
|
|
15820
|
+
src: `data:image/png;base64,${FAVICON_BASE64}`,
|
|
15821
|
+
mimeType: "image/png",
|
|
15822
|
+
sizes: ["128x128"]
|
|
15823
|
+
}
|
|
15824
|
+
]
|
|
15825
|
+
},
|
|
15826
|
+
{ capabilities: { resources: {}, tools: {} } }
|
|
15827
|
+
);
|
|
15828
|
+
const resourceByName = /* @__PURE__ */ new Map();
|
|
15829
|
+
for (const res of resources) {
|
|
15830
|
+
resourceByName.set(res.name, res);
|
|
15831
|
+
}
|
|
15832
|
+
const registeredResources = /* @__PURE__ */ new Set();
|
|
15833
|
+
for (const tool of tools) {
|
|
15834
|
+
const res = resourceByName.get(tool.tool.resource);
|
|
15835
|
+
if (!res) {
|
|
15836
|
+
console.warn(
|
|
15837
|
+
`[MCP] Warning: Resource "${tool.tool.resource}" not found for tool "${tool.name}". Skipping.`
|
|
15838
|
+
);
|
|
15839
|
+
continue;
|
|
15840
|
+
}
|
|
15841
|
+
if (!registeredResources.has(res.uri)) {
|
|
15842
|
+
registeredResources.add(res.uri);
|
|
15843
|
+
ak(
|
|
15844
|
+
mcpServer,
|
|
15845
|
+
res.name,
|
|
15846
|
+
res.uri,
|
|
15847
|
+
{
|
|
15848
|
+
description: res.description,
|
|
15849
|
+
_meta: res._meta
|
|
15850
|
+
},
|
|
15851
|
+
async () => ({
|
|
15852
|
+
contents: [
|
|
15853
|
+
{
|
|
15854
|
+
uri: res.uri,
|
|
15855
|
+
mimeType: EI,
|
|
15856
|
+
text: res.html,
|
|
15857
|
+
_meta: res._meta
|
|
15858
|
+
}
|
|
15859
|
+
]
|
|
15860
|
+
})
|
|
15861
|
+
);
|
|
15862
|
+
}
|
|
15863
|
+
const toolConfig = {
|
|
15864
|
+
title: tool.tool.title,
|
|
15865
|
+
description: tool.tool.description,
|
|
15866
|
+
annotations: tool.tool.annotations,
|
|
15867
|
+
...tool.schema ? { inputSchema: tool.schema } : {},
|
|
15868
|
+
_meta: {
|
|
15869
|
+
...tool.tool._meta,
|
|
15870
|
+
ui: {
|
|
15871
|
+
resourceUri: res.uri,
|
|
15872
|
+
...tool.tool._meta?.ui ?? {}
|
|
15873
|
+
}
|
|
15874
|
+
}
|
|
15875
|
+
};
|
|
15876
|
+
const callback = async (...cbArgs) => {
|
|
15877
|
+
const hasSchema = !!tool.schema;
|
|
15878
|
+
const args = hasSchema ? cbArgs[0] : {};
|
|
15879
|
+
const extra = hasSchema ? cbArgs[1] : cbArgs[0];
|
|
15880
|
+
const argKeys = Object.keys(args);
|
|
15881
|
+
const argsStr = argKeys.length > 0 ? `{${argKeys.join(", ")}}` : "{}";
|
|
15882
|
+
console.log(`[MCP] CallTool: ${tool.name}${argsStr}`);
|
|
15883
|
+
const result = await tool.handler(args, extra);
|
|
15884
|
+
if (typeof result === "string") {
|
|
15885
|
+
return { content: [{ type: "text", text: result }] };
|
|
15886
|
+
}
|
|
15887
|
+
return result;
|
|
15888
|
+
};
|
|
15889
|
+
hk(
|
|
15890
|
+
mcpServer,
|
|
15891
|
+
tool.name,
|
|
15892
|
+
toolConfig,
|
|
15893
|
+
callback
|
|
15894
|
+
);
|
|
15895
|
+
}
|
|
15896
|
+
const toolCount = tools.filter((t) => resourceByName.has(t.tool.resource)).length;
|
|
15897
|
+
const resourceCount = registeredResources.size;
|
|
15898
|
+
console.log(`[MCP] Registered ${toolCount} tool(s) and ${resourceCount} resource(s)`);
|
|
15899
|
+
return mcpServer;
|
|
15900
|
+
}
|
|
15901
|
+
const MCP_PATH = "/mcp";
|
|
15902
|
+
const SESSION_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
15903
|
+
function isJsonRpcMessage(value) {
|
|
15904
|
+
return typeof value === "object" && value !== null && typeof value.method === "string";
|
|
15905
|
+
}
|
|
15906
|
+
const CORS_HEADERS = {
|
|
15907
|
+
"Access-Control-Allow-Origin": "*",
|
|
15908
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
15909
|
+
"Access-Control-Allow-Headers": "content-type, accept, authorization, mcp-session-id, ngrok-skip-browser-warning",
|
|
15910
|
+
"Access-Control-Expose-Headers": "mcp-session-id"
|
|
15911
|
+
};
|
|
15912
|
+
function createMcpHandler(config) {
|
|
15913
|
+
const sessions2 = /* @__PURE__ */ new Map();
|
|
15914
|
+
const authFn = config.auth;
|
|
15915
|
+
const cleanupInterval2 = setInterval(() => {
|
|
15916
|
+
const now = Date.now();
|
|
15917
|
+
for (const [id2, session] of sessions2) {
|
|
15918
|
+
if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS) {
|
|
15919
|
+
sessions2.delete(id2);
|
|
15920
|
+
session.transport.close?.();
|
|
15921
|
+
session.server.close();
|
|
15922
|
+
console.log(`[MCP] Session expired: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
15923
|
+
}
|
|
15924
|
+
}
|
|
15925
|
+
}, 6e4);
|
|
15926
|
+
cleanupInterval2.unref();
|
|
15927
|
+
return async (req, res) => {
|
|
15928
|
+
if (!req.url) return;
|
|
15929
|
+
const url = new node_url.URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
15930
|
+
if (url.pathname !== MCP_PATH) return;
|
|
15931
|
+
if (req.method === "OPTIONS") {
|
|
15932
|
+
res.writeHead(204, CORS_HEADERS);
|
|
15933
|
+
res.end();
|
|
15934
|
+
return;
|
|
15935
|
+
}
|
|
15936
|
+
for (const [key, value] of Object.entries(CORS_HEADERS)) {
|
|
15937
|
+
res.setHeader(key, value);
|
|
15938
|
+
}
|
|
15939
|
+
if (authFn) {
|
|
15940
|
+
const authInfo = await authFn(req);
|
|
15941
|
+
if (!authInfo) {
|
|
15942
|
+
res.writeHead(401, { "WWW-Authenticate": "Bearer" });
|
|
15943
|
+
res.end("Unauthorized");
|
|
15944
|
+
return;
|
|
15945
|
+
}
|
|
15946
|
+
req.auth = authInfo;
|
|
15947
|
+
}
|
|
15948
|
+
let parsedBody;
|
|
15949
|
+
if (req.method === "POST") {
|
|
15950
|
+
const chunks = [];
|
|
15951
|
+
await new Promise((resolve2, reject) => {
|
|
15952
|
+
req.on("data", (chunk) => {
|
|
15953
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
15954
|
+
});
|
|
15955
|
+
req.on("end", resolve2);
|
|
15956
|
+
req.on("error", reject);
|
|
15957
|
+
});
|
|
15958
|
+
const rawBody = Buffer.concat(chunks).toString("utf8");
|
|
15959
|
+
try {
|
|
15960
|
+
parsedBody = JSON.parse(rawBody);
|
|
15961
|
+
if (isJsonRpcMessage(parsedBody)) {
|
|
15962
|
+
const sid = req.headers["mcp-session-id"];
|
|
15963
|
+
const sidStr = sid ? ` (${sid.substring(0, 8)}...)` : "";
|
|
15964
|
+
const extra = parsedBody.method === "resources/read" ? ` uri=${JSON.stringify(parsedBody.params?.uri)}` : "";
|
|
15965
|
+
console.log(`[MCP] ← ${parsedBody.method}${extra}${sidStr}`);
|
|
15966
|
+
}
|
|
15967
|
+
} catch {
|
|
15968
|
+
res.writeHead(400).end("Invalid JSON");
|
|
15969
|
+
return;
|
|
15970
|
+
}
|
|
15971
|
+
}
|
|
15972
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
15973
|
+
if (sessionId) {
|
|
15974
|
+
const session = sessions2.get(sessionId);
|
|
15975
|
+
if (!session) {
|
|
15976
|
+
res.writeHead(404).end("Unknown session");
|
|
15977
|
+
return;
|
|
15978
|
+
}
|
|
15979
|
+
session.lastActivity = Date.now();
|
|
15980
|
+
await session.transport.handleRequest(req, res, parsedBody);
|
|
15981
|
+
return;
|
|
15982
|
+
}
|
|
15983
|
+
if (req.method === "POST") {
|
|
15984
|
+
const server = createProductionMcpServer(config);
|
|
15985
|
+
const transport = new StreamableHTTPServerTransport({
|
|
15986
|
+
sessionIdGenerator: () => node_crypto.randomUUID(),
|
|
15987
|
+
onsessioninitialized: (id2) => {
|
|
15988
|
+
sessions2.set(id2, { server, transport, lastActivity: Date.now() });
|
|
15989
|
+
console.log(`[MCP] Session started: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
15990
|
+
},
|
|
15991
|
+
onsessionclosed: (id2) => {
|
|
15992
|
+
sessions2.delete(id2);
|
|
15993
|
+
console.log(`[MCP] Session closed: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
15994
|
+
}
|
|
15995
|
+
});
|
|
15996
|
+
transport.onerror = (error) => {
|
|
15997
|
+
const id2 = transport.sessionId;
|
|
15998
|
+
console.error(`[MCP] Transport error${id2 ? ` (${id2.substring(0, 8)}...)` : ""}:`, error);
|
|
15999
|
+
};
|
|
16000
|
+
transport.onclose = async () => {
|
|
16001
|
+
const id2 = transport.sessionId;
|
|
16002
|
+
if (id2 && sessions2.has(id2)) {
|
|
16003
|
+
sessions2.delete(id2);
|
|
16004
|
+
console.log(`[MCP] Session closed: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
16005
|
+
}
|
|
16006
|
+
await server.close();
|
|
16007
|
+
};
|
|
16008
|
+
await server.connect(transport);
|
|
16009
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
16010
|
+
return;
|
|
16011
|
+
}
|
|
16012
|
+
res.writeHead(400).end("Bad Request: session ID required");
|
|
16013
|
+
};
|
|
16014
|
+
}
|
|
16015
|
+
function createHandler(config) {
|
|
16016
|
+
const sessions2 = /* @__PURE__ */ new Map();
|
|
16017
|
+
const authFn = config.auth;
|
|
16018
|
+
const cleanupInterval2 = setInterval(() => {
|
|
16019
|
+
const now = Date.now();
|
|
16020
|
+
for (const [id2, session] of sessions2) {
|
|
16021
|
+
if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS) {
|
|
16022
|
+
sessions2.delete(id2);
|
|
16023
|
+
session.transport.close?.();
|
|
16024
|
+
session.server.close();
|
|
16025
|
+
console.log(`[MCP] Session expired: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
16026
|
+
}
|
|
16027
|
+
}
|
|
16028
|
+
}, 6e4);
|
|
16029
|
+
cleanupInterval2.unref();
|
|
16030
|
+
return async (req) => {
|
|
16031
|
+
if (req.method === "OPTIONS") {
|
|
16032
|
+
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
|
16033
|
+
}
|
|
16034
|
+
let authInfo;
|
|
16035
|
+
if (authFn) {
|
|
16036
|
+
const result = await authFn(req);
|
|
16037
|
+
if (!result) {
|
|
16038
|
+
return new Response("Unauthorized", {
|
|
16039
|
+
status: 401,
|
|
16040
|
+
headers: { "WWW-Authenticate": "Bearer", "Access-Control-Allow-Origin": "*" }
|
|
16041
|
+
});
|
|
16042
|
+
}
|
|
16043
|
+
authInfo = result;
|
|
16044
|
+
}
|
|
16045
|
+
let parsedBody;
|
|
16046
|
+
if (req.method === "POST") {
|
|
16047
|
+
try {
|
|
16048
|
+
parsedBody = await req.json();
|
|
16049
|
+
} catch {
|
|
16050
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
16051
|
+
}
|
|
16052
|
+
}
|
|
16053
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
16054
|
+
if (sessionId) {
|
|
16055
|
+
const session = sessions2.get(sessionId);
|
|
16056
|
+
if (!session) {
|
|
16057
|
+
return new Response("Unknown session", { status: 404 });
|
|
16058
|
+
}
|
|
16059
|
+
session.lastActivity = Date.now();
|
|
16060
|
+
const response = await session.transport.handleRequest(req, { parsedBody, authInfo });
|
|
16061
|
+
return addCorsHeaders(response);
|
|
16062
|
+
}
|
|
16063
|
+
if (req.method === "POST") {
|
|
16064
|
+
const { name, version, tools, resources } = config;
|
|
16065
|
+
const server = createProductionMcpServer({ name, version, tools, resources });
|
|
16066
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
16067
|
+
sessionIdGenerator: () => node_crypto.randomUUID(),
|
|
16068
|
+
onsessioninitialized: (id2) => {
|
|
16069
|
+
sessions2.set(id2, { server, transport, lastActivity: Date.now() });
|
|
16070
|
+
console.log(`[MCP] Session started: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
16071
|
+
},
|
|
16072
|
+
onsessionclosed: (id2) => {
|
|
16073
|
+
sessions2.delete(id2);
|
|
16074
|
+
console.log(`[MCP] Session closed: ${id2.substring(0, 8)}... (${sessions2.size} active)`);
|
|
16075
|
+
}
|
|
16076
|
+
});
|
|
16077
|
+
transport.onerror = (error) => {
|
|
16078
|
+
console.error("[MCP] Transport error:", error);
|
|
16079
|
+
};
|
|
16080
|
+
transport.onclose = async () => {
|
|
16081
|
+
const id2 = transport.sessionId;
|
|
16082
|
+
if (id2 && sessions2.has(id2)) {
|
|
16083
|
+
sessions2.delete(id2);
|
|
16084
|
+
}
|
|
16085
|
+
await server.close();
|
|
16086
|
+
};
|
|
16087
|
+
await server.connect(transport);
|
|
16088
|
+
const response = await transport.handleRequest(req, { parsedBody, authInfo });
|
|
16089
|
+
return addCorsHeaders(response);
|
|
16090
|
+
}
|
|
16091
|
+
return new Response("Bad Request: session ID required", { status: 400 });
|
|
16092
|
+
};
|
|
16093
|
+
}
|
|
16094
|
+
function addCorsHeaders(response) {
|
|
16095
|
+
const headers = new Headers(response.headers);
|
|
16096
|
+
headers.set("Access-Control-Allow-Origin", "*");
|
|
16097
|
+
headers.set("Access-Control-Expose-Headers", "mcp-session-id");
|
|
16098
|
+
return new Response(response.body, {
|
|
16099
|
+
status: response.status,
|
|
16100
|
+
statusText: response.statusText,
|
|
16101
|
+
headers
|
|
16102
|
+
});
|
|
16103
|
+
}
|
|
16104
|
+
function startProductionHttpServer(config, port) {
|
|
16105
|
+
const mcpHandler = createMcpHandler(config);
|
|
16106
|
+
const httpServer = node_http.createServer(async (req, res) => {
|
|
16107
|
+
if (!req.url) {
|
|
16108
|
+
res.writeHead(400).end("Missing URL");
|
|
16109
|
+
return;
|
|
16110
|
+
}
|
|
16111
|
+
const url = new node_url.URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
16112
|
+
if (req.method === "OPTIONS") {
|
|
16113
|
+
res.writeHead(204, CORS_HEADERS);
|
|
16114
|
+
res.end();
|
|
16115
|
+
return;
|
|
16116
|
+
}
|
|
16117
|
+
if (req.method === "GET" && url.pathname === "/") {
|
|
16118
|
+
res.writeHead(200, {
|
|
16119
|
+
"Content-Type": "text/html",
|
|
16120
|
+
"Access-Control-Allow-Origin": "*"
|
|
16121
|
+
});
|
|
16122
|
+
res.end(`<!DOCTYPE html>
|
|
16123
|
+
<html>
|
|
16124
|
+
<head>
|
|
16125
|
+
<meta charset="UTF-8" />
|
|
16126
|
+
<link rel="icon" type="image/png" href="/favicon.ico" />
|
|
16127
|
+
<title>Sunpeak MCP Server</title>
|
|
16128
|
+
</head>
|
|
16129
|
+
<body><h1>Sunpeak MCP Server</h1><p>Connect via <a href="/mcp">/mcp</a></p></body>
|
|
16130
|
+
</html>`);
|
|
16131
|
+
return;
|
|
16132
|
+
}
|
|
16133
|
+
if (req.method === "GET" && url.pathname === "/favicon.ico") {
|
|
16134
|
+
res.writeHead(200, {
|
|
16135
|
+
"Content-Type": "image/png",
|
|
16136
|
+
"Content-Length": FAVICON_BUFFER.length,
|
|
16137
|
+
"Cache-Control": "public, max-age=86400",
|
|
16138
|
+
"Access-Control-Allow-Origin": "*"
|
|
16139
|
+
});
|
|
16140
|
+
res.end(FAVICON_BUFFER);
|
|
16141
|
+
return;
|
|
16142
|
+
}
|
|
16143
|
+
await mcpHandler(req, res);
|
|
16144
|
+
if (!res.headersSent) {
|
|
16145
|
+
res.writeHead(404).end("Not Found");
|
|
16146
|
+
}
|
|
16147
|
+
});
|
|
16148
|
+
httpServer.on("clientError", (err, socket) => {
|
|
16149
|
+
console.error("HTTP client error", err);
|
|
16150
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
16151
|
+
});
|
|
16152
|
+
httpServer.listen(port, () => {
|
|
16153
|
+
console.log(`Sunpeak MCP server listening on http://localhost:${port}`);
|
|
16154
|
+
console.log(` MCP endpoint: http://localhost:${port}${MCP_PATH}`);
|
|
16155
|
+
});
|
|
16156
|
+
const shutdown = async () => {
|
|
16157
|
+
console.log("\nShutting down MCP server...");
|
|
16158
|
+
httpServer.close(() => {
|
|
16159
|
+
console.log("MCP server closed");
|
|
16160
|
+
process.exit(0);
|
|
16161
|
+
});
|
|
16162
|
+
setTimeout(() => {
|
|
16163
|
+
console.error("Force closing MCP server");
|
|
16164
|
+
process.exit(1);
|
|
16165
|
+
}, 5e3);
|
|
16166
|
+
};
|
|
16167
|
+
process.on("SIGTERM", () => void shutdown());
|
|
16168
|
+
process.on("SIGINT", () => void shutdown());
|
|
16169
|
+
}
|
|
14585
16170
|
exports.EXTENSION_ID = C6;
|
|
14586
16171
|
exports.FAVICON_BASE64 = FAVICON_BASE64;
|
|
14587
16172
|
exports.FAVICON_BUFFER = FAVICON_BUFFER;
|
|
14588
16173
|
exports.RESOURCE_MIME_TYPE = EI;
|
|
14589
16174
|
exports.RESOURCE_URI_META_KEY = je;
|
|
16175
|
+
exports.createHandler = createHandler;
|
|
16176
|
+
exports.createMcpHandler = createMcpHandler;
|
|
16177
|
+
exports.createProductionMcpServer = createProductionMcpServer;
|
|
14590
16178
|
exports.getUiCapability = pk;
|
|
14591
16179
|
exports.registerAppResource = ak;
|
|
14592
16180
|
exports.registerAppTool = hk;
|
|
14593
16181
|
exports.runMCPServer = runMCPServer;
|
|
16182
|
+
exports.startProductionHttpServer = startProductionHttpServer;
|
|
14594
16183
|
//# sourceMappingURL=index.cjs.map
|