@adapt-toolkit/a2adapt 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +422 -0
- package/dist/hooks/runner.js +115 -17
- package/dist/index.js +2050 -89
- package/dist/mufl_code/A93F52302D67D1F28269D3F35657610A0FA140AABC521D667A262C101A7AC090.muflo +0 -0
- package/dist/mufl_code/actor.mu +287 -20
- package/dist/mufl_code/config.mufl +20 -17
- package/package.json +25 -6
package/dist/index.js
CHANGED
|
@@ -1317,11 +1317,11 @@ var require_errors = __commonJS({
|
|
|
1317
1317
|
gen.code((0, codegen_1._)`${names_1.default.errors}++`);
|
|
1318
1318
|
}
|
|
1319
1319
|
function returnErrors(it, errs) {
|
|
1320
|
-
const { gen, validateName, schemaEnv } = it;
|
|
1320
|
+
const { gen, validateName: validateName2, schemaEnv } = it;
|
|
1321
1321
|
if (schemaEnv.$async) {
|
|
1322
1322
|
gen.throw((0, codegen_1._)`new ${it.ValidationError}(${errs})`);
|
|
1323
1323
|
} else {
|
|
1324
|
-
gen.assign((0, codegen_1._)`${
|
|
1324
|
+
gen.assign((0, codegen_1._)`${validateName2}.errors`, errs);
|
|
1325
1325
|
gen.return(false);
|
|
1326
1326
|
}
|
|
1327
1327
|
}
|
|
@@ -1390,13 +1390,13 @@ var require_boolSchema = __commonJS({
|
|
|
1390
1390
|
message: "boolean schema is false"
|
|
1391
1391
|
};
|
|
1392
1392
|
function topBoolOrEmptySchema(it) {
|
|
1393
|
-
const { gen, schema, validateName } = it;
|
|
1393
|
+
const { gen, schema, validateName: validateName2 } = it;
|
|
1394
1394
|
if (schema === false) {
|
|
1395
1395
|
falseSchemaError(it, false);
|
|
1396
1396
|
} else if (typeof schema == "object" && schema.$async === true) {
|
|
1397
1397
|
gen.return(names_1.default.data);
|
|
1398
1398
|
} else {
|
|
1399
|
-
gen.assign((0, codegen_1._)`${
|
|
1399
|
+
gen.assign((0, codegen_1._)`${validateName2}.errors`, null);
|
|
1400
1400
|
gen.return(true);
|
|
1401
1401
|
}
|
|
1402
1402
|
}
|
|
@@ -2345,15 +2345,15 @@ var require_validate = __commonJS({
|
|
|
2345
2345
|
validateFunction(it, () => (0, boolSchema_1.topBoolOrEmptySchema)(it));
|
|
2346
2346
|
}
|
|
2347
2347
|
exports.validateFunctionCode = validateFunctionCode;
|
|
2348
|
-
function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) {
|
|
2348
|
+
function validateFunction({ gen, validateName: validateName2, schema, schemaEnv, opts }, body) {
|
|
2349
2349
|
if (opts.code.es5) {
|
|
2350
|
-
gen.func(
|
|
2350
|
+
gen.func(validateName2, (0, codegen_1._)`${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => {
|
|
2351
2351
|
gen.code((0, codegen_1._)`"use strict"; ${funcSourceUrl(schema, opts)}`);
|
|
2352
2352
|
destructureValCxtES5(gen, opts);
|
|
2353
2353
|
gen.code(body);
|
|
2354
2354
|
});
|
|
2355
2355
|
} else {
|
|
2356
|
-
gen.func(
|
|
2356
|
+
gen.func(validateName2, (0, codegen_1._)`${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body));
|
|
2357
2357
|
}
|
|
2358
2358
|
}
|
|
2359
2359
|
function destructureValCxt(opts) {
|
|
@@ -2392,8 +2392,8 @@ var require_validate = __commonJS({
|
|
|
2392
2392
|
return;
|
|
2393
2393
|
}
|
|
2394
2394
|
function resetEvaluated(it) {
|
|
2395
|
-
const { gen, validateName } = it;
|
|
2396
|
-
it.evaluated = gen.const("evaluated", (0, codegen_1._)`${
|
|
2395
|
+
const { gen, validateName: validateName2 } = it;
|
|
2396
|
+
it.evaluated = gen.const("evaluated", (0, codegen_1._)`${validateName2}.evaluated`);
|
|
2397
2397
|
gen.if((0, codegen_1._)`${it.evaluated}.dynamicProps`, () => gen.assign((0, codegen_1._)`${it.evaluated}.props`, (0, codegen_1._)`undefined`));
|
|
2398
2398
|
gen.if((0, codegen_1._)`${it.evaluated}.dynamicItems`, () => gen.assign((0, codegen_1._)`${it.evaluated}.items`, (0, codegen_1._)`undefined`));
|
|
2399
2399
|
}
|
|
@@ -2475,11 +2475,11 @@ var require_validate = __commonJS({
|
|
|
2475
2475
|
}
|
|
2476
2476
|
}
|
|
2477
2477
|
function returnResults(it) {
|
|
2478
|
-
const { gen, schemaEnv, validateName, ValidationError, opts } = it;
|
|
2478
|
+
const { gen, schemaEnv, validateName: validateName2, ValidationError, opts } = it;
|
|
2479
2479
|
if (schemaEnv.$async) {
|
|
2480
2480
|
gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError}(${names_1.default.vErrors})`));
|
|
2481
2481
|
} else {
|
|
2482
|
-
gen.assign((0, codegen_1._)`${
|
|
2482
|
+
gen.assign((0, codegen_1._)`${validateName2}.errors`, names_1.default.vErrors);
|
|
2483
2483
|
if (opts.unevaluated)
|
|
2484
2484
|
assignEvaluated(it);
|
|
2485
2485
|
gen.return((0, codegen_1._)`${names_1.default.errors} === 0`);
|
|
@@ -2904,8 +2904,8 @@ var require_compile = __commonJS({
|
|
|
2904
2904
|
code: (0, codegen_1._)`require("ajv/dist/runtime/validation_error").default`
|
|
2905
2905
|
});
|
|
2906
2906
|
}
|
|
2907
|
-
const
|
|
2908
|
-
sch.validateName =
|
|
2907
|
+
const validateName2 = gen.scopeName("validate");
|
|
2908
|
+
sch.validateName = validateName2;
|
|
2909
2909
|
const schemaCxt = {
|
|
2910
2910
|
gen,
|
|
2911
2911
|
allErrors: this.opts.allErrors,
|
|
@@ -2919,7 +2919,7 @@ var require_compile = __commonJS({
|
|
|
2919
2919
|
dataTypes: [],
|
|
2920
2920
|
definedProperties: /* @__PURE__ */ new Set(),
|
|
2921
2921
|
topSchemaRef: gen.scopeValue("schema", this.opts.code.source === true ? { ref: sch.schema, code: (0, codegen_1.stringify)(sch.schema) } : { ref: sch.schema }),
|
|
2922
|
-
validateName,
|
|
2922
|
+
validateName: validateName2,
|
|
2923
2923
|
ValidationError: _ValidationError,
|
|
2924
2924
|
schema: sch.schema,
|
|
2925
2925
|
schemaEnv: sch,
|
|
@@ -2942,14 +2942,14 @@ var require_compile = __commonJS({
|
|
|
2942
2942
|
sourceCode = this.opts.code.process(sourceCode, sch);
|
|
2943
2943
|
const makeValidate = new Function(`${names_1.default.self}`, `${names_1.default.scope}`, sourceCode);
|
|
2944
2944
|
const validate = makeValidate(this, this.scope.get());
|
|
2945
|
-
this.scope.value(
|
|
2945
|
+
this.scope.value(validateName2, { ref: validate });
|
|
2946
2946
|
validate.errors = null;
|
|
2947
2947
|
validate.schema = sch.schema;
|
|
2948
2948
|
validate.schemaEnv = sch;
|
|
2949
2949
|
if (sch.$async)
|
|
2950
2950
|
validate.$async = true;
|
|
2951
2951
|
if (this.opts.code.source === true) {
|
|
2952
|
-
validate.source = { validateName, validateCode, scopeValues: gen._values };
|
|
2952
|
+
validate.source = { validateName: validateName2, validateCode, scopeValues: gen._values };
|
|
2953
2953
|
}
|
|
2954
2954
|
if (this.opts.unevaluated) {
|
|
2955
2955
|
const { props, items } = schemaCxt;
|
|
@@ -4235,8 +4235,8 @@ var require_core = __commonJS({
|
|
|
4235
4235
|
return this;
|
|
4236
4236
|
}
|
|
4237
4237
|
case "object": {
|
|
4238
|
-
const
|
|
4239
|
-
this._cache.delete(
|
|
4238
|
+
const cacheKey2 = schemaKeyRef;
|
|
4239
|
+
this._cache.delete(cacheKey2);
|
|
4240
4240
|
let id = schemaKeyRef[this.opts.schemaId];
|
|
4241
4241
|
if (id) {
|
|
4242
4242
|
id = (0, resolve_1.normalizeId)(id);
|
|
@@ -4401,11 +4401,11 @@ var require_core = __commonJS({
|
|
|
4401
4401
|
Ajv2.ValidationError = validation_error_1.default;
|
|
4402
4402
|
Ajv2.MissingRefError = ref_error_1.default;
|
|
4403
4403
|
exports.default = Ajv2;
|
|
4404
|
-
function checkOptions(checkOpts, options, msg,
|
|
4404
|
+
function checkOptions(checkOpts, options, msg, log2 = "error") {
|
|
4405
4405
|
for (const key in checkOpts) {
|
|
4406
4406
|
const opt = key;
|
|
4407
4407
|
if (opt in options)
|
|
4408
|
-
this.logger[
|
|
4408
|
+
this.logger[log2](`${msg}: option ${key}. ${checkOpts[opt]}`);
|
|
4409
4409
|
}
|
|
4410
4410
|
}
|
|
4411
4411
|
function getSchEnv(keyRef) {
|
|
@@ -4563,7 +4563,7 @@ var require_ref = __commonJS({
|
|
|
4563
4563
|
schemaType: "string",
|
|
4564
4564
|
code(cxt) {
|
|
4565
4565
|
const { gen, schema: $ref, it } = cxt;
|
|
4566
|
-
const { baseId, schemaEnv: env, validateName, opts, self } = it;
|
|
4566
|
+
const { baseId, schemaEnv: env, validateName: validateName2, opts, self } = it;
|
|
4567
4567
|
const { root } = env;
|
|
4568
4568
|
if (($ref === "#" || $ref === "#/") && baseId === root.baseId)
|
|
4569
4569
|
return callRootRef();
|
|
@@ -4575,7 +4575,7 @@ var require_ref = __commonJS({
|
|
|
4575
4575
|
return inlineRefSchema(schOrEnv);
|
|
4576
4576
|
function callRootRef() {
|
|
4577
4577
|
if (env === root)
|
|
4578
|
-
return callRef(cxt,
|
|
4578
|
+
return callRef(cxt, validateName2, env, env.$async);
|
|
4579
4579
|
const rootName = gen.scopeValue("root", { ref: root });
|
|
4580
4580
|
return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root, root.$async);
|
|
4581
4581
|
}
|
|
@@ -6873,12 +6873,12 @@ var require_dist = __commonJS({
|
|
|
6873
6873
|
throw new Error(`Unknown format "${name}"`);
|
|
6874
6874
|
return f;
|
|
6875
6875
|
};
|
|
6876
|
-
function addFormats(ajv, list,
|
|
6876
|
+
function addFormats(ajv, list, fs2, exportName) {
|
|
6877
6877
|
var _a;
|
|
6878
6878
|
var _b;
|
|
6879
6879
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6880
6880
|
for (const f of list)
|
|
6881
|
-
ajv.addFormat(f,
|
|
6881
|
+
ajv.addFormat(f, fs2[f]);
|
|
6882
6882
|
}
|
|
6883
6883
|
module.exports = exports = formatsPlugin;
|
|
6884
6884
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -15620,6 +15620,7 @@ config(en_default2());
|
|
|
15620
15620
|
|
|
15621
15621
|
// ../node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
|
|
15622
15622
|
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
15623
|
+
var DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
|
|
15623
15624
|
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
15624
15625
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
15625
15626
|
var JSONRPC_VERSION = "2.0";
|
|
@@ -15948,6 +15949,7 @@ var InitializeRequestSchema = RequestSchema.extend({
|
|
|
15948
15949
|
method: literal("initialize"),
|
|
15949
15950
|
params: InitializeRequestParamsSchema
|
|
15950
15951
|
});
|
|
15952
|
+
var isInitializeRequest = (value) => InitializeRequestSchema.safeParse(value).success;
|
|
15951
15953
|
var ServerCapabilitiesSchema = object2({
|
|
15952
15954
|
/**
|
|
15953
15955
|
* Experimental, non-standard capabilities that the server supports.
|
|
@@ -21099,81 +21101,2040 @@ var StdioServerTransport = class {
|
|
|
21099
21101
|
}
|
|
21100
21102
|
};
|
|
21101
21103
|
|
|
21104
|
+
// ../node_modules/@hono/node-server/dist/index.mjs
|
|
21105
|
+
import { Http2ServerRequest as Http2ServerRequest2, constants as h2constants } from "http2";
|
|
21106
|
+
import { Http2ServerRequest } from "http2";
|
|
21107
|
+
import { Readable } from "stream";
|
|
21108
|
+
import crypto2 from "crypto";
|
|
21109
|
+
var RequestError = class extends Error {
|
|
21110
|
+
constructor(message, options) {
|
|
21111
|
+
super(message, options);
|
|
21112
|
+
this.name = "RequestError";
|
|
21113
|
+
}
|
|
21114
|
+
};
|
|
21115
|
+
var toRequestError = (e) => {
|
|
21116
|
+
if (e instanceof RequestError) {
|
|
21117
|
+
return e;
|
|
21118
|
+
}
|
|
21119
|
+
return new RequestError(e.message, { cause: e });
|
|
21120
|
+
};
|
|
21121
|
+
var GlobalRequest = global.Request;
|
|
21122
|
+
var Request = class extends GlobalRequest {
|
|
21123
|
+
constructor(input, options) {
|
|
21124
|
+
if (typeof input === "object" && getRequestCache in input) {
|
|
21125
|
+
input = input[getRequestCache]();
|
|
21126
|
+
}
|
|
21127
|
+
if (typeof options?.body?.getReader !== "undefined") {
|
|
21128
|
+
;
|
|
21129
|
+
options.duplex ??= "half";
|
|
21130
|
+
}
|
|
21131
|
+
super(input, options);
|
|
21132
|
+
}
|
|
21133
|
+
};
|
|
21134
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
21135
|
+
const headerRecord = [];
|
|
21136
|
+
const rawHeaders = incoming.rawHeaders;
|
|
21137
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
21138
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
21139
|
+
if (key.charCodeAt(0) !== /*:*/
|
|
21140
|
+
58) {
|
|
21141
|
+
headerRecord.push([key, value]);
|
|
21142
|
+
}
|
|
21143
|
+
}
|
|
21144
|
+
return new Headers(headerRecord);
|
|
21145
|
+
};
|
|
21146
|
+
var wrapBodyStream = Symbol("wrapBodyStream");
|
|
21147
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
21148
|
+
const init = {
|
|
21149
|
+
method,
|
|
21150
|
+
headers,
|
|
21151
|
+
signal: abortController.signal
|
|
21152
|
+
};
|
|
21153
|
+
if (method === "TRACE") {
|
|
21154
|
+
init.method = "GET";
|
|
21155
|
+
const req = new Request(url, init);
|
|
21156
|
+
Object.defineProperty(req, "method", {
|
|
21157
|
+
get() {
|
|
21158
|
+
return "TRACE";
|
|
21159
|
+
}
|
|
21160
|
+
});
|
|
21161
|
+
return req;
|
|
21162
|
+
}
|
|
21163
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
21164
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
|
|
21165
|
+
init.body = new ReadableStream({
|
|
21166
|
+
start(controller) {
|
|
21167
|
+
controller.enqueue(incoming.rawBody);
|
|
21168
|
+
controller.close();
|
|
21169
|
+
}
|
|
21170
|
+
});
|
|
21171
|
+
} else if (incoming[wrapBodyStream]) {
|
|
21172
|
+
let reader;
|
|
21173
|
+
init.body = new ReadableStream({
|
|
21174
|
+
async pull(controller) {
|
|
21175
|
+
try {
|
|
21176
|
+
reader ||= Readable.toWeb(incoming).getReader();
|
|
21177
|
+
const { done, value } = await reader.read();
|
|
21178
|
+
if (done) {
|
|
21179
|
+
controller.close();
|
|
21180
|
+
} else {
|
|
21181
|
+
controller.enqueue(value);
|
|
21182
|
+
}
|
|
21183
|
+
} catch (error2) {
|
|
21184
|
+
controller.error(error2);
|
|
21185
|
+
}
|
|
21186
|
+
}
|
|
21187
|
+
});
|
|
21188
|
+
} else {
|
|
21189
|
+
init.body = Readable.toWeb(incoming);
|
|
21190
|
+
}
|
|
21191
|
+
}
|
|
21192
|
+
return new Request(url, init);
|
|
21193
|
+
};
|
|
21194
|
+
var getRequestCache = Symbol("getRequestCache");
|
|
21195
|
+
var requestCache = Symbol("requestCache");
|
|
21196
|
+
var incomingKey = Symbol("incomingKey");
|
|
21197
|
+
var urlKey = Symbol("urlKey");
|
|
21198
|
+
var headersKey = Symbol("headersKey");
|
|
21199
|
+
var abortControllerKey = Symbol("abortControllerKey");
|
|
21200
|
+
var getAbortController = Symbol("getAbortController");
|
|
21201
|
+
var requestPrototype = {
|
|
21202
|
+
get method() {
|
|
21203
|
+
return this[incomingKey].method || "GET";
|
|
21204
|
+
},
|
|
21205
|
+
get url() {
|
|
21206
|
+
return this[urlKey];
|
|
21207
|
+
},
|
|
21208
|
+
get headers() {
|
|
21209
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
21210
|
+
},
|
|
21211
|
+
[getAbortController]() {
|
|
21212
|
+
this[getRequestCache]();
|
|
21213
|
+
return this[abortControllerKey];
|
|
21214
|
+
},
|
|
21215
|
+
[getRequestCache]() {
|
|
21216
|
+
this[abortControllerKey] ||= new AbortController();
|
|
21217
|
+
return this[requestCache] ||= newRequestFromIncoming(
|
|
21218
|
+
this.method,
|
|
21219
|
+
this[urlKey],
|
|
21220
|
+
this.headers,
|
|
21221
|
+
this[incomingKey],
|
|
21222
|
+
this[abortControllerKey]
|
|
21223
|
+
);
|
|
21224
|
+
}
|
|
21225
|
+
};
|
|
21226
|
+
[
|
|
21227
|
+
"body",
|
|
21228
|
+
"bodyUsed",
|
|
21229
|
+
"cache",
|
|
21230
|
+
"credentials",
|
|
21231
|
+
"destination",
|
|
21232
|
+
"integrity",
|
|
21233
|
+
"mode",
|
|
21234
|
+
"redirect",
|
|
21235
|
+
"referrer",
|
|
21236
|
+
"referrerPolicy",
|
|
21237
|
+
"signal",
|
|
21238
|
+
"keepalive"
|
|
21239
|
+
].forEach((k) => {
|
|
21240
|
+
Object.defineProperty(requestPrototype, k, {
|
|
21241
|
+
get() {
|
|
21242
|
+
return this[getRequestCache]()[k];
|
|
21243
|
+
}
|
|
21244
|
+
});
|
|
21245
|
+
});
|
|
21246
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
21247
|
+
Object.defineProperty(requestPrototype, k, {
|
|
21248
|
+
value: function() {
|
|
21249
|
+
return this[getRequestCache]()[k]();
|
|
21250
|
+
}
|
|
21251
|
+
});
|
|
21252
|
+
});
|
|
21253
|
+
Object.defineProperty(requestPrototype, Symbol.for("nodejs.util.inspect.custom"), {
|
|
21254
|
+
value: function(depth, options, inspectFn) {
|
|
21255
|
+
const props = {
|
|
21256
|
+
method: this.method,
|
|
21257
|
+
url: this.url,
|
|
21258
|
+
headers: this.headers,
|
|
21259
|
+
nativeRequest: this[requestCache]
|
|
21260
|
+
};
|
|
21261
|
+
return `Request (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`;
|
|
21262
|
+
}
|
|
21263
|
+
});
|
|
21264
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
21265
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
21266
|
+
const req = Object.create(requestPrototype);
|
|
21267
|
+
req[incomingKey] = incoming;
|
|
21268
|
+
const incomingUrl = incoming.url || "";
|
|
21269
|
+
if (incomingUrl[0] !== "/" && // short-circuit for performance. most requests are relative URL.
|
|
21270
|
+
(incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
21271
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
21272
|
+
throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
21273
|
+
}
|
|
21274
|
+
try {
|
|
21275
|
+
const url2 = new URL(incomingUrl);
|
|
21276
|
+
req[urlKey] = url2.href;
|
|
21277
|
+
} catch (e) {
|
|
21278
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
21279
|
+
}
|
|
21280
|
+
return req;
|
|
21281
|
+
}
|
|
21282
|
+
const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
21283
|
+
if (!host) {
|
|
21284
|
+
throw new RequestError("Missing host header");
|
|
21285
|
+
}
|
|
21286
|
+
let scheme;
|
|
21287
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
21288
|
+
scheme = incoming.scheme;
|
|
21289
|
+
if (!(scheme === "http" || scheme === "https")) {
|
|
21290
|
+
throw new RequestError("Unsupported scheme");
|
|
21291
|
+
}
|
|
21292
|
+
} else {
|
|
21293
|
+
scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
21294
|
+
}
|
|
21295
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
21296
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
|
|
21297
|
+
throw new RequestError("Invalid host header");
|
|
21298
|
+
}
|
|
21299
|
+
req[urlKey] = url.href;
|
|
21300
|
+
return req;
|
|
21301
|
+
};
|
|
21302
|
+
var responseCache = Symbol("responseCache");
|
|
21303
|
+
var getResponseCache = Symbol("getResponseCache");
|
|
21304
|
+
var cacheKey = Symbol("cache");
|
|
21305
|
+
var GlobalResponse = global.Response;
|
|
21306
|
+
var Response2 = class _Response {
|
|
21307
|
+
#body;
|
|
21308
|
+
#init;
|
|
21309
|
+
[getResponseCache]() {
|
|
21310
|
+
delete this[cacheKey];
|
|
21311
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
21312
|
+
}
|
|
21313
|
+
constructor(body, init) {
|
|
21314
|
+
let headers;
|
|
21315
|
+
this.#body = body;
|
|
21316
|
+
if (init instanceof _Response) {
|
|
21317
|
+
const cachedGlobalResponse = init[responseCache];
|
|
21318
|
+
if (cachedGlobalResponse) {
|
|
21319
|
+
this.#init = cachedGlobalResponse;
|
|
21320
|
+
this[getResponseCache]();
|
|
21321
|
+
return;
|
|
21322
|
+
} else {
|
|
21323
|
+
this.#init = init.#init;
|
|
21324
|
+
headers = new Headers(init.#init.headers);
|
|
21325
|
+
}
|
|
21326
|
+
} else {
|
|
21327
|
+
this.#init = init;
|
|
21328
|
+
}
|
|
21329
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
21330
|
+
;
|
|
21331
|
+
this[cacheKey] = [init?.status || 200, body, headers || init?.headers];
|
|
21332
|
+
}
|
|
21333
|
+
}
|
|
21334
|
+
get headers() {
|
|
21335
|
+
const cache = this[cacheKey];
|
|
21336
|
+
if (cache) {
|
|
21337
|
+
if (!(cache[2] instanceof Headers)) {
|
|
21338
|
+
cache[2] = new Headers(
|
|
21339
|
+
cache[2] || { "content-type": "text/plain; charset=UTF-8" }
|
|
21340
|
+
);
|
|
21341
|
+
}
|
|
21342
|
+
return cache[2];
|
|
21343
|
+
}
|
|
21344
|
+
return this[getResponseCache]().headers;
|
|
21345
|
+
}
|
|
21346
|
+
get status() {
|
|
21347
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
21348
|
+
}
|
|
21349
|
+
get ok() {
|
|
21350
|
+
const status = this.status;
|
|
21351
|
+
return status >= 200 && status < 300;
|
|
21352
|
+
}
|
|
21353
|
+
};
|
|
21354
|
+
["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
|
|
21355
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
21356
|
+
get() {
|
|
21357
|
+
return this[getResponseCache]()[k];
|
|
21358
|
+
}
|
|
21359
|
+
});
|
|
21360
|
+
});
|
|
21361
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
21362
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
21363
|
+
value: function() {
|
|
21364
|
+
return this[getResponseCache]()[k]();
|
|
21365
|
+
}
|
|
21366
|
+
});
|
|
21367
|
+
});
|
|
21368
|
+
Object.defineProperty(Response2.prototype, Symbol.for("nodejs.util.inspect.custom"), {
|
|
21369
|
+
value: function(depth, options, inspectFn) {
|
|
21370
|
+
const props = {
|
|
21371
|
+
status: this.status,
|
|
21372
|
+
headers: this.headers,
|
|
21373
|
+
ok: this.ok,
|
|
21374
|
+
nativeResponse: this[responseCache]
|
|
21375
|
+
};
|
|
21376
|
+
return `Response (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`;
|
|
21377
|
+
}
|
|
21378
|
+
});
|
|
21379
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
21380
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
21381
|
+
async function readWithoutBlocking(readPromise) {
|
|
21382
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
|
|
21383
|
+
}
|
|
21384
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
21385
|
+
const cancel = (error2) => {
|
|
21386
|
+
reader.cancel(error2).catch(() => {
|
|
21387
|
+
});
|
|
21388
|
+
};
|
|
21389
|
+
writable.on("close", cancel);
|
|
21390
|
+
writable.on("error", cancel);
|
|
21391
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
21392
|
+
return reader.closed.finally(() => {
|
|
21393
|
+
writable.off("close", cancel);
|
|
21394
|
+
writable.off("error", cancel);
|
|
21395
|
+
});
|
|
21396
|
+
function handleStreamError(error2) {
|
|
21397
|
+
if (error2) {
|
|
21398
|
+
writable.destroy(error2);
|
|
21399
|
+
}
|
|
21400
|
+
}
|
|
21401
|
+
function onDrain() {
|
|
21402
|
+
reader.read().then(flow, handleStreamError);
|
|
21403
|
+
}
|
|
21404
|
+
function flow({ done, value }) {
|
|
21405
|
+
try {
|
|
21406
|
+
if (done) {
|
|
21407
|
+
writable.end();
|
|
21408
|
+
} else if (!writable.write(value)) {
|
|
21409
|
+
writable.once("drain", onDrain);
|
|
21410
|
+
} else {
|
|
21411
|
+
return reader.read().then(flow, handleStreamError);
|
|
21412
|
+
}
|
|
21413
|
+
} catch (e) {
|
|
21414
|
+
handleStreamError(e);
|
|
21415
|
+
}
|
|
21416
|
+
}
|
|
21417
|
+
}
|
|
21418
|
+
function writeFromReadableStream(stream, writable) {
|
|
21419
|
+
if (stream.locked) {
|
|
21420
|
+
throw new TypeError("ReadableStream is locked.");
|
|
21421
|
+
} else if (writable.destroyed) {
|
|
21422
|
+
return;
|
|
21423
|
+
}
|
|
21424
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
|
|
21425
|
+
}
|
|
21426
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
21427
|
+
const res = {};
|
|
21428
|
+
if (!(headers instanceof Headers)) {
|
|
21429
|
+
headers = new Headers(headers ?? void 0);
|
|
21430
|
+
}
|
|
21431
|
+
const cookies = [];
|
|
21432
|
+
for (const [k, v] of headers) {
|
|
21433
|
+
if (k === "set-cookie") {
|
|
21434
|
+
cookies.push(v);
|
|
21435
|
+
} else {
|
|
21436
|
+
res[k] = v;
|
|
21437
|
+
}
|
|
21438
|
+
}
|
|
21439
|
+
if (cookies.length > 0) {
|
|
21440
|
+
res["set-cookie"] = cookies;
|
|
21441
|
+
}
|
|
21442
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
21443
|
+
return res;
|
|
21444
|
+
};
|
|
21445
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
21446
|
+
if (typeof global.crypto === "undefined") {
|
|
21447
|
+
global.crypto = crypto2;
|
|
21448
|
+
}
|
|
21449
|
+
var outgoingEnded = Symbol("outgoingEnded");
|
|
21450
|
+
var incomingDraining = Symbol("incomingDraining");
|
|
21451
|
+
var DRAIN_TIMEOUT_MS = 500;
|
|
21452
|
+
var MAX_DRAIN_BYTES = 64 * 1024 * 1024;
|
|
21453
|
+
var drainIncoming = (incoming) => {
|
|
21454
|
+
const incomingWithDrainState = incoming;
|
|
21455
|
+
if (incoming.destroyed || incomingWithDrainState[incomingDraining]) {
|
|
21456
|
+
return;
|
|
21457
|
+
}
|
|
21458
|
+
incomingWithDrainState[incomingDraining] = true;
|
|
21459
|
+
if (incoming instanceof Http2ServerRequest2) {
|
|
21460
|
+
try {
|
|
21461
|
+
;
|
|
21462
|
+
incoming.stream?.close?.(h2constants.NGHTTP2_NO_ERROR);
|
|
21463
|
+
} catch {
|
|
21464
|
+
}
|
|
21465
|
+
return;
|
|
21466
|
+
}
|
|
21467
|
+
let bytesRead = 0;
|
|
21468
|
+
const cleanup = () => {
|
|
21469
|
+
clearTimeout(timer);
|
|
21470
|
+
incoming.off("data", onData);
|
|
21471
|
+
incoming.off("end", cleanup);
|
|
21472
|
+
incoming.off("error", cleanup);
|
|
21473
|
+
};
|
|
21474
|
+
const forceClose = () => {
|
|
21475
|
+
cleanup();
|
|
21476
|
+
const socket = incoming.socket;
|
|
21477
|
+
if (socket && !socket.destroyed) {
|
|
21478
|
+
socket.destroySoon();
|
|
21479
|
+
}
|
|
21480
|
+
};
|
|
21481
|
+
const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS);
|
|
21482
|
+
timer.unref?.();
|
|
21483
|
+
const onData = (chunk) => {
|
|
21484
|
+
bytesRead += chunk.length;
|
|
21485
|
+
if (bytesRead > MAX_DRAIN_BYTES) {
|
|
21486
|
+
forceClose();
|
|
21487
|
+
}
|
|
21488
|
+
};
|
|
21489
|
+
incoming.on("data", onData);
|
|
21490
|
+
incoming.on("end", cleanup);
|
|
21491
|
+
incoming.on("error", cleanup);
|
|
21492
|
+
incoming.resume();
|
|
21493
|
+
};
|
|
21494
|
+
var handleRequestError = () => new Response(null, {
|
|
21495
|
+
status: 400
|
|
21496
|
+
});
|
|
21497
|
+
var handleFetchError = (e) => new Response(null, {
|
|
21498
|
+
status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
|
|
21499
|
+
});
|
|
21500
|
+
var handleResponseError = (e, outgoing) => {
|
|
21501
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
21502
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
21503
|
+
console.info("The user aborted a request.");
|
|
21504
|
+
} else {
|
|
21505
|
+
console.error(e);
|
|
21506
|
+
if (!outgoing.headersSent) {
|
|
21507
|
+
outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
21508
|
+
}
|
|
21509
|
+
outgoing.end(`Error: ${err.message}`);
|
|
21510
|
+
outgoing.destroy(err);
|
|
21511
|
+
}
|
|
21512
|
+
};
|
|
21513
|
+
var flushHeaders = (outgoing) => {
|
|
21514
|
+
if ("flushHeaders" in outgoing && outgoing.writable) {
|
|
21515
|
+
outgoing.flushHeaders();
|
|
21516
|
+
}
|
|
21517
|
+
};
|
|
21518
|
+
var responseViaCache = async (res, outgoing) => {
|
|
21519
|
+
let [status, body, header] = res[cacheKey];
|
|
21520
|
+
let hasContentLength = false;
|
|
21521
|
+
if (!header) {
|
|
21522
|
+
header = { "content-type": "text/plain; charset=UTF-8" };
|
|
21523
|
+
} else if (header instanceof Headers) {
|
|
21524
|
+
hasContentLength = header.has("content-length");
|
|
21525
|
+
header = buildOutgoingHttpHeaders(header);
|
|
21526
|
+
} else if (Array.isArray(header)) {
|
|
21527
|
+
const headerObj = new Headers(header);
|
|
21528
|
+
hasContentLength = headerObj.has("content-length");
|
|
21529
|
+
header = buildOutgoingHttpHeaders(headerObj);
|
|
21530
|
+
} else {
|
|
21531
|
+
for (const key in header) {
|
|
21532
|
+
if (key.length === 14 && key.toLowerCase() === "content-length") {
|
|
21533
|
+
hasContentLength = true;
|
|
21534
|
+
break;
|
|
21535
|
+
}
|
|
21536
|
+
}
|
|
21537
|
+
}
|
|
21538
|
+
if (!hasContentLength) {
|
|
21539
|
+
if (typeof body === "string") {
|
|
21540
|
+
header["Content-Length"] = Buffer.byteLength(body);
|
|
21541
|
+
} else if (body instanceof Uint8Array) {
|
|
21542
|
+
header["Content-Length"] = body.byteLength;
|
|
21543
|
+
} else if (body instanceof Blob) {
|
|
21544
|
+
header["Content-Length"] = body.size;
|
|
21545
|
+
}
|
|
21546
|
+
}
|
|
21547
|
+
outgoing.writeHead(status, header);
|
|
21548
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
21549
|
+
outgoing.end(body);
|
|
21550
|
+
} else if (body instanceof Blob) {
|
|
21551
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
21552
|
+
} else {
|
|
21553
|
+
flushHeaders(outgoing);
|
|
21554
|
+
await writeFromReadableStream(body, outgoing)?.catch(
|
|
21555
|
+
(e) => handleResponseError(e, outgoing)
|
|
21556
|
+
);
|
|
21557
|
+
}
|
|
21558
|
+
;
|
|
21559
|
+
outgoing[outgoingEnded]?.();
|
|
21560
|
+
};
|
|
21561
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
21562
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
21563
|
+
if (isPromise(res)) {
|
|
21564
|
+
if (options.errorHandler) {
|
|
21565
|
+
try {
|
|
21566
|
+
res = await res;
|
|
21567
|
+
} catch (err) {
|
|
21568
|
+
const errRes = await options.errorHandler(err);
|
|
21569
|
+
if (!errRes) {
|
|
21570
|
+
return;
|
|
21571
|
+
}
|
|
21572
|
+
res = errRes;
|
|
21573
|
+
}
|
|
21574
|
+
} else {
|
|
21575
|
+
res = await res.catch(handleFetchError);
|
|
21576
|
+
}
|
|
21577
|
+
}
|
|
21578
|
+
if (cacheKey in res) {
|
|
21579
|
+
return responseViaCache(res, outgoing);
|
|
21580
|
+
}
|
|
21581
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
21582
|
+
if (res.body) {
|
|
21583
|
+
const reader = res.body.getReader();
|
|
21584
|
+
const values = [];
|
|
21585
|
+
let done = false;
|
|
21586
|
+
let currentReadPromise = void 0;
|
|
21587
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
21588
|
+
let maxReadCount = 2;
|
|
21589
|
+
for (let i = 0; i < maxReadCount; i++) {
|
|
21590
|
+
currentReadPromise ||= reader.read();
|
|
21591
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
21592
|
+
console.error(e);
|
|
21593
|
+
done = true;
|
|
21594
|
+
});
|
|
21595
|
+
if (!chunk) {
|
|
21596
|
+
if (i === 1) {
|
|
21597
|
+
await new Promise((resolve2) => setTimeout(resolve2));
|
|
21598
|
+
maxReadCount = 3;
|
|
21599
|
+
continue;
|
|
21600
|
+
}
|
|
21601
|
+
break;
|
|
21602
|
+
}
|
|
21603
|
+
currentReadPromise = void 0;
|
|
21604
|
+
if (chunk.value) {
|
|
21605
|
+
values.push(chunk.value);
|
|
21606
|
+
}
|
|
21607
|
+
if (chunk.done) {
|
|
21608
|
+
done = true;
|
|
21609
|
+
break;
|
|
21610
|
+
}
|
|
21611
|
+
}
|
|
21612
|
+
if (done && !("content-length" in resHeaderRecord)) {
|
|
21613
|
+
resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
21614
|
+
}
|
|
21615
|
+
}
|
|
21616
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
21617
|
+
values.forEach((value) => {
|
|
21618
|
+
;
|
|
21619
|
+
outgoing.write(value);
|
|
21620
|
+
});
|
|
21621
|
+
if (done) {
|
|
21622
|
+
outgoing.end();
|
|
21623
|
+
} else {
|
|
21624
|
+
if (values.length === 0) {
|
|
21625
|
+
flushHeaders(outgoing);
|
|
21626
|
+
}
|
|
21627
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
21628
|
+
}
|
|
21629
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {
|
|
21630
|
+
} else {
|
|
21631
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
21632
|
+
outgoing.end();
|
|
21633
|
+
}
|
|
21634
|
+
;
|
|
21635
|
+
outgoing[outgoingEnded]?.();
|
|
21636
|
+
};
|
|
21637
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
21638
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
21639
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request) {
|
|
21640
|
+
Object.defineProperty(global, "Request", {
|
|
21641
|
+
value: Request
|
|
21642
|
+
});
|
|
21643
|
+
Object.defineProperty(global, "Response", {
|
|
21644
|
+
value: Response2
|
|
21645
|
+
});
|
|
21646
|
+
}
|
|
21647
|
+
return async (incoming, outgoing) => {
|
|
21648
|
+
let res, req;
|
|
21649
|
+
try {
|
|
21650
|
+
req = newRequest(incoming, options.hostname);
|
|
21651
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
21652
|
+
if (!incomingEnded) {
|
|
21653
|
+
;
|
|
21654
|
+
incoming[wrapBodyStream] = true;
|
|
21655
|
+
incoming.on("end", () => {
|
|
21656
|
+
incomingEnded = true;
|
|
21657
|
+
});
|
|
21658
|
+
if (incoming instanceof Http2ServerRequest2) {
|
|
21659
|
+
;
|
|
21660
|
+
outgoing[outgoingEnded] = () => {
|
|
21661
|
+
if (!incomingEnded) {
|
|
21662
|
+
setTimeout(() => {
|
|
21663
|
+
if (!incomingEnded) {
|
|
21664
|
+
setTimeout(() => {
|
|
21665
|
+
drainIncoming(incoming);
|
|
21666
|
+
});
|
|
21667
|
+
}
|
|
21668
|
+
});
|
|
21669
|
+
}
|
|
21670
|
+
};
|
|
21671
|
+
}
|
|
21672
|
+
outgoing.on("finish", () => {
|
|
21673
|
+
if (!incomingEnded) {
|
|
21674
|
+
drainIncoming(incoming);
|
|
21675
|
+
}
|
|
21676
|
+
});
|
|
21677
|
+
}
|
|
21678
|
+
outgoing.on("close", () => {
|
|
21679
|
+
const abortController = req[abortControllerKey];
|
|
21680
|
+
if (abortController) {
|
|
21681
|
+
if (incoming.errored) {
|
|
21682
|
+
req[abortControllerKey].abort(incoming.errored.toString());
|
|
21683
|
+
} else if (!outgoing.writableFinished) {
|
|
21684
|
+
req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
21685
|
+
}
|
|
21686
|
+
}
|
|
21687
|
+
if (!incomingEnded) {
|
|
21688
|
+
setTimeout(() => {
|
|
21689
|
+
if (!incomingEnded) {
|
|
21690
|
+
setTimeout(() => {
|
|
21691
|
+
drainIncoming(incoming);
|
|
21692
|
+
});
|
|
21693
|
+
}
|
|
21694
|
+
});
|
|
21695
|
+
}
|
|
21696
|
+
});
|
|
21697
|
+
res = fetchCallback(req, { incoming, outgoing });
|
|
21698
|
+
if (cacheKey in res) {
|
|
21699
|
+
return responseViaCache(res, outgoing);
|
|
21700
|
+
}
|
|
21701
|
+
} catch (e) {
|
|
21702
|
+
if (!res) {
|
|
21703
|
+
if (options.errorHandler) {
|
|
21704
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
21705
|
+
if (!res) {
|
|
21706
|
+
return;
|
|
21707
|
+
}
|
|
21708
|
+
} else if (!req) {
|
|
21709
|
+
res = handleRequestError();
|
|
21710
|
+
} else {
|
|
21711
|
+
res = handleFetchError(e);
|
|
21712
|
+
}
|
|
21713
|
+
} else {
|
|
21714
|
+
return handleResponseError(e, outgoing);
|
|
21715
|
+
}
|
|
21716
|
+
}
|
|
21717
|
+
try {
|
|
21718
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
21719
|
+
} catch (e) {
|
|
21720
|
+
return handleResponseError(e, outgoing);
|
|
21721
|
+
}
|
|
21722
|
+
};
|
|
21723
|
+
};
|
|
21724
|
+
|
|
21725
|
+
// ../node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
|
|
21726
|
+
var WebStandardStreamableHTTPServerTransport = class {
|
|
21727
|
+
constructor(options = {}) {
|
|
21728
|
+
this._started = false;
|
|
21729
|
+
this._hasHandledRequest = false;
|
|
21730
|
+
this._streamMapping = /* @__PURE__ */ new Map();
|
|
21731
|
+
this._requestToStreamMapping = /* @__PURE__ */ new Map();
|
|
21732
|
+
this._requestResponseMap = /* @__PURE__ */ new Map();
|
|
21733
|
+
this._initialized = false;
|
|
21734
|
+
this._enableJsonResponse = false;
|
|
21735
|
+
this._standaloneSseStreamId = "_GET_stream";
|
|
21736
|
+
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
21737
|
+
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
21738
|
+
this._eventStore = options.eventStore;
|
|
21739
|
+
this._onsessioninitialized = options.onsessioninitialized;
|
|
21740
|
+
this._onsessionclosed = options.onsessionclosed;
|
|
21741
|
+
this._allowedHosts = options.allowedHosts;
|
|
21742
|
+
this._allowedOrigins = options.allowedOrigins;
|
|
21743
|
+
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
21744
|
+
this._retryInterval = options.retryInterval;
|
|
21745
|
+
}
|
|
21746
|
+
/**
|
|
21747
|
+
* Starts the transport. This is required by the Transport interface but is a no-op
|
|
21748
|
+
* for the Streamable HTTP transport as connections are managed per-request.
|
|
21749
|
+
*/
|
|
21750
|
+
async start() {
|
|
21751
|
+
if (this._started) {
|
|
21752
|
+
throw new Error("Transport already started");
|
|
21753
|
+
}
|
|
21754
|
+
this._started = true;
|
|
21755
|
+
}
|
|
21756
|
+
/**
|
|
21757
|
+
* Helper to create a JSON error response
|
|
21758
|
+
*/
|
|
21759
|
+
createJsonErrorResponse(status, code, message, options) {
|
|
21760
|
+
const error2 = { code, message };
|
|
21761
|
+
if (options?.data !== void 0) {
|
|
21762
|
+
error2.data = options.data;
|
|
21763
|
+
}
|
|
21764
|
+
return new Response(JSON.stringify({
|
|
21765
|
+
jsonrpc: "2.0",
|
|
21766
|
+
error: error2,
|
|
21767
|
+
id: null
|
|
21768
|
+
}), {
|
|
21769
|
+
status,
|
|
21770
|
+
headers: {
|
|
21771
|
+
"Content-Type": "application/json",
|
|
21772
|
+
...options?.headers
|
|
21773
|
+
}
|
|
21774
|
+
});
|
|
21775
|
+
}
|
|
21776
|
+
/**
|
|
21777
|
+
* Validates request headers for DNS rebinding protection.
|
|
21778
|
+
* @returns Error response if validation fails, undefined if validation passes.
|
|
21779
|
+
*/
|
|
21780
|
+
validateRequestHeaders(req) {
|
|
21781
|
+
if (!this._enableDnsRebindingProtection) {
|
|
21782
|
+
return void 0;
|
|
21783
|
+
}
|
|
21784
|
+
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
21785
|
+
const hostHeader = req.headers.get("host");
|
|
21786
|
+
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
21787
|
+
const error2 = `Invalid Host header: ${hostHeader}`;
|
|
21788
|
+
this.onerror?.(new Error(error2));
|
|
21789
|
+
return this.createJsonErrorResponse(403, -32e3, error2);
|
|
21790
|
+
}
|
|
21791
|
+
}
|
|
21792
|
+
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
21793
|
+
const originHeader = req.headers.get("origin");
|
|
21794
|
+
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
21795
|
+
const error2 = `Invalid Origin header: ${originHeader}`;
|
|
21796
|
+
this.onerror?.(new Error(error2));
|
|
21797
|
+
return this.createJsonErrorResponse(403, -32e3, error2);
|
|
21798
|
+
}
|
|
21799
|
+
}
|
|
21800
|
+
return void 0;
|
|
21801
|
+
}
|
|
21802
|
+
/**
|
|
21803
|
+
* Handles an incoming HTTP request, whether GET, POST, or DELETE
|
|
21804
|
+
* Returns a Response object (Web Standard)
|
|
21805
|
+
*/
|
|
21806
|
+
async handleRequest(req, options) {
|
|
21807
|
+
if (!this.sessionIdGenerator && this._hasHandledRequest) {
|
|
21808
|
+
throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
21809
|
+
}
|
|
21810
|
+
this._hasHandledRequest = true;
|
|
21811
|
+
const validationError = this.validateRequestHeaders(req);
|
|
21812
|
+
if (validationError) {
|
|
21813
|
+
return validationError;
|
|
21814
|
+
}
|
|
21815
|
+
switch (req.method) {
|
|
21816
|
+
case "POST":
|
|
21817
|
+
return this.handlePostRequest(req, options);
|
|
21818
|
+
case "GET":
|
|
21819
|
+
return this.handleGetRequest(req);
|
|
21820
|
+
case "DELETE":
|
|
21821
|
+
return this.handleDeleteRequest(req);
|
|
21822
|
+
default:
|
|
21823
|
+
return this.handleUnsupportedRequest();
|
|
21824
|
+
}
|
|
21825
|
+
}
|
|
21826
|
+
/**
|
|
21827
|
+
* Writes a priming event to establish resumption capability.
|
|
21828
|
+
* Only sends if eventStore is configured (opt-in for resumability) and
|
|
21829
|
+
* the client's protocol version supports empty SSE data (>= 2025-11-25).
|
|
21830
|
+
*/
|
|
21831
|
+
async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
|
|
21832
|
+
if (!this._eventStore) {
|
|
21833
|
+
return;
|
|
21834
|
+
}
|
|
21835
|
+
if (protocolVersion < "2025-11-25") {
|
|
21836
|
+
return;
|
|
21837
|
+
}
|
|
21838
|
+
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
21839
|
+
let primingEvent = `id: ${primingEventId}
|
|
21840
|
+
data:
|
|
21841
|
+
|
|
21842
|
+
`;
|
|
21843
|
+
if (this._retryInterval !== void 0) {
|
|
21844
|
+
primingEvent = `id: ${primingEventId}
|
|
21845
|
+
retry: ${this._retryInterval}
|
|
21846
|
+
data:
|
|
21847
|
+
|
|
21848
|
+
`;
|
|
21849
|
+
}
|
|
21850
|
+
controller.enqueue(encoder.encode(primingEvent));
|
|
21851
|
+
}
|
|
21852
|
+
/**
|
|
21853
|
+
* Handles GET requests for SSE stream
|
|
21854
|
+
*/
|
|
21855
|
+
async handleGetRequest(req) {
|
|
21856
|
+
const acceptHeader = req.headers.get("accept");
|
|
21857
|
+
if (!acceptHeader?.includes("text/event-stream")) {
|
|
21858
|
+
this.onerror?.(new Error("Not Acceptable: Client must accept text/event-stream"));
|
|
21859
|
+
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept text/event-stream");
|
|
21860
|
+
}
|
|
21861
|
+
const sessionError = this.validateSession(req);
|
|
21862
|
+
if (sessionError) {
|
|
21863
|
+
return sessionError;
|
|
21864
|
+
}
|
|
21865
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
21866
|
+
if (protocolError) {
|
|
21867
|
+
return protocolError;
|
|
21868
|
+
}
|
|
21869
|
+
if (this._eventStore) {
|
|
21870
|
+
const lastEventId = req.headers.get("last-event-id");
|
|
21871
|
+
if (lastEventId) {
|
|
21872
|
+
return this.replayEvents(lastEventId);
|
|
21873
|
+
}
|
|
21874
|
+
}
|
|
21875
|
+
if (this._streamMapping.get(this._standaloneSseStreamId) !== void 0) {
|
|
21876
|
+
this.onerror?.(new Error("Conflict: Only one SSE stream is allowed per session"));
|
|
21877
|
+
return this.createJsonErrorResponse(409, -32e3, "Conflict: Only one SSE stream is allowed per session");
|
|
21878
|
+
}
|
|
21879
|
+
const encoder = new TextEncoder();
|
|
21880
|
+
let streamController;
|
|
21881
|
+
const readable = new ReadableStream({
|
|
21882
|
+
start: (controller) => {
|
|
21883
|
+
streamController = controller;
|
|
21884
|
+
},
|
|
21885
|
+
cancel: () => {
|
|
21886
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
21887
|
+
}
|
|
21888
|
+
});
|
|
21889
|
+
const headers = {
|
|
21890
|
+
"Content-Type": "text/event-stream",
|
|
21891
|
+
"Cache-Control": "no-cache, no-transform",
|
|
21892
|
+
Connection: "keep-alive"
|
|
21893
|
+
};
|
|
21894
|
+
if (this.sessionId !== void 0) {
|
|
21895
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
21896
|
+
}
|
|
21897
|
+
this._streamMapping.set(this._standaloneSseStreamId, {
|
|
21898
|
+
controller: streamController,
|
|
21899
|
+
encoder,
|
|
21900
|
+
cleanup: () => {
|
|
21901
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
21902
|
+
try {
|
|
21903
|
+
streamController.close();
|
|
21904
|
+
} catch {
|
|
21905
|
+
}
|
|
21906
|
+
}
|
|
21907
|
+
});
|
|
21908
|
+
return new Response(readable, { headers });
|
|
21909
|
+
}
|
|
21910
|
+
/**
|
|
21911
|
+
* Replays events that would have been sent after the specified event ID
|
|
21912
|
+
* Only used when resumability is enabled
|
|
21913
|
+
*/
|
|
21914
|
+
async replayEvents(lastEventId) {
|
|
21915
|
+
if (!this._eventStore) {
|
|
21916
|
+
this.onerror?.(new Error("Event store not configured"));
|
|
21917
|
+
return this.createJsonErrorResponse(400, -32e3, "Event store not configured");
|
|
21918
|
+
}
|
|
21919
|
+
try {
|
|
21920
|
+
let streamId;
|
|
21921
|
+
if (this._eventStore.getStreamIdForEventId) {
|
|
21922
|
+
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
21923
|
+
if (!streamId) {
|
|
21924
|
+
this.onerror?.(new Error("Invalid event ID format"));
|
|
21925
|
+
return this.createJsonErrorResponse(400, -32e3, "Invalid event ID format");
|
|
21926
|
+
}
|
|
21927
|
+
if (this._streamMapping.get(streamId) !== void 0) {
|
|
21928
|
+
this.onerror?.(new Error("Conflict: Stream already has an active connection"));
|
|
21929
|
+
return this.createJsonErrorResponse(409, -32e3, "Conflict: Stream already has an active connection");
|
|
21930
|
+
}
|
|
21931
|
+
}
|
|
21932
|
+
const headers = {
|
|
21933
|
+
"Content-Type": "text/event-stream",
|
|
21934
|
+
"Cache-Control": "no-cache, no-transform",
|
|
21935
|
+
Connection: "keep-alive"
|
|
21936
|
+
};
|
|
21937
|
+
if (this.sessionId !== void 0) {
|
|
21938
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
21939
|
+
}
|
|
21940
|
+
const encoder = new TextEncoder();
|
|
21941
|
+
let streamController;
|
|
21942
|
+
const readable = new ReadableStream({
|
|
21943
|
+
start: (controller) => {
|
|
21944
|
+
streamController = controller;
|
|
21945
|
+
},
|
|
21946
|
+
cancel: () => {
|
|
21947
|
+
}
|
|
21948
|
+
});
|
|
21949
|
+
const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
21950
|
+
send: async (eventId, message) => {
|
|
21951
|
+
const success = this.writeSSEEvent(streamController, encoder, message, eventId);
|
|
21952
|
+
if (!success) {
|
|
21953
|
+
this.onerror?.(new Error("Failed replay events"));
|
|
21954
|
+
try {
|
|
21955
|
+
streamController.close();
|
|
21956
|
+
} catch {
|
|
21957
|
+
}
|
|
21958
|
+
}
|
|
21959
|
+
}
|
|
21960
|
+
});
|
|
21961
|
+
this._streamMapping.set(replayedStreamId, {
|
|
21962
|
+
controller: streamController,
|
|
21963
|
+
encoder,
|
|
21964
|
+
cleanup: () => {
|
|
21965
|
+
this._streamMapping.delete(replayedStreamId);
|
|
21966
|
+
try {
|
|
21967
|
+
streamController.close();
|
|
21968
|
+
} catch {
|
|
21969
|
+
}
|
|
21970
|
+
}
|
|
21971
|
+
});
|
|
21972
|
+
return new Response(readable, { headers });
|
|
21973
|
+
} catch (error2) {
|
|
21974
|
+
this.onerror?.(error2);
|
|
21975
|
+
return this.createJsonErrorResponse(500, -32e3, "Error replaying events");
|
|
21976
|
+
}
|
|
21977
|
+
}
|
|
21978
|
+
/**
|
|
21979
|
+
* Writes an event to an SSE stream via controller with proper formatting
|
|
21980
|
+
*/
|
|
21981
|
+
writeSSEEvent(controller, encoder, message, eventId) {
|
|
21982
|
+
try {
|
|
21983
|
+
let eventData = `event: message
|
|
21984
|
+
`;
|
|
21985
|
+
if (eventId) {
|
|
21986
|
+
eventData += `id: ${eventId}
|
|
21987
|
+
`;
|
|
21988
|
+
}
|
|
21989
|
+
eventData += `data: ${JSON.stringify(message)}
|
|
21990
|
+
|
|
21991
|
+
`;
|
|
21992
|
+
controller.enqueue(encoder.encode(eventData));
|
|
21993
|
+
return true;
|
|
21994
|
+
} catch (error2) {
|
|
21995
|
+
this.onerror?.(error2);
|
|
21996
|
+
return false;
|
|
21997
|
+
}
|
|
21998
|
+
}
|
|
21999
|
+
/**
|
|
22000
|
+
* Handles unsupported requests (PUT, PATCH, etc.)
|
|
22001
|
+
*/
|
|
22002
|
+
handleUnsupportedRequest() {
|
|
22003
|
+
this.onerror?.(new Error("Method not allowed."));
|
|
22004
|
+
return new Response(JSON.stringify({
|
|
22005
|
+
jsonrpc: "2.0",
|
|
22006
|
+
error: {
|
|
22007
|
+
code: -32e3,
|
|
22008
|
+
message: "Method not allowed."
|
|
22009
|
+
},
|
|
22010
|
+
id: null
|
|
22011
|
+
}), {
|
|
22012
|
+
status: 405,
|
|
22013
|
+
headers: {
|
|
22014
|
+
Allow: "GET, POST, DELETE",
|
|
22015
|
+
"Content-Type": "application/json"
|
|
22016
|
+
}
|
|
22017
|
+
});
|
|
22018
|
+
}
|
|
22019
|
+
/**
|
|
22020
|
+
* Handles POST requests containing JSON-RPC messages
|
|
22021
|
+
*/
|
|
22022
|
+
async handlePostRequest(req, options) {
|
|
22023
|
+
try {
|
|
22024
|
+
const acceptHeader = req.headers.get("accept");
|
|
22025
|
+
if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
|
|
22026
|
+
this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream"));
|
|
22027
|
+
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
22028
|
+
}
|
|
22029
|
+
const ct = req.headers.get("content-type");
|
|
22030
|
+
if (!ct || !ct.includes("application/json")) {
|
|
22031
|
+
this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json"));
|
|
22032
|
+
return this.createJsonErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
|
|
22033
|
+
}
|
|
22034
|
+
const requestInfo = {
|
|
22035
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
22036
|
+
url: new URL(req.url)
|
|
22037
|
+
};
|
|
22038
|
+
let rawMessage;
|
|
22039
|
+
if (options?.parsedBody !== void 0) {
|
|
22040
|
+
rawMessage = options.parsedBody;
|
|
22041
|
+
} else {
|
|
22042
|
+
try {
|
|
22043
|
+
rawMessage = await req.json();
|
|
22044
|
+
} catch {
|
|
22045
|
+
this.onerror?.(new Error("Parse error: Invalid JSON"));
|
|
22046
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
22047
|
+
}
|
|
22048
|
+
}
|
|
22049
|
+
let messages;
|
|
22050
|
+
try {
|
|
22051
|
+
if (Array.isArray(rawMessage)) {
|
|
22052
|
+
messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
|
|
22053
|
+
} else {
|
|
22054
|
+
messages = [JSONRPCMessageSchema.parse(rawMessage)];
|
|
22055
|
+
}
|
|
22056
|
+
} catch {
|
|
22057
|
+
this.onerror?.(new Error("Parse error: Invalid JSON-RPC message"));
|
|
22058
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
22059
|
+
}
|
|
22060
|
+
const isInitializationRequest = messages.some(isInitializeRequest);
|
|
22061
|
+
if (isInitializationRequest) {
|
|
22062
|
+
if (this._initialized && this.sessionId !== void 0) {
|
|
22063
|
+
this.onerror?.(new Error("Invalid Request: Server already initialized"));
|
|
22064
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
|
|
22065
|
+
}
|
|
22066
|
+
if (messages.length > 1) {
|
|
22067
|
+
this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed"));
|
|
22068
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
|
|
22069
|
+
}
|
|
22070
|
+
this.sessionId = this.sessionIdGenerator?.();
|
|
22071
|
+
this._initialized = true;
|
|
22072
|
+
if (this.sessionId && this._onsessioninitialized) {
|
|
22073
|
+
await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
22074
|
+
}
|
|
22075
|
+
}
|
|
22076
|
+
if (!isInitializationRequest) {
|
|
22077
|
+
const sessionError = this.validateSession(req);
|
|
22078
|
+
if (sessionError) {
|
|
22079
|
+
return sessionError;
|
|
22080
|
+
}
|
|
22081
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
22082
|
+
if (protocolError) {
|
|
22083
|
+
return protocolError;
|
|
22084
|
+
}
|
|
22085
|
+
}
|
|
22086
|
+
const hasRequests = messages.some(isJSONRPCRequest);
|
|
22087
|
+
if (!hasRequests) {
|
|
22088
|
+
for (const message of messages) {
|
|
22089
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
22090
|
+
}
|
|
22091
|
+
return new Response(null, { status: 202 });
|
|
22092
|
+
}
|
|
22093
|
+
const streamId = crypto.randomUUID();
|
|
22094
|
+
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
22095
|
+
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
22096
|
+
if (this._enableJsonResponse) {
|
|
22097
|
+
return new Promise((resolve2) => {
|
|
22098
|
+
this._streamMapping.set(streamId, {
|
|
22099
|
+
resolveJson: resolve2,
|
|
22100
|
+
cleanup: () => {
|
|
22101
|
+
this._streamMapping.delete(streamId);
|
|
22102
|
+
}
|
|
22103
|
+
});
|
|
22104
|
+
for (const message of messages) {
|
|
22105
|
+
if (isJSONRPCRequest(message)) {
|
|
22106
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
22107
|
+
}
|
|
22108
|
+
}
|
|
22109
|
+
for (const message of messages) {
|
|
22110
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
22111
|
+
}
|
|
22112
|
+
});
|
|
22113
|
+
}
|
|
22114
|
+
const encoder = new TextEncoder();
|
|
22115
|
+
let streamController;
|
|
22116
|
+
const readable = new ReadableStream({
|
|
22117
|
+
start: (controller) => {
|
|
22118
|
+
streamController = controller;
|
|
22119
|
+
},
|
|
22120
|
+
cancel: () => {
|
|
22121
|
+
this._streamMapping.delete(streamId);
|
|
22122
|
+
}
|
|
22123
|
+
});
|
|
22124
|
+
const headers = {
|
|
22125
|
+
"Content-Type": "text/event-stream",
|
|
22126
|
+
"Cache-Control": "no-cache",
|
|
22127
|
+
Connection: "keep-alive"
|
|
22128
|
+
};
|
|
22129
|
+
if (this.sessionId !== void 0) {
|
|
22130
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
22131
|
+
}
|
|
22132
|
+
for (const message of messages) {
|
|
22133
|
+
if (isJSONRPCRequest(message)) {
|
|
22134
|
+
this._streamMapping.set(streamId, {
|
|
22135
|
+
controller: streamController,
|
|
22136
|
+
encoder,
|
|
22137
|
+
cleanup: () => {
|
|
22138
|
+
this._streamMapping.delete(streamId);
|
|
22139
|
+
try {
|
|
22140
|
+
streamController.close();
|
|
22141
|
+
} catch {
|
|
22142
|
+
}
|
|
22143
|
+
}
|
|
22144
|
+
});
|
|
22145
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
22146
|
+
}
|
|
22147
|
+
}
|
|
22148
|
+
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
22149
|
+
for (const message of messages) {
|
|
22150
|
+
let closeSSEStream;
|
|
22151
|
+
let closeStandaloneSSEStream;
|
|
22152
|
+
if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
|
|
22153
|
+
closeSSEStream = () => {
|
|
22154
|
+
this.closeSSEStream(message.id);
|
|
22155
|
+
};
|
|
22156
|
+
closeStandaloneSSEStream = () => {
|
|
22157
|
+
this.closeStandaloneSSEStream();
|
|
22158
|
+
};
|
|
22159
|
+
}
|
|
22160
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
22161
|
+
}
|
|
22162
|
+
return new Response(readable, { status: 200, headers });
|
|
22163
|
+
} catch (error2) {
|
|
22164
|
+
this.onerror?.(error2);
|
|
22165
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error2) });
|
|
22166
|
+
}
|
|
22167
|
+
}
|
|
22168
|
+
/**
|
|
22169
|
+
* Handles DELETE requests to terminate sessions
|
|
22170
|
+
*/
|
|
22171
|
+
async handleDeleteRequest(req) {
|
|
22172
|
+
const sessionError = this.validateSession(req);
|
|
22173
|
+
if (sessionError) {
|
|
22174
|
+
return sessionError;
|
|
22175
|
+
}
|
|
22176
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
22177
|
+
if (protocolError) {
|
|
22178
|
+
return protocolError;
|
|
22179
|
+
}
|
|
22180
|
+
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
22181
|
+
await this.close();
|
|
22182
|
+
return new Response(null, { status: 200 });
|
|
22183
|
+
}
|
|
22184
|
+
/**
|
|
22185
|
+
* Validates session ID for non-initialization requests.
|
|
22186
|
+
* Returns Response error if invalid, undefined otherwise
|
|
22187
|
+
*/
|
|
22188
|
+
validateSession(req) {
|
|
22189
|
+
if (this.sessionIdGenerator === void 0) {
|
|
22190
|
+
return void 0;
|
|
22191
|
+
}
|
|
22192
|
+
if (!this._initialized) {
|
|
22193
|
+
this.onerror?.(new Error("Bad Request: Server not initialized"));
|
|
22194
|
+
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Server not initialized");
|
|
22195
|
+
}
|
|
22196
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
22197
|
+
if (!sessionId) {
|
|
22198
|
+
this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required"));
|
|
22199
|
+
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Mcp-Session-Id header is required");
|
|
22200
|
+
}
|
|
22201
|
+
if (sessionId !== this.sessionId) {
|
|
22202
|
+
this.onerror?.(new Error("Session not found"));
|
|
22203
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
22204
|
+
}
|
|
22205
|
+
return void 0;
|
|
22206
|
+
}
|
|
22207
|
+
/**
|
|
22208
|
+
* Validates the MCP-Protocol-Version header on incoming requests.
|
|
22209
|
+
*
|
|
22210
|
+
* For initialization: Version negotiation handles unknown versions gracefully
|
|
22211
|
+
* (server responds with its supported version).
|
|
22212
|
+
*
|
|
22213
|
+
* For subsequent requests with MCP-Protocol-Version header:
|
|
22214
|
+
* - Accept if in supported list
|
|
22215
|
+
* - 400 if unsupported
|
|
22216
|
+
*
|
|
22217
|
+
* For HTTP requests without the MCP-Protocol-Version header:
|
|
22218
|
+
* - Accept and default to the version negotiated at initialization
|
|
22219
|
+
*/
|
|
22220
|
+
validateProtocolVersion(req) {
|
|
22221
|
+
const protocolVersion = req.headers.get("mcp-protocol-version");
|
|
22222
|
+
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
|
|
22223
|
+
this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`));
|
|
22224
|
+
return this.createJsonErrorResponse(400, -32e3, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
|
|
22225
|
+
}
|
|
22226
|
+
return void 0;
|
|
22227
|
+
}
|
|
22228
|
+
async close() {
|
|
22229
|
+
this._streamMapping.forEach(({ cleanup }) => {
|
|
22230
|
+
cleanup();
|
|
22231
|
+
});
|
|
22232
|
+
this._streamMapping.clear();
|
|
22233
|
+
this._requestResponseMap.clear();
|
|
22234
|
+
this.onclose?.();
|
|
22235
|
+
}
|
|
22236
|
+
/**
|
|
22237
|
+
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
22238
|
+
* Use this to implement polling behavior during long-running operations -
|
|
22239
|
+
* client will reconnect after the retry interval specified in the priming event.
|
|
22240
|
+
*/
|
|
22241
|
+
closeSSEStream(requestId) {
|
|
22242
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
22243
|
+
if (!streamId)
|
|
22244
|
+
return;
|
|
22245
|
+
const stream = this._streamMapping.get(streamId);
|
|
22246
|
+
if (stream) {
|
|
22247
|
+
stream.cleanup();
|
|
22248
|
+
}
|
|
22249
|
+
}
|
|
22250
|
+
/**
|
|
22251
|
+
* Close the standalone GET SSE stream, triggering client reconnection.
|
|
22252
|
+
* Use this to implement polling behavior for server-initiated notifications.
|
|
22253
|
+
*/
|
|
22254
|
+
closeStandaloneSSEStream() {
|
|
22255
|
+
const stream = this._streamMapping.get(this._standaloneSseStreamId);
|
|
22256
|
+
if (stream) {
|
|
22257
|
+
stream.cleanup();
|
|
22258
|
+
}
|
|
22259
|
+
}
|
|
22260
|
+
async send(message, options) {
|
|
22261
|
+
let requestId = options?.relatedRequestId;
|
|
22262
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
22263
|
+
requestId = message.id;
|
|
22264
|
+
}
|
|
22265
|
+
if (requestId === void 0) {
|
|
22266
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
22267
|
+
throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
|
|
22268
|
+
}
|
|
22269
|
+
let eventId;
|
|
22270
|
+
if (this._eventStore) {
|
|
22271
|
+
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
22272
|
+
}
|
|
22273
|
+
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
22274
|
+
if (standaloneSse === void 0) {
|
|
22275
|
+
return;
|
|
22276
|
+
}
|
|
22277
|
+
if (standaloneSse.controller && standaloneSse.encoder) {
|
|
22278
|
+
this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
22279
|
+
}
|
|
22280
|
+
return;
|
|
22281
|
+
}
|
|
22282
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
22283
|
+
if (!streamId) {
|
|
22284
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
22285
|
+
}
|
|
22286
|
+
const stream = this._streamMapping.get(streamId);
|
|
22287
|
+
if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
|
|
22288
|
+
let eventId;
|
|
22289
|
+
if (this._eventStore) {
|
|
22290
|
+
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
22291
|
+
}
|
|
22292
|
+
this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
22293
|
+
}
|
|
22294
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
22295
|
+
this._requestResponseMap.set(requestId, message);
|
|
22296
|
+
const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id]) => id);
|
|
22297
|
+
const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
|
|
22298
|
+
if (allResponsesReady) {
|
|
22299
|
+
if (!stream) {
|
|
22300
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
22301
|
+
}
|
|
22302
|
+
if (this._enableJsonResponse && stream.resolveJson) {
|
|
22303
|
+
const headers = {
|
|
22304
|
+
"Content-Type": "application/json"
|
|
22305
|
+
};
|
|
22306
|
+
if (this.sessionId !== void 0) {
|
|
22307
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
22308
|
+
}
|
|
22309
|
+
const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
|
|
22310
|
+
if (responses.length === 1) {
|
|
22311
|
+
stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
|
|
22312
|
+
} else {
|
|
22313
|
+
stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
|
|
22314
|
+
}
|
|
22315
|
+
} else {
|
|
22316
|
+
stream.cleanup();
|
|
22317
|
+
}
|
|
22318
|
+
for (const id of relatedIds) {
|
|
22319
|
+
this._requestResponseMap.delete(id);
|
|
22320
|
+
this._requestToStreamMapping.delete(id);
|
|
22321
|
+
}
|
|
22322
|
+
}
|
|
22323
|
+
}
|
|
22324
|
+
}
|
|
22325
|
+
};
|
|
22326
|
+
|
|
22327
|
+
// ../node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
|
|
22328
|
+
var StreamableHTTPServerTransport = class {
|
|
22329
|
+
constructor(options = {}) {
|
|
22330
|
+
this._requestContext = /* @__PURE__ */ new WeakMap();
|
|
22331
|
+
this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);
|
|
22332
|
+
this._requestListener = getRequestListener(async (webRequest) => {
|
|
22333
|
+
const context = this._requestContext.get(webRequest);
|
|
22334
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
22335
|
+
authInfo: context?.authInfo,
|
|
22336
|
+
parsedBody: context?.parsedBody
|
|
22337
|
+
});
|
|
22338
|
+
}, { overrideGlobalObjects: false });
|
|
22339
|
+
}
|
|
22340
|
+
/**
|
|
22341
|
+
* Gets the session ID for this transport instance.
|
|
22342
|
+
*/
|
|
22343
|
+
get sessionId() {
|
|
22344
|
+
return this._webStandardTransport.sessionId;
|
|
22345
|
+
}
|
|
22346
|
+
/**
|
|
22347
|
+
* Sets callback for when the transport is closed.
|
|
22348
|
+
*/
|
|
22349
|
+
set onclose(handler) {
|
|
22350
|
+
this._webStandardTransport.onclose = handler;
|
|
22351
|
+
}
|
|
22352
|
+
get onclose() {
|
|
22353
|
+
return this._webStandardTransport.onclose;
|
|
22354
|
+
}
|
|
22355
|
+
/**
|
|
22356
|
+
* Sets callback for transport errors.
|
|
22357
|
+
*/
|
|
22358
|
+
set onerror(handler) {
|
|
22359
|
+
this._webStandardTransport.onerror = handler;
|
|
22360
|
+
}
|
|
22361
|
+
get onerror() {
|
|
22362
|
+
return this._webStandardTransport.onerror;
|
|
22363
|
+
}
|
|
22364
|
+
/**
|
|
22365
|
+
* Sets callback for incoming messages.
|
|
22366
|
+
*/
|
|
22367
|
+
set onmessage(handler) {
|
|
22368
|
+
this._webStandardTransport.onmessage = handler;
|
|
22369
|
+
}
|
|
22370
|
+
get onmessage() {
|
|
22371
|
+
return this._webStandardTransport.onmessage;
|
|
22372
|
+
}
|
|
22373
|
+
/**
|
|
22374
|
+
* Starts the transport. This is required by the Transport interface but is a no-op
|
|
22375
|
+
* for the Streamable HTTP transport as connections are managed per-request.
|
|
22376
|
+
*/
|
|
22377
|
+
async start() {
|
|
22378
|
+
return this._webStandardTransport.start();
|
|
22379
|
+
}
|
|
22380
|
+
/**
|
|
22381
|
+
* Closes the transport and all active connections.
|
|
22382
|
+
*/
|
|
22383
|
+
async close() {
|
|
22384
|
+
return this._webStandardTransport.close();
|
|
22385
|
+
}
|
|
22386
|
+
/**
|
|
22387
|
+
* Sends a JSON-RPC message through the transport.
|
|
22388
|
+
*/
|
|
22389
|
+
async send(message, options) {
|
|
22390
|
+
return this._webStandardTransport.send(message, options);
|
|
22391
|
+
}
|
|
22392
|
+
/**
|
|
22393
|
+
* Handles an incoming HTTP request, whether GET or POST.
|
|
22394
|
+
*
|
|
22395
|
+
* This method converts Node.js HTTP objects to Web Standard Request/Response
|
|
22396
|
+
* and delegates to the underlying WebStandardStreamableHTTPServerTransport.
|
|
22397
|
+
*
|
|
22398
|
+
* @param req - Node.js IncomingMessage, optionally with auth property from middleware
|
|
22399
|
+
* @param res - Node.js ServerResponse
|
|
22400
|
+
* @param parsedBody - Optional pre-parsed body from body-parser middleware
|
|
22401
|
+
*/
|
|
22402
|
+
async handleRequest(req, res, parsedBody) {
|
|
22403
|
+
const authInfo = req.auth;
|
|
22404
|
+
const handler = getRequestListener(async (webRequest) => {
|
|
22405
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
22406
|
+
authInfo,
|
|
22407
|
+
parsedBody
|
|
22408
|
+
});
|
|
22409
|
+
}, { overrideGlobalObjects: false });
|
|
22410
|
+
await handler(req, res);
|
|
22411
|
+
}
|
|
22412
|
+
/**
|
|
22413
|
+
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
22414
|
+
* Use this to implement polling behavior during long-running operations -
|
|
22415
|
+
* client will reconnect after the retry interval specified in the priming event.
|
|
22416
|
+
*/
|
|
22417
|
+
closeSSEStream(requestId) {
|
|
22418
|
+
this._webStandardTransport.closeSSEStream(requestId);
|
|
22419
|
+
}
|
|
22420
|
+
/**
|
|
22421
|
+
* Close the standalone GET SSE stream, triggering client reconnection.
|
|
22422
|
+
* Use this to implement polling behavior for server-initiated notifications.
|
|
22423
|
+
*/
|
|
22424
|
+
closeStandaloneSSEStream() {
|
|
22425
|
+
this._webStandardTransport.closeStandaloneSSEStream();
|
|
22426
|
+
}
|
|
22427
|
+
};
|
|
22428
|
+
|
|
21102
22429
|
// src/index.ts
|
|
21103
22430
|
import { homedir } from "node:os";
|
|
21104
|
-
import { resolve } from "node:path";
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
|
|
22431
|
+
import { resolve, join, dirname } from "node:path";
|
|
22432
|
+
import { fileURLToPath } from "node:url";
|
|
22433
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
22434
|
+
import { createServer as createHttpServer } from "node:http";
|
|
22435
|
+
import * as fs from "node:fs";
|
|
22436
|
+
import { adapt_wrapper } from "@adapt-toolkit/sdk/executables";
|
|
22437
|
+
import { PacketWrapperConfigurator } from "@adapt-toolkit/sdk/wrappers";
|
|
22438
|
+
import { object_to_adapt_value } from "@adapt-toolkit/sdk/wrapper";
|
|
22439
|
+
var VERSION = true ? "0.4.0" : "0.0.0-dev";
|
|
22440
|
+
var STATE_DIR = resolve(
|
|
22441
|
+
process.env.A2ADAPT_STATE_DIR ?? resolve(homedir(), ".a2adapt")
|
|
22442
|
+
);
|
|
22443
|
+
var BROKER_URL = process.env.A2ADAPT_BROKER_URL ?? "wss://a2adapt.adaptframework.solutions/broker";
|
|
22444
|
+
var TRANSPORT = process.env.A2ADAPT_TRANSPORT ?? "http";
|
|
22445
|
+
var PORT = parseInt(process.env.A2ADAPT_PORT ?? "3030", 10);
|
|
22446
|
+
var log = (...parts) => process.stderr.write(`a2adapt: ${parts.join(" ")}
|
|
22447
|
+
`);
|
|
22448
|
+
var NAME_RE = /^[A-Za-z0-9 _.-]{1,64}$/;
|
|
22449
|
+
function validateName(name) {
|
|
22450
|
+
if (!NAME_RE.test(name)) {
|
|
22451
|
+
return "name must be 1-64 chars of letters, digits, space, _ . or -";
|
|
22452
|
+
}
|
|
22453
|
+
if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
|
|
22454
|
+
return "invalid name";
|
|
22455
|
+
}
|
|
22456
|
+
return null;
|
|
22457
|
+
}
|
|
22458
|
+
function locateUnit() {
|
|
22459
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
22460
|
+
const override = process.env.A2ADAPT_UNIT_DIR;
|
|
22461
|
+
const candidates = override ? [resolve(override)] : [join(here, "mufl_code"), join(here, "..", "mufl_code")];
|
|
22462
|
+
for (const dir of candidates) {
|
|
22463
|
+
if (!fs.existsSync(dir)) continue;
|
|
22464
|
+
const muflo = fs.readdirSync(dir).find((f) => f.endsWith(".muflo"));
|
|
22465
|
+
if (muflo) {
|
|
22466
|
+
const hash = muflo.slice(0, -".muflo".length);
|
|
22467
|
+
const contents = new Uint8Array(fs.readFileSync(join(dir, muflo)));
|
|
22468
|
+
return { dir, hash, contents };
|
|
22469
|
+
}
|
|
22470
|
+
}
|
|
22471
|
+
throw new Error(
|
|
22472
|
+
`no compiled .muflo packet found (looked in: ${candidates.join(", ")})`
|
|
22473
|
+
);
|
|
22474
|
+
}
|
|
22475
|
+
var UNIT;
|
|
22476
|
+
var wrapper;
|
|
22477
|
+
var identities = /* @__PURE__ */ new Map();
|
|
22478
|
+
var sessionBinding = /* @__PURE__ */ new Map();
|
|
22479
|
+
var bindingOwner = /* @__PURE__ */ new Map();
|
|
22480
|
+
var evictedSessions = /* @__PURE__ */ new Set();
|
|
22481
|
+
var identityDir = (name) => join(STATE_DIR, name);
|
|
22482
|
+
var seedPath = (dir) => join(dir, "identity.seed");
|
|
22483
|
+
var dataPath = (dir) => join(dir, "state_data.bin");
|
|
22484
|
+
var cursorPath = (dir) => join(dir, "inbox_cursor");
|
|
22485
|
+
var inboxLogPath = (dir) => join(dir, "inbox.log");
|
|
22486
|
+
function listPersistedNames() {
|
|
22487
|
+
if (!fs.existsSync(STATE_DIR)) return [];
|
|
22488
|
+
return fs.readdirSync(STATE_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && fs.existsSync(seedPath(join(STATE_DIR, d.name)))).map((d) => d.name);
|
|
22489
|
+
}
|
|
22490
|
+
function hasSavedState(dir) {
|
|
22491
|
+
try {
|
|
22492
|
+
return fs.existsSync(dataPath(dir)) && fs.statSync(dataPath(dir)).size > 0;
|
|
22493
|
+
} catch {
|
|
22494
|
+
return false;
|
|
22495
|
+
}
|
|
22496
|
+
}
|
|
22497
|
+
function saveState(id) {
|
|
22498
|
+
try {
|
|
22499
|
+
const exported = id.pw.packet.ExecuteTransaction(
|
|
22500
|
+
object_to_adapt_value({ name: "::actor::export_state", targ: void 0 })
|
|
22501
|
+
);
|
|
22502
|
+
const bytes = Buffer.from(exported.Serialize());
|
|
22503
|
+
fs.mkdirSync(id.dir, { recursive: true });
|
|
22504
|
+
const tmp = `${dataPath(id.dir)}.tmp`;
|
|
22505
|
+
fs.writeFileSync(tmp, bytes);
|
|
22506
|
+
fs.renameSync(tmp, dataPath(id.dir));
|
|
22507
|
+
} catch (err) {
|
|
22508
|
+
log(`[${id.name}] failed to save state:`, String(err));
|
|
22509
|
+
}
|
|
22510
|
+
}
|
|
22511
|
+
function readCursor(dir) {
|
|
22512
|
+
try {
|
|
22513
|
+
return parseInt(fs.readFileSync(cursorPath(dir), "utf8").trim(), 10) || 0;
|
|
22514
|
+
} catch {
|
|
22515
|
+
return 0;
|
|
22516
|
+
}
|
|
22517
|
+
}
|
|
22518
|
+
function writeCursor(dir, n) {
|
|
22519
|
+
try {
|
|
22520
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22521
|
+
fs.writeFileSync(cursorPath(dir), String(n));
|
|
22522
|
+
} catch {
|
|
22523
|
+
}
|
|
22524
|
+
}
|
|
22525
|
+
function appendInboxLog(id, sender, text, date3) {
|
|
22526
|
+
try {
|
|
22527
|
+
fs.mkdirSync(id.dir, { recursive: true });
|
|
22528
|
+
const line = JSON.stringify({ sender, text, date: date3 }) + "\n";
|
|
22529
|
+
fs.appendFileSync(inboxLogPath(id.dir), line);
|
|
22530
|
+
} catch (err) {
|
|
22531
|
+
log(`[${id.name}] failed to append inbox.log:`, String(err));
|
|
22532
|
+
}
|
|
22533
|
+
}
|
|
22534
|
+
var activeMcpServers = /* @__PURE__ */ new Set();
|
|
22535
|
+
var inboxResourceUri = (name) => `a2adapt://inbox/${encodeURIComponent(name)}`;
|
|
22536
|
+
function pushNotification(identityName, summary) {
|
|
22537
|
+
log(`[${identityName}] notify:`, summary);
|
|
22538
|
+
for (const server of activeMcpServers) {
|
|
22539
|
+
try {
|
|
22540
|
+
server.sendLoggingMessage({ level: "info", logger: "a2adapt", data: summary });
|
|
22541
|
+
} catch (e) {
|
|
22542
|
+
log("sendLoggingMessage failed:", String(e));
|
|
22543
|
+
}
|
|
22544
|
+
try {
|
|
22545
|
+
server.server.sendResourceUpdated({ uri: inboxResourceUri(identityName) });
|
|
22546
|
+
} catch {
|
|
22547
|
+
}
|
|
22548
|
+
}
|
|
22549
|
+
}
|
|
22550
|
+
function readonlyTx(id, name) {
|
|
22551
|
+
return id.pw.packet.ExecuteTransaction(
|
|
22552
|
+
object_to_adapt_value({ name, targ: void 0 })
|
|
22553
|
+
);
|
|
22554
|
+
}
|
|
22555
|
+
async function withLock(id, fn) {
|
|
22556
|
+
const prev = id.lock;
|
|
22557
|
+
let release;
|
|
22558
|
+
id.lock = new Promise((r) => release = r);
|
|
22559
|
+
await prev;
|
|
22560
|
+
try {
|
|
22561
|
+
return await fn();
|
|
22562
|
+
} finally {
|
|
22563
|
+
release();
|
|
22564
|
+
}
|
|
22565
|
+
}
|
|
22566
|
+
function enqueueMutation(id, envelope, timeoutMs = 25e3) {
|
|
22567
|
+
return new Promise((res, rej) => {
|
|
22568
|
+
const timer = setTimeout(() => {
|
|
22569
|
+
const i = id.pending.findIndex((p) => p.timer === timer);
|
|
22570
|
+
if (i >= 0) id.pending.splice(i, 1);
|
|
22571
|
+
rej(new Error("timed out waiting for the transaction result"));
|
|
22572
|
+
}, timeoutMs);
|
|
22573
|
+
id.pending.push({ resolve: res, reject: rej, timer });
|
|
22574
|
+
id.pw.add_client_message(envelope);
|
|
22575
|
+
});
|
|
22576
|
+
}
|
|
22577
|
+
function mutatingTx(id, name, targ, timeoutMs) {
|
|
22578
|
+
const envelope = object_to_adapt_value({ name, targ });
|
|
22579
|
+
return withLock(id, () => enqueueMutation(id, envelope, timeoutMs));
|
|
22580
|
+
}
|
|
22581
|
+
function wireHandlers(id) {
|
|
22582
|
+
id.pw.on_return_data = (data) => {
|
|
22583
|
+
const kind = data.Reduce("kind").Visualize();
|
|
22584
|
+
if (kind === "save_state") {
|
|
22585
|
+
saveState(id);
|
|
22586
|
+
return;
|
|
22587
|
+
}
|
|
22588
|
+
if (kind === "notify_agent") {
|
|
22589
|
+
const payload = data.Reduce("payload");
|
|
22590
|
+
const event = payload.Reduce("event").Visualize();
|
|
22591
|
+
if (event === "message_received") {
|
|
22592
|
+
const sender = payload.Reduce("sender_name").Visualize();
|
|
22593
|
+
const text = payload.Reduce("text").Visualize();
|
|
22594
|
+
const date3 = payload.Reduce("date").Visualize();
|
|
22595
|
+
appendInboxLog(id, sender, text, date3);
|
|
22596
|
+
process.nextTick(
|
|
22597
|
+
() => pushNotification(id.name, `[${id.name}] new message from ${sender}: ${text} (${date3})`)
|
|
22598
|
+
);
|
|
22599
|
+
} else if (event === "contact_accepted") {
|
|
22600
|
+
const name = payload.Reduce("name").Visualize();
|
|
22601
|
+
const cid = payload.Reduce("container_id").Visualize();
|
|
22602
|
+
process.nextTick(
|
|
22603
|
+
() => pushNotification(id.name, `[${id.name}] contact "${name}" (${cid}) accepted your invite.`)
|
|
22604
|
+
);
|
|
22605
|
+
}
|
|
22606
|
+
return;
|
|
22607
|
+
}
|
|
22608
|
+
const p = id.pending.shift();
|
|
22609
|
+
if (!p) return;
|
|
22610
|
+
clearTimeout(p.timer);
|
|
22611
|
+
p.resolve(data.Reduce("payload"));
|
|
22612
|
+
};
|
|
22613
|
+
id.pw.on_transaction_failure = (message) => {
|
|
22614
|
+
const p = id.pending.shift();
|
|
22615
|
+
if (p) {
|
|
22616
|
+
clearTimeout(p.timer);
|
|
22617
|
+
p.reject(new Error(message));
|
|
22618
|
+
} else {
|
|
22619
|
+
log(`[${id.name}] inbound transaction rejected:`, message);
|
|
22620
|
+
}
|
|
22621
|
+
};
|
|
22622
|
+
}
|
|
22623
|
+
function createPacket(name, seed, dir) {
|
|
22624
|
+
const config2 = new PacketWrapperConfigurator();
|
|
22625
|
+
config2.process_arguments([
|
|
22626
|
+
"--unit_hash",
|
|
22627
|
+
UNIT.hash,
|
|
22628
|
+
"--seed_phrase",
|
|
22629
|
+
seed,
|
|
22630
|
+
"--unit_dir_path",
|
|
22631
|
+
UNIT.dir
|
|
22632
|
+
]);
|
|
22633
|
+
return new Promise((resolveCreate, rejectCreate) => {
|
|
22634
|
+
const timer = setTimeout(
|
|
22635
|
+
() => rejectCreate(new Error(`packet creation for "${name}" timed out`)),
|
|
22636
|
+
3e4
|
|
22637
|
+
);
|
|
22638
|
+
wrapper.packet_manager.create_packet(
|
|
22639
|
+
config2,
|
|
22640
|
+
(pw) => {
|
|
22641
|
+
clearTimeout(timer);
|
|
22642
|
+
const id = {
|
|
22643
|
+
name,
|
|
22644
|
+
seed,
|
|
22645
|
+
cid: pw.packet.GetContainerID().Visualize(),
|
|
22646
|
+
pw,
|
|
22647
|
+
dir,
|
|
22648
|
+
pending: [],
|
|
22649
|
+
lock: Promise.resolve()
|
|
22650
|
+
};
|
|
22651
|
+
wireHandlers(id);
|
|
22652
|
+
identities.set(name, id);
|
|
22653
|
+
log(`[${name}] packet created \u2014 container id ${id.cid}`);
|
|
22654
|
+
resolveCreate(id);
|
|
22655
|
+
},
|
|
22656
|
+
UNIT.contents
|
|
22657
|
+
);
|
|
22658
|
+
});
|
|
22659
|
+
}
|
|
22660
|
+
async function provisionIdentity(name) {
|
|
22661
|
+
const dir = identityDir(name);
|
|
22662
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22663
|
+
const seed = randomBytes(24).toString("hex");
|
|
22664
|
+
fs.writeFileSync(seedPath(dir), seed, { mode: 384 });
|
|
22665
|
+
const id = await createPacket(name, seed, dir);
|
|
22666
|
+
await mutatingTx(id, "::actor::set_my_name", { name });
|
|
22667
|
+
saveState(id);
|
|
22668
|
+
return id;
|
|
22669
|
+
}
|
|
22670
|
+
async function restoreIdentity(name) {
|
|
22671
|
+
const dir = identityDir(name);
|
|
22672
|
+
const seed = fs.readFileSync(seedPath(dir), "utf8").trim();
|
|
22673
|
+
const id = await createPacket(name, seed, dir);
|
|
22674
|
+
if (hasSavedState(dir)) {
|
|
22675
|
+
try {
|
|
22676
|
+
const buf = fs.readFileSync(dataPath(dir));
|
|
22677
|
+
const adaptData = id.pw.packet.ParseValue(new Uint8Array(buf));
|
|
22678
|
+
await mutatingTx(id, "::actor::import_state", adaptData);
|
|
22679
|
+
} catch (err) {
|
|
22680
|
+
log(`[${name}] failed to import saved state (continuing fresh):`, String(err));
|
|
22681
|
+
}
|
|
22682
|
+
}
|
|
22683
|
+
return id;
|
|
22684
|
+
}
|
|
22685
|
+
async function bootWrapper() {
|
|
22686
|
+
UNIT = locateUnit();
|
|
22687
|
+
const argv = [
|
|
22688
|
+
"--broker_address",
|
|
22689
|
+
BROKER_URL,
|
|
22690
|
+
"--test_mode",
|
|
22691
|
+
"--logger_config",
|
|
22692
|
+
"--level",
|
|
22693
|
+
"INFO",
|
|
22694
|
+
"--stdout",
|
|
22695
|
+
"stderr",
|
|
22696
|
+
"--logger_config_end"
|
|
22697
|
+
];
|
|
22698
|
+
log(`booting wrapper (unit ${UNIT.hash.slice(0, 12)}\u2026, broker ${BROKER_URL})`);
|
|
22699
|
+
wrapper = await adapt_wrapper.start(argv);
|
|
22700
|
+
wrapper.on_packet_created_cb = (cid) => log(`wrapper: packet ready ${cid.slice(0, 12)}\u2026`);
|
|
22701
|
+
wrapper.start();
|
|
22702
|
+
const names = listPersistedNames();
|
|
22703
|
+
if (names.length === 0) {
|
|
22704
|
+
log("no persisted identities \u2014 start with create_identity");
|
|
22705
|
+
} else {
|
|
22706
|
+
log(`restoring ${names.length} identit${names.length === 1 ? "y" : "ies"}: ${names.join(", ")}`);
|
|
22707
|
+
for (const name of names) {
|
|
22708
|
+
try {
|
|
22709
|
+
await restoreIdentity(name);
|
|
22710
|
+
} catch (err) {
|
|
22711
|
+
log(`failed to restore "${name}":`, String(err));
|
|
22712
|
+
}
|
|
22713
|
+
}
|
|
22714
|
+
}
|
|
22715
|
+
}
|
|
22716
|
+
function resolveBound(sessionId) {
|
|
22717
|
+
if (evictedSessions.has(sessionId)) {
|
|
22718
|
+
return { error: "Your identity binding was reassigned to another session. Call choose_identity again to continue." };
|
|
22719
|
+
}
|
|
22720
|
+
const name = sessionBinding.get(sessionId);
|
|
22721
|
+
if (!name) {
|
|
22722
|
+
return { error: "No identity bound to this session. Call choose_identity (or create_identity) first." };
|
|
22723
|
+
}
|
|
22724
|
+
const id = identities.get(name);
|
|
22725
|
+
if (!id) {
|
|
22726
|
+
sessionBinding.delete(sessionId);
|
|
22727
|
+
bindingOwner.delete(name);
|
|
22728
|
+
return { error: `The bound identity "${name}" no longer exists. Choose another with choose_identity.` };
|
|
22729
|
+
}
|
|
22730
|
+
return { id };
|
|
22731
|
+
}
|
|
22732
|
+
function bindSession(sessionId, name) {
|
|
22733
|
+
const prev = sessionBinding.get(sessionId);
|
|
22734
|
+
if (prev && prev !== name && bindingOwner.get(prev) === sessionId) {
|
|
22735
|
+
bindingOwner.delete(prev);
|
|
22736
|
+
}
|
|
22737
|
+
sessionBinding.set(sessionId, name);
|
|
22738
|
+
bindingOwner.set(name, sessionId);
|
|
22739
|
+
evictedSessions.delete(sessionId);
|
|
22740
|
+
}
|
|
22741
|
+
function renderContacts(v) {
|
|
22742
|
+
const out = [];
|
|
22743
|
+
if (v.IsNil()) return out;
|
|
22744
|
+
for (const key of v.GetKeys()) {
|
|
22745
|
+
const c = v.Reduce(key);
|
|
22746
|
+
if (c.IsNil()) continue;
|
|
22747
|
+
out.push({
|
|
22748
|
+
name: c.Reduce("name").Visualize(),
|
|
22749
|
+
container_id: c.Reduce("container_id").Visualize()
|
|
22750
|
+
});
|
|
22751
|
+
}
|
|
22752
|
+
return out;
|
|
22753
|
+
}
|
|
22754
|
+
function renderInbox(v) {
|
|
22755
|
+
const out = [];
|
|
22756
|
+
if (v.IsNil()) return out;
|
|
22757
|
+
for (let i = 0; ; i++) {
|
|
22758
|
+
const m = v.Reduce(i);
|
|
22759
|
+
if (m.IsNil()) break;
|
|
22760
|
+
out.push({
|
|
22761
|
+
sender_id: m.Reduce("sender_id").Visualize(),
|
|
22762
|
+
sender_name: m.Reduce("sender_name").Visualize(),
|
|
22763
|
+
text: m.Reduce("text").Visualize(),
|
|
22764
|
+
date: m.Reduce("date").Visualize()
|
|
22765
|
+
});
|
|
22766
|
+
}
|
|
22767
|
+
return out;
|
|
22768
|
+
}
|
|
21108
22769
|
function textResult(text, isError = false) {
|
|
21109
22770
|
return { content: [{ type: "text", text }], isError };
|
|
21110
22771
|
}
|
|
21111
|
-
function
|
|
21112
|
-
|
|
21113
|
-
|
|
21114
|
-
|
|
21115
|
-
|
|
21116
|
-
|
|
21117
|
-
|
|
22772
|
+
function createMcpServer(getSessionId) {
|
|
22773
|
+
const server = new McpServer(
|
|
22774
|
+
{ name: "a2adapt", version: VERSION },
|
|
22775
|
+
{ capabilities: { logging: {}, resources: {} } }
|
|
22776
|
+
);
|
|
22777
|
+
activeMcpServers.add(server);
|
|
22778
|
+
const boundOr = () => {
|
|
22779
|
+
const b = resolveBound(getSessionId());
|
|
22780
|
+
return "error" in b ? { err: textResult(b.error, true) } : { id: b.id };
|
|
22781
|
+
};
|
|
22782
|
+
server.tool(
|
|
22783
|
+
"create_identity",
|
|
22784
|
+
"Create a new self-sovereign identity (an ADAPT node) with the given display name and bind it to this session. The name is what peers see for you in invites. Persisted permanently; reject if the name already exists.",
|
|
22785
|
+
{ name: external_exports.string().min(1).describe('Display name for the new identity, e.g. "Alice".') },
|
|
22786
|
+
async ({ name }) => {
|
|
22787
|
+
const bad = validateName(name);
|
|
22788
|
+
if (bad) return textResult(`create_identity failed: ${bad}`, true);
|
|
22789
|
+
if (identities.has(name)) return textResult(`create_identity failed: an identity named "${name}" already exists.`, true);
|
|
22790
|
+
try {
|
|
22791
|
+
const id = await provisionIdentity(name);
|
|
22792
|
+
bindSession(getSessionId(), name);
|
|
22793
|
+
return textResult(`Created identity "${name}" (${id.cid}) and bound it to this session.`);
|
|
22794
|
+
} catch (err) {
|
|
22795
|
+
return textResult(`create_identity failed: ${String(err)}`, true);
|
|
22796
|
+
}
|
|
22797
|
+
}
|
|
22798
|
+
);
|
|
22799
|
+
server.tool(
|
|
22800
|
+
"choose_identity",
|
|
22801
|
+
"Bind an existing identity to this session so the messaging tools act as it. Binding is exclusive: if the identity is already in use by another session, this is declined unless force=true, which evicts the other session.",
|
|
22802
|
+
{
|
|
22803
|
+
name: external_exports.string().min(1).describe("Name of the identity to bind."),
|
|
22804
|
+
force: external_exports.boolean().default(false).describe("Evict another session that holds this identity.")
|
|
22805
|
+
},
|
|
22806
|
+
async ({ name, force }) => {
|
|
22807
|
+
if (!identities.has(name)) {
|
|
22808
|
+
return textResult(`choose_identity failed: no identity named "${name}". Create it with create_identity.`, true);
|
|
22809
|
+
}
|
|
22810
|
+
const sid = getSessionId();
|
|
22811
|
+
const holder = bindingOwner.get(name);
|
|
22812
|
+
if (holder && holder !== sid) {
|
|
22813
|
+
if (!force) {
|
|
22814
|
+
return textResult(`choose_identity failed: "${name}" is currently bound to another session. Retry with force=true to take it over.`, true);
|
|
22815
|
+
}
|
|
22816
|
+
evictedSessions.add(holder);
|
|
22817
|
+
sessionBinding.delete(holder);
|
|
22818
|
+
bindingOwner.delete(name);
|
|
22819
|
+
}
|
|
22820
|
+
bindSession(sid, name);
|
|
22821
|
+
const id = identities.get(name);
|
|
22822
|
+
return textResult(`Bound to identity "${name}" (${id.cid}).`);
|
|
22823
|
+
}
|
|
22824
|
+
);
|
|
22825
|
+
server.tool(
|
|
22826
|
+
"list_identities",
|
|
22827
|
+
"List all identities hosted by this node (name + container id), marking which one is bound to this session and which are in use elsewhere.",
|
|
22828
|
+
{},
|
|
22829
|
+
async () => {
|
|
22830
|
+
if (identities.size === 0) return textResult("No identities yet. Create one with create_identity.");
|
|
22831
|
+
const sid = getSessionId();
|
|
22832
|
+
const mine = sessionBinding.get(sid);
|
|
22833
|
+
const lines = [...identities.values()].map((id) => {
|
|
22834
|
+
const holder = bindingOwner.get(id.name);
|
|
22835
|
+
const tag = id.name === mine ? " \u2190 this session" : holder ? " (in use by another session)" : "";
|
|
22836
|
+
return `\u2022 ${id.name} \u2014 ${id.cid}${tag}`;
|
|
22837
|
+
});
|
|
22838
|
+
return textResult(`Identities (${identities.size}):
|
|
22839
|
+
${lines.join("\n")}`);
|
|
22840
|
+
}
|
|
22841
|
+
);
|
|
22842
|
+
server.tool(
|
|
22843
|
+
"current_identity",
|
|
22844
|
+
"Report the identity currently bound to this session (if any).",
|
|
22845
|
+
{},
|
|
22846
|
+
async () => {
|
|
22847
|
+
const b = resolveBound(getSessionId());
|
|
22848
|
+
if ("error" in b) return textResult(b.error);
|
|
22849
|
+
return textResult(`Bound to "${b.id.name}" (${b.id.cid}).`);
|
|
22850
|
+
}
|
|
22851
|
+
);
|
|
22852
|
+
server.tool(
|
|
22853
|
+
"remove_identity",
|
|
22854
|
+
"Permanently delete a persisted identity \u2014 its packet and all on-disk state. This cannot be undone.",
|
|
22855
|
+
{ name: external_exports.string().min(1).describe("Name of the identity to delete.") },
|
|
22856
|
+
async ({ name }) => {
|
|
22857
|
+
const id = identities.get(name);
|
|
22858
|
+
if (!id) return textResult(`remove_identity failed: no identity named "${name}".`, true);
|
|
22859
|
+
try {
|
|
22860
|
+
wrapper.remove_packet(id.cid);
|
|
22861
|
+
} catch (err) {
|
|
22862
|
+
log(`remove_packet(${id.cid}) failed:`, String(err));
|
|
22863
|
+
}
|
|
22864
|
+
identities.delete(name);
|
|
22865
|
+
const holder = bindingOwner.get(name);
|
|
22866
|
+
if (holder) {
|
|
22867
|
+
bindingOwner.delete(name);
|
|
22868
|
+
sessionBinding.delete(holder);
|
|
22869
|
+
}
|
|
22870
|
+
try {
|
|
22871
|
+
fs.rmSync(id.dir, { recursive: true, force: true });
|
|
22872
|
+
} catch (err) {
|
|
22873
|
+
return textResult(`Identity "${name}" removed from memory, but deleting ${id.dir} failed: ${String(err)}`, true);
|
|
22874
|
+
}
|
|
22875
|
+
return textResult(`Removed identity "${name}" and its state.`);
|
|
22876
|
+
}
|
|
22877
|
+
);
|
|
22878
|
+
server.resource(
|
|
22879
|
+
"inbox",
|
|
22880
|
+
"a2adapt://inbox",
|
|
22881
|
+
{ description: "Decrypted incoming messages for the bound identity \u2014 auto-notifies on new arrivals." },
|
|
22882
|
+
async () => {
|
|
22883
|
+
const { id, err } = boundOr();
|
|
22884
|
+
const uri = id ? inboxResourceUri(id.name) : "a2adapt://inbox";
|
|
22885
|
+
if (err || !id) return { contents: [{ uri, mimeType: "text/plain", text: "No identity bound to this session." }] };
|
|
22886
|
+
const inbox = renderInbox(readonlyTx(id, "::actor::list_incoming_messages"));
|
|
22887
|
+
const text = inbox.length === 0 ? "Inbox is empty." : `Inbox (${inbox.length}):
|
|
22888
|
+
${inbox.map((m, i) => `${i + 1}. [${m.sender_name}] ${m.text} (${m.date})`).join("\n")}`;
|
|
22889
|
+
return { contents: [{ uri, mimeType: "text/plain", text }] };
|
|
22890
|
+
}
|
|
22891
|
+
);
|
|
22892
|
+
server.tool(
|
|
22893
|
+
"generate_invite",
|
|
22894
|
+
"Generate a named invite to share out-of-band with another agent. The invite carries your identity and display name; whoever redeems it is registered under the name you pass here. Requires a bound identity.",
|
|
22895
|
+
{ name: external_exports.string().min(1).describe('Name to register the peer who redeems this invite, e.g. "Bob".') },
|
|
22896
|
+
async ({ name }) => {
|
|
22897
|
+
const { id, err } = boundOr();
|
|
22898
|
+
if (err) return err;
|
|
22899
|
+
try {
|
|
22900
|
+
const data = await mutatingTx(id, "::actor::generate_invite", { name });
|
|
22901
|
+
const blob = Buffer.from(data.Reduce("invite").GetBinary()).toString("base64");
|
|
22902
|
+
return textResult(
|
|
22903
|
+
`Invite for "${name}" created. Share this blob out-of-band (they paste it into add_contact):
|
|
21118
22904
|
|
|
21119
|
-
|
|
21120
|
-
|
|
21121
|
-
|
|
22905
|
+
${blob}`
|
|
22906
|
+
);
|
|
22907
|
+
} catch (e) {
|
|
22908
|
+
return textResult(`generate_invite failed: ${String(e)}`, true);
|
|
22909
|
+
}
|
|
22910
|
+
}
|
|
22911
|
+
);
|
|
22912
|
+
server.tool(
|
|
22913
|
+
"add_contact",
|
|
22914
|
+
"Add a contact from an invite blob produced by another agent's generate_invite. If no name is given, the inviter's embedded display name is used. Also replies to the inviter so they register you back. Requires a bound identity.",
|
|
22915
|
+
{
|
|
22916
|
+
invite: external_exports.string().min(1).describe("The base64 invite blob to redeem."),
|
|
22917
|
+
name: external_exports.string().min(1).optional().describe("Optional custom name for the inviter; defaults to their own name.")
|
|
22918
|
+
},
|
|
22919
|
+
async ({ invite, name }) => {
|
|
22920
|
+
const { id, err } = boundOr();
|
|
22921
|
+
if (err) return err;
|
|
22922
|
+
let buf;
|
|
22923
|
+
try {
|
|
22924
|
+
buf = Buffer.from(invite.trim(), "base64");
|
|
22925
|
+
if (buf.length === 0) throw new Error("empty");
|
|
22926
|
+
} catch {
|
|
22927
|
+
return textResult("add_contact failed: the invite blob is not valid base64.", true);
|
|
22928
|
+
}
|
|
22929
|
+
try {
|
|
22930
|
+
const blobValue = id.pw.packet.NewBinaryFromBuffer(buf);
|
|
22931
|
+
const targ = { invite: blobValue };
|
|
22932
|
+
if (name) targ.name = name;
|
|
22933
|
+
const data = await mutatingTx(id, "::actor::add_contact", targ);
|
|
22934
|
+
const added = data.Reduce("added").Visualize();
|
|
22935
|
+
const cid = data.Reduce("container_id").Visualize();
|
|
22936
|
+
return textResult(`Added contact "${added}" (${cid}).`);
|
|
22937
|
+
} catch (e) {
|
|
22938
|
+
return textResult(`add_contact failed: ${String(e)}`, true);
|
|
22939
|
+
}
|
|
22940
|
+
}
|
|
21122
22941
|
);
|
|
22942
|
+
server.tool(
|
|
22943
|
+
"list_contacts",
|
|
22944
|
+
"List the contacts the bound identity knows about (name + container id).",
|
|
22945
|
+
{},
|
|
22946
|
+
async () => {
|
|
22947
|
+
const { id, err } = boundOr();
|
|
22948
|
+
if (err) return err;
|
|
22949
|
+
try {
|
|
22950
|
+
const contacts = renderContacts(readonlyTx(id, "::actor::list_contacts"));
|
|
22951
|
+
if (contacts.length === 0) return textResult("No contacts yet.");
|
|
22952
|
+
return textResult(`Contacts (${contacts.length}):
|
|
22953
|
+
${contacts.map((c) => `\u2022 ${c.name} \u2014 ${c.container_id}`).join("\n")}`);
|
|
22954
|
+
} catch (e) {
|
|
22955
|
+
return textResult(`list_contacts failed: ${String(e)}`, true);
|
|
22956
|
+
}
|
|
22957
|
+
}
|
|
22958
|
+
);
|
|
22959
|
+
server.tool(
|
|
22960
|
+
"send_message",
|
|
22961
|
+
"Send an end-to-end-encrypted message to a known contact (by name or container id). Requires a bound identity.",
|
|
22962
|
+
{
|
|
22963
|
+
contact: external_exports.string().min(1).describe("Contact name or container id to send to."),
|
|
22964
|
+
text: external_exports.string().min(1).describe("The message text.")
|
|
22965
|
+
},
|
|
22966
|
+
async ({ contact, text }) => {
|
|
22967
|
+
const { id, err } = boundOr();
|
|
22968
|
+
if (err) return err;
|
|
22969
|
+
try {
|
|
22970
|
+
await mutatingTx(id, "::actor::send_message", { contact, text });
|
|
22971
|
+
return textResult(`Message sent to "${contact}".`);
|
|
22972
|
+
} catch (e) {
|
|
22973
|
+
return textResult(`send_message failed: ${String(e)}`, true);
|
|
22974
|
+
}
|
|
22975
|
+
}
|
|
22976
|
+
);
|
|
22977
|
+
server.tool(
|
|
22978
|
+
"list_incoming_messages",
|
|
22979
|
+
"List all received messages in the bound identity's inbox (decrypted, with sender).",
|
|
22980
|
+
{},
|
|
22981
|
+
async () => {
|
|
22982
|
+
const { id, err } = boundOr();
|
|
22983
|
+
if (err) return err;
|
|
22984
|
+
try {
|
|
22985
|
+
const inbox = renderInbox(readonlyTx(id, "::actor::list_incoming_messages"));
|
|
22986
|
+
if (inbox.length === 0) return textResult("Inbox is empty.");
|
|
22987
|
+
return textResult(`Inbox (${inbox.length}):
|
|
22988
|
+
${inbox.map((m, i) => `${i + 1}. [${m.sender_name}] ${m.text} (${m.date})`).join("\n")}`);
|
|
22989
|
+
} catch (e) {
|
|
22990
|
+
return textResult(`list_incoming_messages failed: ${String(e)}`, true);
|
|
22991
|
+
}
|
|
22992
|
+
}
|
|
22993
|
+
);
|
|
22994
|
+
server.tool(
|
|
22995
|
+
"process_incoming_message",
|
|
22996
|
+
"Surface messages that have arrived for the bound identity since the last check, and mark them processed (non-blocking \u2014 use to poll for new mail).",
|
|
22997
|
+
{},
|
|
22998
|
+
async () => {
|
|
22999
|
+
const { id, err } = boundOr();
|
|
23000
|
+
if (err) return err;
|
|
23001
|
+
try {
|
|
23002
|
+
const inbox = renderInbox(readonlyTx(id, "::actor::list_incoming_messages"));
|
|
23003
|
+
const cursor = readCursor(id.dir);
|
|
23004
|
+
const fresh = inbox.slice(cursor);
|
|
23005
|
+
writeCursor(id.dir, inbox.length);
|
|
23006
|
+
if (fresh.length === 0) return textResult("No new messages.");
|
|
23007
|
+
return textResult(`${fresh.length} new message(s):
|
|
23008
|
+
${fresh.map((m) => `\u2022 [${m.sender_name}] ${m.text} (${m.date})`).join("\n")}`);
|
|
23009
|
+
} catch (e) {
|
|
23010
|
+
return textResult(`process_incoming_message failed: ${String(e)}`, true);
|
|
23011
|
+
}
|
|
23012
|
+
}
|
|
23013
|
+
);
|
|
23014
|
+
return server;
|
|
23015
|
+
}
|
|
23016
|
+
function readBody(req) {
|
|
23017
|
+
return new Promise((resolve2, reject) => {
|
|
23018
|
+
let data = "";
|
|
23019
|
+
req.on("data", (chunk) => data += chunk);
|
|
23020
|
+
req.on("end", () => {
|
|
23021
|
+
try {
|
|
23022
|
+
resolve2(JSON.parse(data));
|
|
23023
|
+
} catch (e) {
|
|
23024
|
+
reject(e);
|
|
23025
|
+
}
|
|
23026
|
+
});
|
|
23027
|
+
req.on("error", reject);
|
|
23028
|
+
});
|
|
21123
23029
|
}
|
|
21124
|
-
var server = new McpServer({ name: "a2adapt", version: VERSION });
|
|
21125
|
-
server.tool(
|
|
21126
|
-
"generate_invite",
|
|
21127
|
-
"Generate a named invite to share with another agent out-of-band. The invite carries your public identity and a default display name; the receiver adds it with add_contact and may keep that name or set their own.",
|
|
21128
|
-
{
|
|
21129
|
-
name: external_exports.string().min(1).describe('The contact name to associate with this invite, e.g. "Alex".')
|
|
21130
|
-
},
|
|
21131
|
-
async (_args) => nodeNotWiredYet("generate_invite")
|
|
21132
|
-
);
|
|
21133
|
-
server.tool(
|
|
21134
|
-
"add_contact",
|
|
21135
|
-
"Add a contact from an invite blob produced by another agent's generate_invite. If no name is given, the invite's embedded default name is used.",
|
|
21136
|
-
{
|
|
21137
|
-
invite: external_exports.string().min(1).describe("The invite blob (copy-pasted string) to add."),
|
|
21138
|
-
name: external_exports.string().min(1).optional().describe("Optional custom name; defaults to the name embedded in the invite.")
|
|
21139
|
-
},
|
|
21140
|
-
async (_args) => nodeNotWiredYet("add_contact")
|
|
21141
|
-
);
|
|
21142
|
-
server.tool(
|
|
21143
|
-
"list_contacts",
|
|
21144
|
-
"List the contacts this node knows about (name + container id).",
|
|
21145
|
-
{},
|
|
21146
|
-
async () => nodeNotWiredYet("list_contacts")
|
|
21147
|
-
);
|
|
21148
|
-
server.tool(
|
|
21149
|
-
"send_message",
|
|
21150
|
-
"Send an end-to-end-encrypted message to a known contact.",
|
|
21151
|
-
{
|
|
21152
|
-
contact: external_exports.string().min(1).describe("Contact name or container id to send to."),
|
|
21153
|
-
text: external_exports.string().min(1).describe("The message text.")
|
|
21154
|
-
},
|
|
21155
|
-
async (_args) => nodeNotWiredYet("send_message")
|
|
21156
|
-
);
|
|
21157
|
-
server.tool(
|
|
21158
|
-
"process_incoming_message",
|
|
21159
|
-
"Drain newly-arrived inbound messages from the broker, apply them to the node, and surface the decrypted contents with sender attribution.",
|
|
21160
|
-
{},
|
|
21161
|
-
async () => nodeNotWiredYet("process_incoming_message")
|
|
21162
|
-
);
|
|
21163
|
-
server.tool(
|
|
21164
|
-
"list_incoming_messages",
|
|
21165
|
-
"List received messages currently held in the node inbox (decrypted, with sender).",
|
|
21166
|
-
{},
|
|
21167
|
-
async () => nodeNotWiredYet("list_incoming_messages")
|
|
21168
|
-
);
|
|
21169
23030
|
async function main() {
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
21173
|
-
|
|
23031
|
+
if (TRANSPORT === "stdio") {
|
|
23032
|
+
const server = createMcpServer(() => "stdio");
|
|
23033
|
+
const transport = new StdioServerTransport();
|
|
23034
|
+
await server.connect(transport);
|
|
23035
|
+
log("MCP stdio transport connected, booting wrapper\u2026");
|
|
23036
|
+
await bootWrapper();
|
|
23037
|
+
log(`MCP server v${VERSION} ready (transport=stdio, identities=${identities.size}, state=${STATE_DIR}, broker=${BROKER_URL})`);
|
|
23038
|
+
const flush = () => {
|
|
23039
|
+
for (const id of identities.values()) saveState(id);
|
|
23040
|
+
process.exit(0);
|
|
23041
|
+
};
|
|
23042
|
+
process.on("SIGINT", flush);
|
|
23043
|
+
process.on("SIGTERM", flush);
|
|
23044
|
+
return;
|
|
23045
|
+
}
|
|
23046
|
+
log("booting wrapper\u2026");
|
|
23047
|
+
await bootWrapper();
|
|
23048
|
+
log(`wrapper ready (identities=${identities.size}), starting HTTP server\u2026`);
|
|
23049
|
+
const transports = {};
|
|
23050
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
23051
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
23052
|
+
if (url.pathname !== "/mcp") {
|
|
23053
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
23054
|
+
res.end("Not found");
|
|
23055
|
+
return;
|
|
23056
|
+
}
|
|
23057
|
+
try {
|
|
23058
|
+
if (req.method === "POST") {
|
|
23059
|
+
const body = await readBody(req);
|
|
23060
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
23061
|
+
if (sessionId && transports[sessionId]) {
|
|
23062
|
+
await transports[sessionId].handleRequest(req, res, body);
|
|
23063
|
+
} else if (!sessionId && isInitializeRequest(body)) {
|
|
23064
|
+
const transport = new StreamableHTTPServerTransport({
|
|
23065
|
+
sessionIdGenerator: () => randomUUID(),
|
|
23066
|
+
onsessioninitialized: (sid) => {
|
|
23067
|
+
transports[sid] = transport;
|
|
23068
|
+
log(`session ${sid.slice(0, 8)}\u2026 initialized`);
|
|
23069
|
+
}
|
|
23070
|
+
});
|
|
23071
|
+
const server = createMcpServer(() => transport.sessionId ?? "pending");
|
|
23072
|
+
transport.onclose = () => {
|
|
23073
|
+
const sid = transport.sessionId;
|
|
23074
|
+
if (sid) {
|
|
23075
|
+
delete transports[sid];
|
|
23076
|
+
const name = sessionBinding.get(sid);
|
|
23077
|
+
if (name && bindingOwner.get(name) === sid) bindingOwner.delete(name);
|
|
23078
|
+
sessionBinding.delete(sid);
|
|
23079
|
+
evictedSessions.delete(sid);
|
|
23080
|
+
log(`session ${sid.slice(0, 8)}\u2026 closed`);
|
|
23081
|
+
}
|
|
23082
|
+
activeMcpServers.delete(server);
|
|
23083
|
+
};
|
|
23084
|
+
await server.connect(transport);
|
|
23085
|
+
await transport.handleRequest(req, res, body);
|
|
23086
|
+
} else {
|
|
23087
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
23088
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Bad Request: No valid session ID" }, id: null }));
|
|
23089
|
+
}
|
|
23090
|
+
} else if (req.method === "GET") {
|
|
23091
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
23092
|
+
if (!sessionId || !transports[sessionId]) {
|
|
23093
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
23094
|
+
res.end("Invalid or missing session ID");
|
|
23095
|
+
return;
|
|
23096
|
+
}
|
|
23097
|
+
await transports[sessionId].handleRequest(req, res);
|
|
23098
|
+
} else if (req.method === "DELETE") {
|
|
23099
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
23100
|
+
if (!sessionId || !transports[sessionId]) {
|
|
23101
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
23102
|
+
res.end("Invalid or missing session ID");
|
|
23103
|
+
return;
|
|
23104
|
+
}
|
|
23105
|
+
await transports[sessionId].handleRequest(req, res);
|
|
23106
|
+
} else {
|
|
23107
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
23108
|
+
res.end("Method not allowed");
|
|
23109
|
+
}
|
|
23110
|
+
} catch (err) {
|
|
23111
|
+
log("HTTP handler error:", String(err));
|
|
23112
|
+
if (!res.headersSent) {
|
|
23113
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
23114
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }));
|
|
23115
|
+
}
|
|
23116
|
+
}
|
|
23117
|
+
});
|
|
23118
|
+
httpServer.listen(PORT, () => {
|
|
23119
|
+
log(`MCP server v${VERSION} ready (transport=http, port=${PORT}, identities=${identities.size}, state=${STATE_DIR}, broker=${BROKER_URL})`);
|
|
23120
|
+
});
|
|
23121
|
+
const shutdown = async () => {
|
|
23122
|
+
log("shutting down\u2026");
|
|
23123
|
+
for (const sid of Object.keys(transports)) {
|
|
23124
|
+
try {
|
|
23125
|
+
await transports[sid].close();
|
|
23126
|
+
} catch {
|
|
23127
|
+
}
|
|
23128
|
+
delete transports[sid];
|
|
23129
|
+
}
|
|
23130
|
+
for (const id of identities.values()) saveState(id);
|
|
23131
|
+
httpServer.close();
|
|
23132
|
+
process.exit(0);
|
|
23133
|
+
};
|
|
23134
|
+
process.on("SIGINT", shutdown);
|
|
23135
|
+
process.on("SIGTERM", shutdown);
|
|
21174
23136
|
}
|
|
21175
23137
|
main().catch((err) => {
|
|
21176
|
-
|
|
21177
|
-
`);
|
|
23138
|
+
log(`fatal startup error: ${err?.stack ?? err}`);
|
|
21178
23139
|
process.exit(1);
|
|
21179
23140
|
});
|