ofw-mcp 2.10.2 → 2.12.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/auth.js +86 -56
- package/dist/bundle.js +740 -228
- package/dist/cache/store.js +6 -1
- package/dist/client.js +24 -12
- package/dist/index.js +1 -1
- package/dist/session-cache.js +78 -0
- package/dist/tools/expenses.js +21 -3
- package/dist/tools/journal.js +21 -3
- package/dist/tools/messages.js +60 -6
- package/dist/tools/pagination.js +120 -0
- package/mint.yaml +143 -0
- package/package.json +8 -6
- package/server.json +14 -2
- package/skills/ofw/SKILL.md +5 -4
package/dist/bundle.js
CHANGED
|
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
|
|
|
2991
2991
|
const schOrFunc = root.refs[ref];
|
|
2992
2992
|
if (schOrFunc)
|
|
2993
2993
|
return schOrFunc;
|
|
2994
|
-
let _sch =
|
|
2994
|
+
let _sch = resolve3.call(this, root, ref);
|
|
2995
2995
|
if (_sch === void 0) {
|
|
2996
2996
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
2997
2997
|
const { schemaId } = this.opts;
|
|
@@ -3018,7 +3018,7 @@ var require_compile = __commonJS({
|
|
|
3018
3018
|
function sameSchemaEnv(s1, s2) {
|
|
3019
3019
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3020
3020
|
}
|
|
3021
|
-
function
|
|
3021
|
+
function resolve3(root, ref) {
|
|
3022
3022
|
let sch;
|
|
3023
3023
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3024
3024
|
ref = sch;
|
|
@@ -3649,7 +3649,7 @@ var require_fast_uri = __commonJS({
|
|
|
3649
3649
|
}
|
|
3650
3650
|
return uri;
|
|
3651
3651
|
}
|
|
3652
|
-
function
|
|
3652
|
+
function resolve3(baseURI, relativeURI, options) {
|
|
3653
3653
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3654
3654
|
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3655
3655
|
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
@@ -3933,7 +3933,7 @@ var require_fast_uri = __commonJS({
|
|
|
3933
3933
|
var fastUri = {
|
|
3934
3934
|
SCHEMES,
|
|
3935
3935
|
normalize,
|
|
3936
|
-
resolve:
|
|
3936
|
+
resolve: resolve3,
|
|
3937
3937
|
resolveComponent,
|
|
3938
3938
|
equal,
|
|
3939
3939
|
serialize,
|
|
@@ -9161,7 +9161,7 @@ var require_websocket = __commonJS({
|
|
|
9161
9161
|
var http = __require("http");
|
|
9162
9162
|
var net = __require("net");
|
|
9163
9163
|
var tls = __require("tls");
|
|
9164
|
-
var { randomBytes, createHash: createHash2 } = __require("crypto");
|
|
9164
|
+
var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
|
|
9165
9165
|
var { Duplex, Readable } = __require("stream");
|
|
9166
9166
|
var { URL: URL2 } = __require("url");
|
|
9167
9167
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -9699,7 +9699,7 @@ var require_websocket = __commonJS({
|
|
|
9699
9699
|
}
|
|
9700
9700
|
}
|
|
9701
9701
|
const defaultPort = isSecure ? 443 : 80;
|
|
9702
|
-
const key =
|
|
9702
|
+
const key = randomBytes2(16).toString("base64");
|
|
9703
9703
|
const request = isSecure ? https.request : http.request;
|
|
9704
9704
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
9705
9705
|
let perMessageDeflate;
|
|
@@ -32550,7 +32550,7 @@ var Protocol = class {
|
|
|
32550
32550
|
return;
|
|
32551
32551
|
}
|
|
32552
32552
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
32553
|
-
await new Promise((
|
|
32553
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
32554
32554
|
options?.signal?.throwIfAborted();
|
|
32555
32555
|
}
|
|
32556
32556
|
} catch (error51) {
|
|
@@ -32567,7 +32567,7 @@ var Protocol = class {
|
|
|
32567
32567
|
*/
|
|
32568
32568
|
request(request, resultSchema, options) {
|
|
32569
32569
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
32570
|
-
return new Promise((
|
|
32570
|
+
return new Promise((resolve3, reject) => {
|
|
32571
32571
|
const earlyReject = (error51) => {
|
|
32572
32572
|
reject(error51);
|
|
32573
32573
|
};
|
|
@@ -32645,7 +32645,7 @@ var Protocol = class {
|
|
|
32645
32645
|
if (!parseResult.success) {
|
|
32646
32646
|
reject(parseResult.error);
|
|
32647
32647
|
} else {
|
|
32648
|
-
|
|
32648
|
+
resolve3(parseResult.data);
|
|
32649
32649
|
}
|
|
32650
32650
|
} catch (error51) {
|
|
32651
32651
|
reject(error51);
|
|
@@ -32906,12 +32906,12 @@ var Protocol = class {
|
|
|
32906
32906
|
}
|
|
32907
32907
|
} catch {
|
|
32908
32908
|
}
|
|
32909
|
-
return new Promise((
|
|
32909
|
+
return new Promise((resolve3, reject) => {
|
|
32910
32910
|
if (signal.aborted) {
|
|
32911
32911
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
32912
32912
|
return;
|
|
32913
32913
|
}
|
|
32914
|
-
const timeoutId = setTimeout(
|
|
32914
|
+
const timeoutId = setTimeout(resolve3, interval);
|
|
32915
32915
|
signal.addEventListener("abort", () => {
|
|
32916
32916
|
clearTimeout(timeoutId);
|
|
32917
32917
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -34002,7 +34002,7 @@ var McpServer = class {
|
|
|
34002
34002
|
let task = createTaskResult.task;
|
|
34003
34003
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
34004
34004
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
34005
|
-
await new Promise((
|
|
34005
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
34006
34006
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
34007
34007
|
if (!updatedTask) {
|
|
34008
34008
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -34666,64 +34666,17 @@ var StdioServerTransport = class {
|
|
|
34666
34666
|
this.onclose?.();
|
|
34667
34667
|
}
|
|
34668
34668
|
send(message) {
|
|
34669
|
-
return new Promise((
|
|
34669
|
+
return new Promise((resolve3) => {
|
|
34670
34670
|
const json2 = serializeMessage(message);
|
|
34671
34671
|
if (this._stdout.write(json2)) {
|
|
34672
|
-
|
|
34672
|
+
resolve3();
|
|
34673
34673
|
} else {
|
|
34674
|
-
this._stdout.once("drain",
|
|
34674
|
+
this._stdout.once("drain", resolve3);
|
|
34675
34675
|
}
|
|
34676
34676
|
});
|
|
34677
34677
|
}
|
|
34678
34678
|
};
|
|
34679
34679
|
|
|
34680
|
-
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
34681
|
-
async function createMcpServer(opts) {
|
|
34682
|
-
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
34683
|
-
if (opts.banner !== void 0) {
|
|
34684
|
-
console.error(opts.banner);
|
|
34685
|
-
}
|
|
34686
|
-
const deps = opts.deps;
|
|
34687
|
-
for (const register of opts.tools) {
|
|
34688
|
-
await register(server, deps);
|
|
34689
|
-
}
|
|
34690
|
-
return server;
|
|
34691
|
-
}
|
|
34692
|
-
function withGracefulShutdown(server, opts = {}) {
|
|
34693
|
-
const shouldExit = opts.exit ?? true;
|
|
34694
|
-
let shuttingDown = false;
|
|
34695
|
-
const handler = (signal) => {
|
|
34696
|
-
if (shuttingDown)
|
|
34697
|
-
return;
|
|
34698
|
-
shuttingDown = true;
|
|
34699
|
-
void (async () => {
|
|
34700
|
-
try {
|
|
34701
|
-
if (opts.onSignal)
|
|
34702
|
-
await opts.onSignal(signal);
|
|
34703
|
-
await server.close();
|
|
34704
|
-
} catch (err) {
|
|
34705
|
-
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34706
|
-
} finally {
|
|
34707
|
-
if (shouldExit)
|
|
34708
|
-
process.exit(0);
|
|
34709
|
-
}
|
|
34710
|
-
})();
|
|
34711
|
-
};
|
|
34712
|
-
process.on("SIGINT", () => handler("SIGINT"));
|
|
34713
|
-
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
34714
|
-
}
|
|
34715
|
-
async function runMcp(opts) {
|
|
34716
|
-
const server = await createMcpServer(opts);
|
|
34717
|
-
const shutdown = opts.shutdown ?? true;
|
|
34718
|
-
if (shutdown !== false) {
|
|
34719
|
-
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
34720
|
-
}
|
|
34721
|
-
const spec = opts.transport ?? "stdio";
|
|
34722
|
-
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
34723
|
-
await server.connect(transport);
|
|
34724
|
-
return server;
|
|
34725
|
-
}
|
|
34726
|
-
|
|
34727
34680
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
34728
34681
|
var McpToolError = class extends Error {
|
|
34729
34682
|
/** Actionable remediation text, when one applies. */
|
|
@@ -34736,6 +34689,9 @@ var McpToolError = class extends Error {
|
|
|
34736
34689
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
34737
34690
|
}
|
|
34738
34691
|
};
|
|
34692
|
+
function createHelpfulError(message, opts) {
|
|
34693
|
+
return new McpToolError(message, opts);
|
|
34694
|
+
}
|
|
34739
34695
|
var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
34740
34696
|
var JWT_RE = /\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
34741
34697
|
var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
|
|
@@ -34773,6 +34729,81 @@ function textResult(data) {
|
|
|
34773
34729
|
function rawTextResult(text) {
|
|
34774
34730
|
return { content: [{ type: "text", text }] };
|
|
34775
34731
|
}
|
|
34732
|
+
function errorResult(message) {
|
|
34733
|
+
return {
|
|
34734
|
+
content: [{ type: "text", text: redactSecrets(message) }],
|
|
34735
|
+
isError: true
|
|
34736
|
+
};
|
|
34737
|
+
}
|
|
34738
|
+
|
|
34739
|
+
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
34740
|
+
function hintResultOrRethrow(err) {
|
|
34741
|
+
if (err instanceof McpToolError && err.hint) {
|
|
34742
|
+
return errorResult(`${err.message}
|
|
34743
|
+
|
|
34744
|
+
Hint: ${err.hint}`);
|
|
34745
|
+
}
|
|
34746
|
+
throw err;
|
|
34747
|
+
}
|
|
34748
|
+
function surfaceToolHints(server) {
|
|
34749
|
+
const register = server.registerTool.bind(server);
|
|
34750
|
+
server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
|
|
34751
|
+
let result;
|
|
34752
|
+
try {
|
|
34753
|
+
result = cb(...args);
|
|
34754
|
+
} catch (err) {
|
|
34755
|
+
return hintResultOrRethrow(err);
|
|
34756
|
+
}
|
|
34757
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
34758
|
+
});
|
|
34759
|
+
}
|
|
34760
|
+
async function createMcpServer(opts) {
|
|
34761
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
34762
|
+
if (opts.surfaceHints !== false)
|
|
34763
|
+
surfaceToolHints(server);
|
|
34764
|
+
if (opts.banner !== void 0) {
|
|
34765
|
+
console.error(opts.banner);
|
|
34766
|
+
}
|
|
34767
|
+
const deps = opts.deps;
|
|
34768
|
+
for (const register of opts.tools) {
|
|
34769
|
+
await register(server, deps);
|
|
34770
|
+
}
|
|
34771
|
+
return server;
|
|
34772
|
+
}
|
|
34773
|
+
function withGracefulShutdown(server, opts = {}) {
|
|
34774
|
+
const shouldExit = opts.exit ?? true;
|
|
34775
|
+
let shuttingDown = false;
|
|
34776
|
+
const handler = (signal) => {
|
|
34777
|
+
if (shuttingDown)
|
|
34778
|
+
return;
|
|
34779
|
+
shuttingDown = true;
|
|
34780
|
+
void (async () => {
|
|
34781
|
+
try {
|
|
34782
|
+
if (opts.onSignal)
|
|
34783
|
+
await opts.onSignal(signal);
|
|
34784
|
+
await server.close();
|
|
34785
|
+
} catch (err) {
|
|
34786
|
+
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34787
|
+
} finally {
|
|
34788
|
+
if (shouldExit)
|
|
34789
|
+
process.exit(0);
|
|
34790
|
+
}
|
|
34791
|
+
})();
|
|
34792
|
+
};
|
|
34793
|
+
process.on("SIGINT", () => handler("SIGINT"));
|
|
34794
|
+
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
34795
|
+
}
|
|
34796
|
+
async function runMcp(opts) {
|
|
34797
|
+
const server = await createMcpServer(opts);
|
|
34798
|
+
const shutdown = opts.shutdown ?? true;
|
|
34799
|
+
if (shutdown !== false) {
|
|
34800
|
+
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
34801
|
+
}
|
|
34802
|
+
const spec = opts.transport ?? "stdio";
|
|
34803
|
+
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
34804
|
+
await server.connect(transport);
|
|
34805
|
+
return server;
|
|
34806
|
+
}
|
|
34776
34807
|
|
|
34777
34808
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
34778
34809
|
import { homedir } from "node:os";
|
|
@@ -34844,6 +34875,34 @@ async function fileBlob(path, opts = {}) {
|
|
|
34844
34875
|
return blob;
|
|
34845
34876
|
}
|
|
34846
34877
|
|
|
34878
|
+
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
34879
|
+
var RateLimitedError = class extends Error {
|
|
34880
|
+
status = 429;
|
|
34881
|
+
constructor(service) {
|
|
34882
|
+
super(`Rate limited (429) by ${service} after retries.`);
|
|
34883
|
+
this.name = "RateLimitedError";
|
|
34884
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34885
|
+
}
|
|
34886
|
+
};
|
|
34887
|
+
var RequestTimeoutError = class extends Error {
|
|
34888
|
+
timeoutMs;
|
|
34889
|
+
constructor(service, timeoutMs) {
|
|
34890
|
+
super(`Request to ${service} timed out after ${timeoutMs}ms.`);
|
|
34891
|
+
this.name = "RequestTimeoutError";
|
|
34892
|
+
this.timeoutMs = timeoutMs;
|
|
34893
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34894
|
+
}
|
|
34895
|
+
};
|
|
34896
|
+
var ApiError = class extends Error {
|
|
34897
|
+
status;
|
|
34898
|
+
constructor(status, message) {
|
|
34899
|
+
super(message);
|
|
34900
|
+
this.name = "ApiError";
|
|
34901
|
+
this.status = status;
|
|
34902
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34903
|
+
}
|
|
34904
|
+
};
|
|
34905
|
+
|
|
34847
34906
|
// node_modules/@chrischall/mcp-utils/dist/zod/parse-lenient.js
|
|
34848
34907
|
function parseLenient(schema, raw, opts) {
|
|
34849
34908
|
const result = schema.safeParse(raw);
|
|
@@ -34878,58 +34937,329 @@ var pageSchema = {
|
|
|
34878
34937
|
page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
|
|
34879
34938
|
};
|
|
34880
34939
|
|
|
34940
|
+
// node_modules/@chrischall/mcp-utils/dist/auth/index.js
|
|
34941
|
+
async function resolveAuthPattern(pattern) {
|
|
34942
|
+
const ordered = [
|
|
34943
|
+
pattern.token,
|
|
34944
|
+
pattern.oauth,
|
|
34945
|
+
pattern.sessionScrape,
|
|
34946
|
+
pattern.fetchproxy
|
|
34947
|
+
];
|
|
34948
|
+
for (const resolver of ordered) {
|
|
34949
|
+
if (resolver)
|
|
34950
|
+
return resolver();
|
|
34951
|
+
}
|
|
34952
|
+
throw createHelpfulError("No auth configured. Provide a token, OAuth credentials, login credentials, or sign in in your browser (fetchproxy fallback).", { hint: "Set one of the supported credential env vars, or sign in in your browser." });
|
|
34953
|
+
}
|
|
34954
|
+
|
|
34881
34955
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
34956
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
34957
|
+
import { dirname, join as join2, resolve as resolve2 } from "node:path";
|
|
34958
|
+
import { homedir as homedir2 } from "node:os";
|
|
34959
|
+
import { randomBytes, createHmac } from "node:crypto";
|
|
34960
|
+
function bindingDigest(secret, salt) {
|
|
34961
|
+
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
34962
|
+
}
|
|
34963
|
+
function isEnvelope(raw) {
|
|
34964
|
+
return raw !== null && typeof raw === "object" && raw.v === 1 && "state" in raw;
|
|
34965
|
+
}
|
|
34966
|
+
var StatePersistenceError = class extends Error {
|
|
34967
|
+
cause;
|
|
34968
|
+
constructor(cause) {
|
|
34969
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
34970
|
+
this.name = "StatePersistenceError";
|
|
34971
|
+
this.cause = cause;
|
|
34972
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34973
|
+
}
|
|
34974
|
+
};
|
|
34975
|
+
function createFileStatePersistence(opts) {
|
|
34976
|
+
const { filePath, validate } = opts;
|
|
34977
|
+
const secret = opts.boundTo;
|
|
34978
|
+
return {
|
|
34979
|
+
load() {
|
|
34980
|
+
if (!existsSync(filePath))
|
|
34981
|
+
return null;
|
|
34982
|
+
try {
|
|
34983
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
34984
|
+
let state;
|
|
34985
|
+
if (isEnvelope(raw)) {
|
|
34986
|
+
const b = raw.boundTo;
|
|
34987
|
+
if (secret === void 0) {
|
|
34988
|
+
if (b !== void 0)
|
|
34989
|
+
return null;
|
|
34990
|
+
} else {
|
|
34991
|
+
if (b === void 0)
|
|
34992
|
+
return null;
|
|
34993
|
+
if (bindingDigest(secret, b.salt) !== b.digest)
|
|
34994
|
+
return null;
|
|
34995
|
+
}
|
|
34996
|
+
state = raw.state;
|
|
34997
|
+
} else {
|
|
34998
|
+
if (secret !== void 0)
|
|
34999
|
+
return null;
|
|
35000
|
+
state = raw;
|
|
35001
|
+
}
|
|
35002
|
+
if (validate !== void 0)
|
|
35003
|
+
return validate(state);
|
|
35004
|
+
return state;
|
|
35005
|
+
} catch {
|
|
35006
|
+
return null;
|
|
35007
|
+
}
|
|
35008
|
+
},
|
|
35009
|
+
save(state) {
|
|
35010
|
+
const dir = dirname(filePath);
|
|
35011
|
+
const tmp = `${filePath}.tmp-${randomBytes(6).toString("hex")}`;
|
|
35012
|
+
const dirExisted = existsSync(dir);
|
|
35013
|
+
try {
|
|
35014
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
35015
|
+
if (!dirExisted)
|
|
35016
|
+
chmodSync(dir, 448);
|
|
35017
|
+
let boundTo;
|
|
35018
|
+
if (secret !== void 0) {
|
|
35019
|
+
const salt = randomBytes(16).toString("hex");
|
|
35020
|
+
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
35021
|
+
}
|
|
35022
|
+
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
35023
|
+
writeFileSync(tmp, JSON.stringify(envelope, null, 2), { mode: 384 });
|
|
35024
|
+
chmodSync(tmp, 384);
|
|
35025
|
+
renameSync(tmp, filePath);
|
|
35026
|
+
chmodSync(filePath, 384);
|
|
35027
|
+
} catch (err) {
|
|
35028
|
+
try {
|
|
35029
|
+
if (existsSync(tmp))
|
|
35030
|
+
unlinkSync(tmp);
|
|
35031
|
+
} catch {
|
|
35032
|
+
}
|
|
35033
|
+
throw err;
|
|
35034
|
+
}
|
|
35035
|
+
},
|
|
35036
|
+
clear() {
|
|
35037
|
+
try {
|
|
35038
|
+
if (existsSync(filePath))
|
|
35039
|
+
unlinkSync(filePath);
|
|
35040
|
+
} catch {
|
|
35041
|
+
}
|
|
35042
|
+
}
|
|
35043
|
+
};
|
|
35044
|
+
}
|
|
35045
|
+
function homeOf(env) {
|
|
35046
|
+
return readEnvVar("HOME", { env }) ?? homedir2();
|
|
35047
|
+
}
|
|
35048
|
+
function resolveStateDir(opts = {}) {
|
|
35049
|
+
const env = opts.env;
|
|
35050
|
+
return join2(readEnvVar("MCP_DATA_DIR", { env }) ?? homeOf(env), opts.subdir ?? "");
|
|
35051
|
+
}
|
|
35052
|
+
function resolveStateFile(opts) {
|
|
35053
|
+
const override = opts.envVar !== void 0 ? readEnvVar(opts.envVar, { env: opts.env }) : void 0;
|
|
35054
|
+
if (override !== void 0) {
|
|
35055
|
+
if (override === "~")
|
|
35056
|
+
return homeOf(opts.env);
|
|
35057
|
+
if (override.startsWith("~/"))
|
|
35058
|
+
return join2(homeOf(opts.env), override.slice(2));
|
|
35059
|
+
return resolve2(override);
|
|
35060
|
+
}
|
|
35061
|
+
const dirOpts = { env: opts.env };
|
|
35062
|
+
if (opts.subdir !== void 0)
|
|
35063
|
+
dirOpts.subdir = opts.subdir;
|
|
35064
|
+
return join2(resolveStateDir(dirOpts), opts.fileName);
|
|
35065
|
+
}
|
|
34882
35066
|
var TOKEN_REFRESH_SKEW_MS = 5 * 60 * 1e3;
|
|
35067
|
+
function defaultIsRefreshRevoked(err) {
|
|
35068
|
+
if (err instanceof RateLimitedError || err instanceof RequestTimeoutError)
|
|
35069
|
+
return false;
|
|
35070
|
+
if (err instanceof ApiError && err.status >= 500)
|
|
35071
|
+
return false;
|
|
35072
|
+
return true;
|
|
35073
|
+
}
|
|
35074
|
+
function isBearerTokens(raw) {
|
|
35075
|
+
if (raw === null || typeof raw !== "object")
|
|
35076
|
+
return false;
|
|
35077
|
+
const t = raw;
|
|
35078
|
+
if (typeof t.accessToken !== "string" || t.accessToken === "")
|
|
35079
|
+
return false;
|
|
35080
|
+
if (typeof t.expiresAt !== "number" || !Number.isFinite(t.expiresAt))
|
|
35081
|
+
return false;
|
|
35082
|
+
return t.refreshToken === void 0 || typeof t.refreshToken === "string";
|
|
35083
|
+
}
|
|
34883
35084
|
var TokenManager = class {
|
|
34884
|
-
|
|
34885
|
-
|
|
34886
|
-
expiresAt;
|
|
35085
|
+
tokens;
|
|
35086
|
+
bootstrapFn;
|
|
34887
35087
|
refreshFn;
|
|
34888
35088
|
skewMs;
|
|
35089
|
+
persistence;
|
|
35090
|
+
now;
|
|
35091
|
+
isRefreshRevokedFn;
|
|
35092
|
+
onPersistErrorFn;
|
|
34889
35093
|
inFlight;
|
|
35094
|
+
bootstrapInFlight;
|
|
35095
|
+
/**
|
|
35096
|
+
* Persistence is consulted at most once per process. Without this the
|
|
35097
|
+
* revoked-token recovery below re-reads the SAME rejected record — `clear()`
|
|
35098
|
+
* is optional on {@link StatePersistence} and its failures are swallowed, so
|
|
35099
|
+
* recovery must not depend on it. After the first read the in-memory tokens
|
|
35100
|
+
* (or their deliberate absence) are the truth.
|
|
35101
|
+
*/
|
|
35102
|
+
persistenceRead = false;
|
|
34890
35103
|
constructor(opts) {
|
|
34891
|
-
|
|
34892
|
-
|
|
34893
|
-
|
|
35104
|
+
if (typeof opts.initial === "function") {
|
|
35105
|
+
this.bootstrapFn = opts.initial;
|
|
35106
|
+
} else {
|
|
35107
|
+
this.tokens = { ...opts.initial };
|
|
35108
|
+
}
|
|
34894
35109
|
this.refreshFn = opts.refresh;
|
|
34895
35110
|
this.skewMs = opts.skewMs ?? TOKEN_REFRESH_SKEW_MS;
|
|
35111
|
+
this.persistence = opts.persistence;
|
|
35112
|
+
this.now = opts.now ?? Date.now;
|
|
35113
|
+
this.isRefreshRevokedFn = opts.isRefreshRevoked ?? defaultIsRefreshRevoked;
|
|
35114
|
+
this.onPersistErrorFn = opts.onPersistError;
|
|
34896
35115
|
}
|
|
34897
35116
|
/** Whether the token is within the skew window of (or past) expiry. */
|
|
34898
35117
|
needsRefresh() {
|
|
34899
|
-
|
|
35118
|
+
if (this.tokens === void 0)
|
|
35119
|
+
return false;
|
|
35120
|
+
return this.now() >= this.tokens.expiresAt - this.skewMs;
|
|
35121
|
+
}
|
|
35122
|
+
/**
|
|
35123
|
+
* A stored token is worth using when it is still valid, OR when it carries a
|
|
35124
|
+
* refresh token — an expired-but-refreshable token still saves the login,
|
|
35125
|
+
* which is the expensive half.
|
|
35126
|
+
*/
|
|
35127
|
+
isUsable(t) {
|
|
35128
|
+
return this.now() < t.expiresAt - this.skewMs || t.refreshToken !== void 0;
|
|
35129
|
+
}
|
|
35130
|
+
/** Read persisted tokens, guarding shape and usability. Never throws. */
|
|
35131
|
+
async loadPersisted() {
|
|
35132
|
+
if (this.persistence === void 0 || this.persistenceRead)
|
|
35133
|
+
return null;
|
|
35134
|
+
this.persistenceRead = true;
|
|
35135
|
+
try {
|
|
35136
|
+
const raw = await this.persistence.load();
|
|
35137
|
+
if (!isBearerTokens(raw) || !this.isUsable(raw))
|
|
35138
|
+
return null;
|
|
35139
|
+
return raw;
|
|
35140
|
+
} catch {
|
|
35141
|
+
return null;
|
|
35142
|
+
}
|
|
35143
|
+
}
|
|
35144
|
+
/**
|
|
35145
|
+
* Write tokens. Silent by default (a lost write costs a future login, not this
|
|
35146
|
+
* request); throws a {@link StatePersistenceError} when `onPersistError` does.
|
|
35147
|
+
*/
|
|
35148
|
+
async persist(t) {
|
|
35149
|
+
if (this.persistence === void 0)
|
|
35150
|
+
return;
|
|
35151
|
+
try {
|
|
35152
|
+
await this.persistence.save(t);
|
|
35153
|
+
} catch (err) {
|
|
35154
|
+
if (this.onPersistErrorFn === void 0)
|
|
35155
|
+
return;
|
|
35156
|
+
try {
|
|
35157
|
+
this.onPersistErrorFn(err);
|
|
35158
|
+
} catch (hookErr) {
|
|
35159
|
+
throw new StatePersistenceError(hookErr);
|
|
35160
|
+
}
|
|
35161
|
+
}
|
|
35162
|
+
}
|
|
35163
|
+
/** Discard persisted tokens (a refresh they could not satisfy). Never throws. */
|
|
35164
|
+
async clearPersisted() {
|
|
35165
|
+
if (this.persistence?.clear === void 0)
|
|
35166
|
+
return;
|
|
35167
|
+
try {
|
|
35168
|
+
await this.persistence.clear();
|
|
35169
|
+
} catch {
|
|
35170
|
+
}
|
|
35171
|
+
}
|
|
35172
|
+
/** The current tokens, single-flighting the bootstrap if there are none. */
|
|
35173
|
+
ensureTokens() {
|
|
35174
|
+
if (this.tokens !== void 0)
|
|
35175
|
+
return Promise.resolve(this.tokens);
|
|
35176
|
+
if (this.bootstrapInFlight === void 0) {
|
|
35177
|
+
this.bootstrapInFlight = this.runBootstrap().finally(() => {
|
|
35178
|
+
this.bootstrapInFlight = void 0;
|
|
35179
|
+
});
|
|
35180
|
+
}
|
|
35181
|
+
return this.bootstrapInFlight;
|
|
35182
|
+
}
|
|
35183
|
+
/** One bootstrap attempt: persisted tokens if usable, else the login. */
|
|
35184
|
+
async runBootstrap() {
|
|
35185
|
+
const stored = await this.loadPersisted();
|
|
35186
|
+
if (stored !== null) {
|
|
35187
|
+
this.tokens = stored;
|
|
35188
|
+
return stored;
|
|
35189
|
+
}
|
|
35190
|
+
if (this.bootstrapFn === void 0) {
|
|
35191
|
+
throw new Error("TokenManager: no tokens and no bootstrap function to mint them.");
|
|
35192
|
+
}
|
|
35193
|
+
const fresh = await this.bootstrapFn();
|
|
35194
|
+
this.tokens = fresh;
|
|
35195
|
+
try {
|
|
35196
|
+
await this.persist(fresh);
|
|
35197
|
+
} catch (err) {
|
|
35198
|
+
throw err instanceof StatePersistenceError ? err.cause : err;
|
|
35199
|
+
}
|
|
35200
|
+
return fresh;
|
|
34900
35201
|
}
|
|
34901
35202
|
/**
|
|
34902
35203
|
* Single-flight refresh. Concurrent callers share one in-flight promise; it is
|
|
34903
35204
|
* cleared on settle (success or failure) so a subsequent refresh can proceed.
|
|
34904
35205
|
*/
|
|
34905
35206
|
refreshNow() {
|
|
34906
|
-
if (
|
|
34907
|
-
|
|
34908
|
-
if (rt === void 0) {
|
|
34909
|
-
return Promise.reject(new Error("TokenManager: cannot refresh \u2014 no refresh token is available."));
|
|
34910
|
-
}
|
|
34911
|
-
this.inFlight = (async () => {
|
|
34912
|
-
const tok = await this.refreshFn(rt);
|
|
34913
|
-
this.accessToken = tok.accessToken;
|
|
34914
|
-
if (tok.refreshToken !== void 0 && tok.refreshToken !== "") {
|
|
34915
|
-
this.refreshToken = tok.refreshToken;
|
|
34916
|
-
}
|
|
34917
|
-
this.expiresAt = tok.expiresAt;
|
|
34918
|
-
})().finally(() => {
|
|
35207
|
+
if (this.inFlight === void 0) {
|
|
35208
|
+
this.inFlight = this.runRefresh().finally(() => {
|
|
34919
35209
|
this.inFlight = void 0;
|
|
34920
35210
|
});
|
|
34921
35211
|
}
|
|
34922
35212
|
return this.inFlight;
|
|
34923
35213
|
}
|
|
35214
|
+
/** One refresh attempt against the current refresh token. */
|
|
35215
|
+
async runRefresh() {
|
|
35216
|
+
const current = this.tokens ?? await this.ensureTokens();
|
|
35217
|
+
const rt = current.refreshToken;
|
|
35218
|
+
if (rt === void 0) {
|
|
35219
|
+
throw new Error("TokenManager: cannot refresh \u2014 no refresh token is available.");
|
|
35220
|
+
}
|
|
35221
|
+
const tok = await this.refreshFn(rt);
|
|
35222
|
+
this.tokens = {
|
|
35223
|
+
accessToken: tok.accessToken,
|
|
35224
|
+
// Rotation is optional: keep the current refresh token when none comes back.
|
|
35225
|
+
refreshToken: tok.refreshToken !== void 0 && tok.refreshToken !== "" ? tok.refreshToken : rt,
|
|
35226
|
+
expiresAt: tok.expiresAt
|
|
35227
|
+
};
|
|
35228
|
+
await this.persist(this.tokens);
|
|
35229
|
+
}
|
|
35230
|
+
/**
|
|
35231
|
+
* Recover from a refresh the current credential could not satisfy — commonly
|
|
35232
|
+
* a refresh token restored from a previous process and revoked since. Without
|
|
35233
|
+
* a bootstrap to fall back on this is terminal; with one, re-minting beats
|
|
35234
|
+
* staying broken forever. Shared so the two entry points cannot diverge.
|
|
35235
|
+
*/
|
|
35236
|
+
async reBootstrap(err) {
|
|
35237
|
+
if (err instanceof StatePersistenceError)
|
|
35238
|
+
throw err.cause;
|
|
35239
|
+
if (this.bootstrapFn === void 0)
|
|
35240
|
+
throw err;
|
|
35241
|
+
if (!this.isRefreshRevokedFn(err))
|
|
35242
|
+
throw err;
|
|
35243
|
+
this.tokens = void 0;
|
|
35244
|
+
await this.clearPersisted();
|
|
35245
|
+
return this.ensureTokens();
|
|
35246
|
+
}
|
|
34924
35247
|
/** Get a valid access token, refreshing proactively inside the skew window. */
|
|
34925
35248
|
async getAccessToken() {
|
|
34926
|
-
|
|
34927
|
-
|
|
34928
|
-
|
|
35249
|
+
let tokens = this.tokens ?? await this.ensureTokens();
|
|
35250
|
+
if (this.needsRefresh()) {
|
|
35251
|
+
try {
|
|
35252
|
+
await this.refreshNow();
|
|
35253
|
+
} catch (err) {
|
|
35254
|
+
return (await this.reBootstrap(err)).accessToken;
|
|
35255
|
+
}
|
|
35256
|
+
tokens = this.tokens ?? tokens;
|
|
35257
|
+
}
|
|
35258
|
+
return tokens.accessToken;
|
|
34929
35259
|
}
|
|
34930
|
-
/** Current absolute expiry (epoch ms). */
|
|
35260
|
+
/** Current absolute expiry (epoch ms), or `0` before the first bootstrap. */
|
|
34931
35261
|
getExpiresAt() {
|
|
34932
|
-
return this.expiresAt;
|
|
35262
|
+
return this.tokens?.expiresAt ?? 0;
|
|
34933
35263
|
}
|
|
34934
35264
|
/**
|
|
34935
35265
|
* Run an authenticated request with reactive 401-replay. `call` receives a
|
|
@@ -34947,16 +35277,21 @@ var TokenManager = class {
|
|
|
34947
35277
|
const usedToken = await this.getAccessToken();
|
|
34948
35278
|
let res = await call(usedToken);
|
|
34949
35279
|
if (res.status === 401) {
|
|
34950
|
-
if (this.accessToken === usedToken)
|
|
34951
|
-
|
|
34952
|
-
|
|
35280
|
+
if (this.tokens?.accessToken === usedToken) {
|
|
35281
|
+
try {
|
|
35282
|
+
await this.refreshNow();
|
|
35283
|
+
} catch (err) {
|
|
35284
|
+
await this.reBootstrap(err);
|
|
35285
|
+
}
|
|
35286
|
+
}
|
|
35287
|
+
res = await call(this.tokens?.accessToken ?? usedToken);
|
|
34953
35288
|
}
|
|
34954
35289
|
return res;
|
|
34955
35290
|
}
|
|
34956
35291
|
};
|
|
34957
35292
|
|
|
34958
35293
|
// src/client.ts
|
|
34959
|
-
import { dirname, join as
|
|
35294
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
34960
35295
|
import { fileURLToPath } from "url";
|
|
34961
35296
|
|
|
34962
35297
|
// node_modules/@fetchproxy/protocol/dist/frames.js
|
|
@@ -36133,7 +36468,7 @@ var DEFAULT_BIND_TIMEOUT_MS = 5e3;
|
|
|
36133
36468
|
async function electRole(opts) {
|
|
36134
36469
|
const server = createServer();
|
|
36135
36470
|
const bindTimeoutMs = opts.bindTimeoutMs ?? DEFAULT_BIND_TIMEOUT_MS;
|
|
36136
|
-
return new Promise((
|
|
36471
|
+
return new Promise((resolve3, reject) => {
|
|
36137
36472
|
let timer;
|
|
36138
36473
|
const clearBindTimer = () => {
|
|
36139
36474
|
if (timer) {
|
|
@@ -36149,7 +36484,7 @@ async function electRole(opts) {
|
|
|
36149
36484
|
server.close();
|
|
36150
36485
|
} catch {
|
|
36151
36486
|
}
|
|
36152
|
-
|
|
36487
|
+
resolve3({ role: "peer" });
|
|
36153
36488
|
} else {
|
|
36154
36489
|
reject(e);
|
|
36155
36490
|
}
|
|
@@ -36157,7 +36492,7 @@ async function electRole(opts) {
|
|
|
36157
36492
|
const onListening = () => {
|
|
36158
36493
|
clearBindTimer();
|
|
36159
36494
|
server.removeListener("error", onError);
|
|
36160
|
-
|
|
36495
|
+
resolve3({ role: "host", server });
|
|
36161
36496
|
};
|
|
36162
36497
|
server.once("error", onError);
|
|
36163
36498
|
server.once("listening", onListening);
|
|
@@ -36319,16 +36654,16 @@ async function awaitSessionReady(ready, opts) {
|
|
|
36319
36654
|
|
|
36320
36655
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
36321
36656
|
import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
36322
|
-
import { join as
|
|
36657
|
+
import { join as join4 } from "node:path";
|
|
36323
36658
|
|
|
36324
36659
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
36325
36660
|
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
36326
|
-
import { join as
|
|
36327
|
-
import { homedir as
|
|
36661
|
+
import { join as join3 } from "node:path";
|
|
36662
|
+
import { homedir as homedir3 } from "node:os";
|
|
36328
36663
|
var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
|
|
36329
36664
|
var SAFE_SCOPED = /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
36330
36665
|
function defaultIdentityDir() {
|
|
36331
|
-
return
|
|
36666
|
+
return join3(homedir3(), ".fetchproxy", "identity");
|
|
36332
36667
|
}
|
|
36333
36668
|
function safeIdentityFileBase(serverName) {
|
|
36334
36669
|
if (!serverName || serverName === ".." || serverName.includes("..") || !SAFE_PLAIN.test(serverName) && !SAFE_SCOPED.test(serverName)) {
|
|
@@ -36338,7 +36673,7 @@ function safeIdentityFileBase(serverName) {
|
|
|
36338
36673
|
}
|
|
36339
36674
|
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
36340
36675
|
const safeFile = safeIdentityFileBase(serverName);
|
|
36341
|
-
const path =
|
|
36676
|
+
const path = join3(dir, `${safeFile}.json`);
|
|
36342
36677
|
await mkdir(dir, { recursive: true, mode: 448 });
|
|
36343
36678
|
try {
|
|
36344
36679
|
const raw = await readFile(path, "utf8");
|
|
@@ -36410,13 +36745,13 @@ function decideExtensionTrust(args) {
|
|
|
36410
36745
|
};
|
|
36411
36746
|
}
|
|
36412
36747
|
function extensionTrustPath(serverName, dir = defaultIdentityDir()) {
|
|
36413
|
-
return
|
|
36748
|
+
return join4(dir, `${safeIdentityFileBase(serverName)}.extension-trust.json`);
|
|
36414
36749
|
}
|
|
36415
36750
|
function extensionTrustPathHint(serverName) {
|
|
36416
36751
|
try {
|
|
36417
36752
|
return extensionTrustPath(serverName);
|
|
36418
36753
|
} catch {
|
|
36419
|
-
return
|
|
36754
|
+
return join4(defaultIdentityDir(), "<server-name>.extension-trust.json");
|
|
36420
36755
|
}
|
|
36421
36756
|
}
|
|
36422
36757
|
function isPin(x) {
|
|
@@ -36503,8 +36838,8 @@ async function startHost(opts) {
|
|
|
36503
36838
|
let rejectOwnSession;
|
|
36504
36839
|
let ownSessionReady;
|
|
36505
36840
|
function resetSessionPromise() {
|
|
36506
|
-
ownSessionReady = new Promise((
|
|
36507
|
-
resolveOwnSession =
|
|
36841
|
+
ownSessionReady = new Promise((resolve3, reject) => {
|
|
36842
|
+
resolveOwnSession = resolve3;
|
|
36508
36843
|
rejectOwnSession = reject;
|
|
36509
36844
|
});
|
|
36510
36845
|
ownSessionReady.catch(() => {
|
|
@@ -36725,7 +37060,7 @@ async function startHost(opts) {
|
|
|
36725
37060
|
});
|
|
36726
37061
|
});
|
|
36727
37062
|
return {
|
|
36728
|
-
close: () => new Promise((
|
|
37063
|
+
close: () => new Promise((resolve3) => {
|
|
36729
37064
|
for (const client2 of wss.clients) {
|
|
36730
37065
|
try {
|
|
36731
37066
|
client2.terminate();
|
|
@@ -36733,7 +37068,7 @@ async function startHost(opts) {
|
|
|
36733
37068
|
}
|
|
36734
37069
|
}
|
|
36735
37070
|
wss.close(() => {
|
|
36736
|
-
opts.httpServer.close(() =>
|
|
37071
|
+
opts.httpServer.close(() => resolve3());
|
|
36737
37072
|
});
|
|
36738
37073
|
}),
|
|
36739
37074
|
sendOwnInner: async (inner) => {
|
|
@@ -36763,8 +37098,8 @@ async function startHost(opts) {
|
|
|
36763
37098
|
var enc3 = new TextEncoder();
|
|
36764
37099
|
async function startPeer(opts) {
|
|
36765
37100
|
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`);
|
|
36766
|
-
await new Promise((
|
|
36767
|
-
ws.once("open", () =>
|
|
37101
|
+
await new Promise((resolve3, reject) => {
|
|
37102
|
+
ws.once("open", () => resolve3());
|
|
36768
37103
|
ws.once("error", reject);
|
|
36769
37104
|
});
|
|
36770
37105
|
const hello = await buildServerHello({
|
|
@@ -36794,8 +37129,8 @@ async function startPeer(opts) {
|
|
|
36794
37129
|
let pendingPairCode = null;
|
|
36795
37130
|
let resolveFirstReady;
|
|
36796
37131
|
let rejectFirstReady;
|
|
36797
|
-
const sessionPromise = new Promise((
|
|
36798
|
-
resolveFirstReady =
|
|
37132
|
+
const sessionPromise = new Promise((resolve3, reject) => {
|
|
37133
|
+
resolveFirstReady = resolve3;
|
|
36799
37134
|
rejectFirstReady = reject;
|
|
36800
37135
|
});
|
|
36801
37136
|
let extensionHello = null;
|
|
@@ -36996,6 +37331,17 @@ function classifyBridgeError(err) {
|
|
|
36996
37331
|
}
|
|
36997
37332
|
|
|
36998
37333
|
// node_modules/@fetchproxy/server/dist/ws-server.js
|
|
37334
|
+
function envWsPort() {
|
|
37335
|
+
const raw = process.env.FETCHPROXY_WS_PORT;
|
|
37336
|
+
if (raw === void 0 || raw.trim() === "")
|
|
37337
|
+
return void 0;
|
|
37338
|
+
if (!/^\d+$/.test(raw.trim()))
|
|
37339
|
+
return void 0;
|
|
37340
|
+
const port = Number(raw.trim());
|
|
37341
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
37342
|
+
return void 0;
|
|
37343
|
+
return port;
|
|
37344
|
+
}
|
|
36999
37345
|
var FetchproxyProtocolError = class extends Error {
|
|
37000
37346
|
constructor(message) {
|
|
37001
37347
|
super(message);
|
|
@@ -37243,7 +37589,7 @@ var FetchproxyServer = class {
|
|
|
37243
37589
|
}
|
|
37244
37590
|
}
|
|
37245
37591
|
this.opts = {
|
|
37246
|
-
port: opts.port ?? 37149,
|
|
37592
|
+
port: opts.port ?? envWsPort() ?? 37149,
|
|
37247
37593
|
host: opts.host ?? "127.0.0.1",
|
|
37248
37594
|
serverName: opts.serverName,
|
|
37249
37595
|
version: opts.version,
|
|
@@ -37673,8 +38019,8 @@ var FetchproxyServer = class {
|
|
|
37673
38019
|
async _fetchOnceWithTimeout(init) {
|
|
37674
38020
|
const id = this.nextRequestId++;
|
|
37675
38021
|
const inner = { type: "request", id, op: "fetch", init };
|
|
37676
|
-
const pending = new Promise((
|
|
37677
|
-
this.pending.set(id,
|
|
38022
|
+
const pending = new Promise((resolve3) => {
|
|
38023
|
+
this.pending.set(id, resolve3);
|
|
37678
38024
|
});
|
|
37679
38025
|
await this.sendInnerFrame(inner);
|
|
37680
38026
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
@@ -37685,12 +38031,12 @@ var FetchproxyServer = class {
|
|
|
37685
38031
|
try {
|
|
37686
38032
|
return await Promise.race([
|
|
37687
38033
|
pending,
|
|
37688
|
-
new Promise((
|
|
38034
|
+
new Promise((resolve3) => {
|
|
37689
38035
|
timer = setTimeout(() => {
|
|
37690
38036
|
this.pending.delete(id);
|
|
37691
38037
|
const elapsedMs = Date.now() - start;
|
|
37692
38038
|
const error51 = `fetchproxy: ${init.url} did not respond within ${timeoutMs}ms`;
|
|
37693
|
-
|
|
38039
|
+
resolve3({
|
|
37694
38040
|
ok: false,
|
|
37695
38041
|
error: error51,
|
|
37696
38042
|
kind: "timeout",
|
|
@@ -38052,8 +38398,8 @@ var FetchproxyServer = class {
|
|
|
38052
38398
|
const tabUrl = `https://${host}/`;
|
|
38053
38399
|
inner = { type: "request", id, op: "read_cookies", init: { tabUrl } };
|
|
38054
38400
|
}
|
|
38055
|
-
const pending = new Promise((
|
|
38056
|
-
this.pendingReadCookies.set(id,
|
|
38401
|
+
const pending = new Promise((resolve3) => {
|
|
38402
|
+
this.pendingReadCookies.set(id, resolve3);
|
|
38057
38403
|
});
|
|
38058
38404
|
await this.sendInnerFrame(inner);
|
|
38059
38405
|
const result = await this._withVerbTimeout(pending, this.pendingReadCookies, id, `https://${host}`);
|
|
@@ -38111,8 +38457,8 @@ var FetchproxyServer = class {
|
|
|
38111
38457
|
...cookiePath !== void 0 ? { path: cookiePath } : {}
|
|
38112
38458
|
}
|
|
38113
38459
|
};
|
|
38114
|
-
const pending = new Promise((
|
|
38115
|
-
this.pendingWriteCookies.set(id, { resolve:
|
|
38460
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38461
|
+
this.pendingWriteCookies.set(id, { resolve: resolve3, reject });
|
|
38116
38462
|
});
|
|
38117
38463
|
await this.sendInnerFrame(inner);
|
|
38118
38464
|
return this._withVerbTimeout(pending, this.pendingWriteCookies, id, `https://${host}`);
|
|
@@ -38174,8 +38520,8 @@ var FetchproxyServer = class {
|
|
|
38174
38520
|
...opts.pointers ? { pointers: { ...opts.pointers } } : {}
|
|
38175
38521
|
}
|
|
38176
38522
|
};
|
|
38177
|
-
const pending = new Promise((
|
|
38178
|
-
this.pendingStorage.set(id, { resolve:
|
|
38523
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38524
|
+
this.pendingStorage.set(id, { resolve: resolve3, reject });
|
|
38179
38525
|
});
|
|
38180
38526
|
await this.sendInnerFrame(inner);
|
|
38181
38527
|
return this._withVerbTimeout(pending, this.pendingStorage, id, `https://${host}`);
|
|
@@ -38279,8 +38625,8 @@ var FetchproxyServer = class {
|
|
|
38279
38625
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38280
38626
|
}
|
|
38281
38627
|
};
|
|
38282
|
-
const pending = new Promise((
|
|
38283
|
-
this.pendingCapture.set(id, { resolve:
|
|
38628
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38629
|
+
this.pendingCapture.set(id, { resolve: resolve3, reject });
|
|
38284
38630
|
});
|
|
38285
38631
|
await this.sendInnerFrame(inner);
|
|
38286
38632
|
return this._withVerbTimeout(pending, this.pendingCapture, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
@@ -38364,8 +38710,8 @@ var FetchproxyServer = class {
|
|
|
38364
38710
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38365
38711
|
}
|
|
38366
38712
|
};
|
|
38367
|
-
const pending = new Promise((
|
|
38368
|
-
this.pendingRedirect.set(id, { resolve:
|
|
38713
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38714
|
+
this.pendingRedirect.set(id, { resolve: resolve3, reject });
|
|
38369
38715
|
});
|
|
38370
38716
|
await this.sendInnerFrame(inner);
|
|
38371
38717
|
return this._withVerbTimeout(pending, this.pendingRedirect, id, `https://${opts.host}${opts.path ?? "/*"}`);
|
|
@@ -38446,8 +38792,8 @@ var FetchproxyServer = class {
|
|
|
38446
38792
|
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
38447
38793
|
}
|
|
38448
38794
|
};
|
|
38449
|
-
const pending = new Promise((
|
|
38450
|
-
this.pendingDownload.set(id, { resolve:
|
|
38795
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38796
|
+
this.pendingDownload.set(id, { resolve: resolve3, reject });
|
|
38451
38797
|
});
|
|
38452
38798
|
await this.sendInnerFrame(inner);
|
|
38453
38799
|
return this._withVerbTimeout(pending, this.pendingDownload, id, opts.url);
|
|
@@ -38494,8 +38840,8 @@ var FetchproxyServer = class {
|
|
|
38494
38840
|
keys: [...opts.keys]
|
|
38495
38841
|
}
|
|
38496
38842
|
};
|
|
38497
|
-
const pending = new Promise((
|
|
38498
|
-
this.pendingIdb.set(id, { resolve:
|
|
38843
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38844
|
+
this.pendingIdb.set(id, { resolve: resolve3, reject });
|
|
38499
38845
|
});
|
|
38500
38846
|
await this.sendInnerFrame(inner);
|
|
38501
38847
|
return this._withVerbTimeout(pending, this.pendingIdb, id, origin);
|
|
@@ -38534,8 +38880,8 @@ var FetchproxyServer = class {
|
|
|
38534
38880
|
op: "read_dom",
|
|
38535
38881
|
init: { origin, names: [...opts.names] }
|
|
38536
38882
|
};
|
|
38537
|
-
const pending = new Promise((
|
|
38538
|
-
this.pendingStorage.set(id, { resolve:
|
|
38883
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38884
|
+
this.pendingStorage.set(id, { resolve: resolve3, reject });
|
|
38539
38885
|
});
|
|
38540
38886
|
await this.sendInnerFrame(inner);
|
|
38541
38887
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
@@ -38580,8 +38926,8 @@ var FetchproxyServer = class {
|
|
|
38580
38926
|
...opts.tabUrl !== void 0 ? { tabUrl: opts.tabUrl } : {}
|
|
38581
38927
|
}
|
|
38582
38928
|
};
|
|
38583
|
-
const pending = new Promise((
|
|
38584
|
-
this.pendingGraphql.set(id, { resolve:
|
|
38929
|
+
const pending = new Promise((resolve3, reject) => {
|
|
38930
|
+
this.pendingGraphql.set(id, { resolve: resolve3, reject });
|
|
38585
38931
|
});
|
|
38586
38932
|
await this.sendInnerFrame(inner);
|
|
38587
38933
|
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name);
|
|
@@ -39074,7 +39420,7 @@ async function loginWithPassword(username, password) {
|
|
|
39074
39420
|
// package.json
|
|
39075
39421
|
var package_default = {
|
|
39076
39422
|
name: "ofw-mcp",
|
|
39077
|
-
version: "2.
|
|
39423
|
+
version: "2.12.0",
|
|
39078
39424
|
license: "MIT",
|
|
39079
39425
|
mcpName: "io.github.chrischall/ofw-mcp",
|
|
39080
39426
|
description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
|
|
@@ -39095,18 +39441,20 @@ var package_default = {
|
|
|
39095
39441
|
".claude-plugin",
|
|
39096
39442
|
"skills",
|
|
39097
39443
|
".mcp.json",
|
|
39098
|
-
"server.json"
|
|
39444
|
+
"server.json",
|
|
39445
|
+
"mint.yaml"
|
|
39099
39446
|
],
|
|
39100
39447
|
scripts: {
|
|
39101
39448
|
build: "tsc && npm run bundle",
|
|
39102
39449
|
bundle: `esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --banner:js='import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);' --outfile=dist/bundle.js`,
|
|
39103
39450
|
dev: "node --env-file=.env dist/index.js",
|
|
39104
|
-
test: "vitest run",
|
|
39105
|
-
"test:coverage": "vitest run --coverage",
|
|
39106
|
-
"test:watch": "vitest"
|
|
39451
|
+
test: "npm run typecheck && vitest run",
|
|
39452
|
+
"test:coverage": "npm run typecheck && vitest run --coverage",
|
|
39453
|
+
"test:watch": "vitest",
|
|
39454
|
+
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
39107
39455
|
},
|
|
39108
39456
|
dependencies: {
|
|
39109
|
-
"@chrischall/mcp-utils": "^0.
|
|
39457
|
+
"@chrischall/mcp-utils": "^0.18.0",
|
|
39110
39458
|
"@fetchproxy/bootstrap": "^2.0.0",
|
|
39111
39459
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39112
39460
|
dotenv: "^17.4.2",
|
|
@@ -39126,66 +39474,118 @@ function fetchproxyDisabled() {
|
|
|
39126
39474
|
return parseBoolEnv("OFW_DISABLE_FETCHPROXY");
|
|
39127
39475
|
}
|
|
39128
39476
|
async function resolveAuth() {
|
|
39477
|
+
const pattern = {};
|
|
39129
39478
|
const username = readEnvVar("OFW_USERNAME");
|
|
39130
39479
|
const password = readEnvVar("OFW_PASSWORD");
|
|
39131
39480
|
if (username && password) {
|
|
39132
|
-
|
|
39133
|
-
|
|
39481
|
+
pattern.sessionScrape = async () => {
|
|
39482
|
+
const { token, expiresAt: expiresAt2 } = await loginWithPassword(username, password);
|
|
39483
|
+
return { credential: token, source: "env", expiresAt: expiresAt2 };
|
|
39484
|
+
};
|
|
39134
39485
|
}
|
|
39135
39486
|
if (!fetchproxyDisabled()) {
|
|
39136
|
-
|
|
39137
|
-
|
|
39138
|
-
|
|
39139
|
-
|
|
39140
|
-
|
|
39141
|
-
|
|
39142
|
-
|
|
39143
|
-
|
|
39144
|
-
|
|
39145
|
-
|
|
39146
|
-
|
|
39147
|
-
|
|
39148
|
-
|
|
39149
|
-
|
|
39150
|
-
|
|
39151
|
-
|
|
39152
|
-
|
|
39487
|
+
pattern.fetchproxy = async () => {
|
|
39488
|
+
try {
|
|
39489
|
+
const session = await bootstrap({
|
|
39490
|
+
serverName: package_default.name,
|
|
39491
|
+
version: package_default.version,
|
|
39492
|
+
// OFW serves both ofw.ourfamilywizard.com and www.ourfamilywizard.com;
|
|
39493
|
+
// the API + auth token live on the apex. The extension matches on
|
|
39494
|
+
// suffix, so listing the apex covers both.
|
|
39495
|
+
domains: ["ourfamilywizard.com"],
|
|
39496
|
+
declare: {
|
|
39497
|
+
cookies: [],
|
|
39498
|
+
// The web app stores the Bearer token in localStorage["auth"] and
|
|
39499
|
+
// its expiry (ISO string) in localStorage["tokenExpiry"]. Mirroring
|
|
39500
|
+
// both means our 401-replay logic can be slightly smarter, and the
|
|
39501
|
+
// expiry surfaces correctly in diagnostics.
|
|
39502
|
+
localStorage: ["auth", "tokenExpiry"],
|
|
39503
|
+
sessionStorage: [],
|
|
39504
|
+
captureHeaders: []
|
|
39505
|
+
}
|
|
39506
|
+
});
|
|
39507
|
+
const token = session.localStorage["auth"];
|
|
39508
|
+
const expiryRaw = session.localStorage["tokenExpiry"];
|
|
39509
|
+
if (!token) {
|
|
39510
|
+
throw new Error(
|
|
39511
|
+
'localStorage["auth"] missing on ourfamilywizard.com. Sign into OFW in your browser (with the fetchproxy extension installed) and retry.'
|
|
39512
|
+
);
|
|
39153
39513
|
}
|
|
39154
|
-
|
|
39155
|
-
|
|
39156
|
-
|
|
39157
|
-
|
|
39158
|
-
|
|
39159
|
-
|
|
39160
|
-
)
|
|
39161
|
-
|
|
39162
|
-
|
|
39163
|
-
|
|
39164
|
-
|
|
39165
|
-
|
|
39166
|
-
|
|
39167
|
-
} catch (e) {
|
|
39168
|
-
if (classifyBridgeError(e) === "bridge_down") {
|
|
39169
|
-
const downErr = e;
|
|
39514
|
+
return {
|
|
39515
|
+
credential: token,
|
|
39516
|
+
source: "fetchproxy",
|
|
39517
|
+
...expiryRaw ? { expiresAt: new Date(expiryRaw) } : {}
|
|
39518
|
+
};
|
|
39519
|
+
} catch (e) {
|
|
39520
|
+
if (classifyBridgeError(e) === "bridge_down") {
|
|
39521
|
+
const downErr = e;
|
|
39522
|
+
throw new Error(
|
|
39523
|
+
`OFW auth: fetchproxy bridge is down (extension service worker unreachable after retry). ${downErr.hint}`
|
|
39524
|
+
);
|
|
39525
|
+
}
|
|
39526
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
39170
39527
|
throw new Error(
|
|
39171
|
-
`OFW auth:
|
|
39528
|
+
`OFW auth: no OFW_USERNAME/OFW_PASSWORD set, and fetchproxy fallback failed: ${msg}`
|
|
39172
39529
|
);
|
|
39173
39530
|
}
|
|
39174
|
-
|
|
39175
|
-
throw new Error(
|
|
39176
|
-
`OFW auth: no OFW_USERNAME/OFW_PASSWORD set, and fetchproxy fallback failed: ${msg}`
|
|
39177
|
-
);
|
|
39178
|
-
}
|
|
39531
|
+
};
|
|
39179
39532
|
}
|
|
39180
|
-
|
|
39181
|
-
|
|
39533
|
+
if (!pattern.sessionScrape && !pattern.fetchproxy) {
|
|
39534
|
+
throw new Error(
|
|
39535
|
+
"OFW auth: set OFW_USERNAME + OFW_PASSWORD, or install the fetchproxy extension and sign into ourfamilywizard.com (unset OFW_DISABLE_FETCHPROXY if it is set)."
|
|
39536
|
+
);
|
|
39537
|
+
}
|
|
39538
|
+
const { credential, source, expiresAt } = await resolveAuthPattern(pattern);
|
|
39539
|
+
return {
|
|
39540
|
+
token: credential,
|
|
39541
|
+
// The pattern's `source` is a free-form string; ours is a two-value union,
|
|
39542
|
+
// and both resolvers above set it to a member of that union.
|
|
39543
|
+
source,
|
|
39544
|
+
...expiresAt ? { expiresAt } : {}
|
|
39545
|
+
};
|
|
39546
|
+
}
|
|
39547
|
+
|
|
39548
|
+
// src/session-cache.ts
|
|
39549
|
+
function sessionCachePath(env = process.env) {
|
|
39550
|
+
return resolveStateFile({
|
|
39551
|
+
env,
|
|
39552
|
+
envVar: "OFW_SESSION_FILE",
|
|
39553
|
+
subdir: ".ofw-mcp",
|
|
39554
|
+
fileName: "session.json"
|
|
39555
|
+
});
|
|
39556
|
+
}
|
|
39557
|
+
function isTokens(raw) {
|
|
39558
|
+
if (raw === null || typeof raw !== "object") return false;
|
|
39559
|
+
const t = raw;
|
|
39560
|
+
return typeof t.accessToken === "string" && t.accessToken !== "" && typeof t.expiresAt === "number" && (t.refreshToken === void 0 || typeof t.refreshToken === "string");
|
|
39561
|
+
}
|
|
39562
|
+
function createSessionCache(opts = {}) {
|
|
39563
|
+
const env = opts.env ?? process.env;
|
|
39564
|
+
if (opts.injectedResolver === true) return null;
|
|
39565
|
+
if (!parseBoolEnv("OFW_SESSION_CACHE", { env, default: true })) return null;
|
|
39566
|
+
const username = readEnvVar("OFW_USERNAME", { env });
|
|
39567
|
+
const password = readEnvVar("OFW_PASSWORD", { env });
|
|
39568
|
+
if (username === void 0 || password === void 0) return null;
|
|
39569
|
+
return createFileStatePersistence({
|
|
39570
|
+
filePath: sessionCachePath(env),
|
|
39571
|
+
// Joined on a NUL, written as an escape rather than a literal byte: a
|
|
39572
|
+
// password may contain spaces, so a space-joined pair could collide with
|
|
39573
|
+
// a different pair by shifting the boundary between the two halves.
|
|
39574
|
+
boundTo: [username.trim().toLowerCase(), password].join("\0"),
|
|
39575
|
+
validate: (raw) => isTokens(raw) ? raw : null
|
|
39576
|
+
});
|
|
39577
|
+
}
|
|
39578
|
+
function reportCacheWriteFailure(err) {
|
|
39579
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
39580
|
+
console.error(
|
|
39581
|
+
`[ofw-mcp] could not cache the session token (${detail}); continuing without the cache \u2014 every restart will re-authenticate until this is fixed.`
|
|
39182
39582
|
);
|
|
39183
39583
|
}
|
|
39184
39584
|
|
|
39185
39585
|
// src/client.ts
|
|
39186
39586
|
try {
|
|
39187
|
-
const dir =
|
|
39188
|
-
await loadDotenvSafely({ path:
|
|
39587
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
39588
|
+
await loadDotenvSafely({ path: join5(dir, "..", ".env") });
|
|
39189
39589
|
} catch {
|
|
39190
39590
|
}
|
|
39191
39591
|
function parseContentDispositionFilename(cd) {
|
|
@@ -39216,9 +39616,9 @@ var OFWClient = class {
|
|
|
39216
39616
|
// Bearer-token lifecycle is delegated to the shared, race-safe TokenManager
|
|
39217
39617
|
// (proactive refresh inside the skew window, single-flight refresh so a burst
|
|
39218
39618
|
// of concurrent callers coalesces onto ONE `resolveAuth()`, and a 401-replay
|
|
39219
|
-
// guarded against double-refresh). It is created lazily,
|
|
39220
|
-
//
|
|
39221
|
-
//
|
|
39619
|
+
// guarded against double-refresh). It is created lazily, and mints on first
|
|
39620
|
+
// use through the function form of `initial` — see `mint` below for why that
|
|
39621
|
+
// form rather than a seeded placeholder.
|
|
39222
39622
|
tokenManager;
|
|
39223
39623
|
// Optional injected auth resolver. When set, the refresh callback uses it
|
|
39224
39624
|
// instead of the module-level global `resolveAuth` (env-var → fetchproxy
|
|
@@ -39232,22 +39632,28 @@ var OFWClient = class {
|
|
|
39232
39632
|
}
|
|
39233
39633
|
getTokenManager() {
|
|
39234
39634
|
if (!this.tokenManager) {
|
|
39635
|
+
const mint = async () => {
|
|
39636
|
+
const { token, expiresAt } = await (this.authResolver ?? resolveAuth)();
|
|
39637
|
+
return {
|
|
39638
|
+
accessToken: token,
|
|
39639
|
+
refreshToken: OFW_REFRESH_SENTINEL,
|
|
39640
|
+
expiresAt: (expiresAt ?? new Date(Date.now() + OFW_TOKEN_TTL_MS)).getTime()
|
|
39641
|
+
};
|
|
39642
|
+
};
|
|
39235
39643
|
this.tokenManager = new TokenManager({
|
|
39236
|
-
initial:
|
|
39644
|
+
initial: mint,
|
|
39645
|
+
persistence: createSessionCache({ injectedResolver: this.authResolver !== void 0 }) ?? void 0,
|
|
39646
|
+
onPersistError: reportCacheWriteFailure,
|
|
39647
|
+
// A failed renewal IS a failed login here, so the library's
|
|
39648
|
+
// re-mint-on-revoked recovery would just repeat the call that failed.
|
|
39649
|
+
isRefreshRevoked: () => false,
|
|
39237
39650
|
skewMs: OFW_TOKEN_EXPIRY_SKEW_MS,
|
|
39238
39651
|
// Map OFW's mint/refresh onto the refresh callback. `resolveAuth()`
|
|
39239
39652
|
// returns a token and a best-effort expiry; when the fetchproxy path
|
|
39240
39653
|
// can't supply one we fall back to the same 6h estimate the password
|
|
39241
39654
|
// path uses (the 401-replay covers a wrong guess). We re-arm the
|
|
39242
39655
|
// sentinel so the manager can refresh again later.
|
|
39243
|
-
refresh:
|
|
39244
|
-
const { token, expiresAt } = await (this.authResolver ?? resolveAuth)();
|
|
39245
|
-
return {
|
|
39246
|
-
accessToken: token,
|
|
39247
|
-
refreshToken: OFW_REFRESH_SENTINEL,
|
|
39248
|
-
expiresAt: (expiresAt ?? new Date(Date.now() + OFW_TOKEN_TTL_MS)).getTime()
|
|
39249
|
-
};
|
|
39250
|
-
}
|
|
39656
|
+
refresh: mint
|
|
39251
39657
|
});
|
|
39252
39658
|
}
|
|
39253
39659
|
return this.tokenManager;
|
|
@@ -40071,25 +40477,25 @@ async function syncAll(client2, opts, store) {
|
|
|
40071
40477
|
|
|
40072
40478
|
// src/config.ts
|
|
40073
40479
|
import { createHash } from "node:crypto";
|
|
40074
|
-
import { homedir as
|
|
40075
|
-
import { join as
|
|
40480
|
+
import { homedir as homedir4 } from "node:os";
|
|
40481
|
+
import { join as join6 } from "node:path";
|
|
40076
40482
|
function readCacheIdentity() {
|
|
40077
40483
|
return readEnvVar("OFW_CACHE_IDENTITY") ?? readEnvVar("OFW_USERNAME") ?? "_default";
|
|
40078
40484
|
}
|
|
40079
40485
|
function getCacheDir() {
|
|
40080
40486
|
const override = process.env.OFW_CACHE_DIR;
|
|
40081
40487
|
if (override && override.trim().length > 0) return override.trim();
|
|
40082
|
-
return
|
|
40488
|
+
return join6(homedir4(), ".cache", "ofw-mcp");
|
|
40083
40489
|
}
|
|
40084
40490
|
function getCacheDbPath() {
|
|
40085
40491
|
const identity = readCacheIdentity();
|
|
40086
40492
|
const hash2 = createHash("sha256").update(identity).digest("hex").slice(0, 16);
|
|
40087
|
-
return
|
|
40493
|
+
return join6(getCacheDir(), `${hash2}.db`);
|
|
40088
40494
|
}
|
|
40089
40495
|
function getAttachmentsDir() {
|
|
40090
40496
|
const override = process.env.OFW_ATTACHMENTS_DIR;
|
|
40091
40497
|
if (override && override.trim().length > 0) return override.trim();
|
|
40092
|
-
return
|
|
40498
|
+
return join6(homedir4(), "Downloads", "ofw-mcp");
|
|
40093
40499
|
}
|
|
40094
40500
|
function getWriteMode() {
|
|
40095
40501
|
const raw = process.env.OFW_WRITE_MODE;
|
|
@@ -41459,8 +41865,8 @@ async function extractAttachment(bytes, mimeType, fileName, opts = {}) {
|
|
|
41459
41865
|
}
|
|
41460
41866
|
|
|
41461
41867
|
// src/tools/attachments.ts
|
|
41462
|
-
import { readFileSync, statSync, mkdirSync, writeFileSync } from "node:fs";
|
|
41463
|
-
import { basename, dirname as
|
|
41868
|
+
import { readFileSync as readFileSync2, statSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
41869
|
+
import { basename, dirname as dirname3, extname } from "node:path";
|
|
41464
41870
|
var MIME_BY_EXT = {
|
|
41465
41871
|
".pdf": "application/pdf",
|
|
41466
41872
|
".png": "image/png",
|
|
@@ -41534,14 +41940,14 @@ var NodeAttachmentIO = class {
|
|
|
41534
41940
|
}
|
|
41535
41941
|
readDownloaded(path) {
|
|
41536
41942
|
try {
|
|
41537
|
-
return
|
|
41943
|
+
return readFileSync2(path);
|
|
41538
41944
|
} catch {
|
|
41539
41945
|
return null;
|
|
41540
41946
|
}
|
|
41541
41947
|
}
|
|
41542
41948
|
writeDownload(dest, bytes) {
|
|
41543
|
-
|
|
41544
|
-
|
|
41949
|
+
mkdirSync2(dirname3(dest), { recursive: true });
|
|
41950
|
+
writeFileSync2(dest, bytes);
|
|
41545
41951
|
}
|
|
41546
41952
|
};
|
|
41547
41953
|
|
|
@@ -41605,7 +42011,54 @@ async function buildInlineDelivery(input) {
|
|
|
41605
42011
|
}
|
|
41606
42012
|
|
|
41607
42013
|
// src/tools/messages.ts
|
|
41608
|
-
import { basename as basename2, join as
|
|
42014
|
+
import { basename as basename2, join as join7 } from "node:path";
|
|
42015
|
+
|
|
42016
|
+
// src/tools/pagination.ts
|
|
42017
|
+
function pageState(input) {
|
|
42018
|
+
const hasMore = input.page * input.size < input.total;
|
|
42019
|
+
return { hasMore, nextPage: hasMore ? input.page + 1 : null };
|
|
42020
|
+
}
|
|
42021
|
+
function asRecord(value) {
|
|
42022
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
42023
|
+
}
|
|
42024
|
+
function recordArray(root) {
|
|
42025
|
+
if (Array.isArray(root.data)) return root.data;
|
|
42026
|
+
for (const value of Object.values(root)) {
|
|
42027
|
+
if (Array.isArray(value)) return value;
|
|
42028
|
+
}
|
|
42029
|
+
return null;
|
|
42030
|
+
}
|
|
42031
|
+
function readUpstreamPaging(payload) {
|
|
42032
|
+
const root = asRecord(payload);
|
|
42033
|
+
const rows = root === null ? null : recordArray(root);
|
|
42034
|
+
const meta3 = root === null ? null : asRecord(root.metadata);
|
|
42035
|
+
const total = meta3 !== null && typeof meta3.totalElements === "number" && Number.isFinite(meta3.totalElements) ? meta3.totalElements : null;
|
|
42036
|
+
const last = meta3 !== null && typeof meta3.last === "boolean" ? meta3.last : null;
|
|
42037
|
+
return { returned: rows?.length ?? 0, total, last };
|
|
42038
|
+
}
|
|
42039
|
+
function offsetState(input) {
|
|
42040
|
+
const last = input.last ?? null;
|
|
42041
|
+
const consumed = input.start - input.base + input.returned;
|
|
42042
|
+
const hasMore = last !== null ? !last : input.total !== null ? consumed < input.total : input.returned >= input.max;
|
|
42043
|
+
return { hasMore, nextStart: hasMore ? input.start + input.max : null };
|
|
42044
|
+
}
|
|
42045
|
+
function withPaginationFirst(input) {
|
|
42046
|
+
const body = asRecord(input.payload);
|
|
42047
|
+
if (body === null) return null;
|
|
42048
|
+
const scope = input.total !== null ? ` of ${input.total}` : "";
|
|
42049
|
+
const head = {
|
|
42050
|
+
hasMore: input.state.hasMore,
|
|
42051
|
+
nextStart: input.state.nextStart,
|
|
42052
|
+
start: input.start,
|
|
42053
|
+
max: input.max,
|
|
42054
|
+
returned: input.returned,
|
|
42055
|
+
...input.total !== null ? { total: input.total } : {},
|
|
42056
|
+
paginationNote: input.state.hasMore ? `PARTIAL: this response holds ${input.returned} record(s) starting at ${input.start}${scope}. ${input.hint} Do not state a total or an absence from this response alone.` : `This response reaches the end of the list${scope === "" ? "" : ` (${input.total} record(s) in total)`}.`
|
|
42057
|
+
};
|
|
42058
|
+
return { ...head, ...body, ...head };
|
|
42059
|
+
}
|
|
42060
|
+
|
|
42061
|
+
// src/tools/messages.ts
|
|
41609
42062
|
var DateSchema = external_exports.looseObject({ dateTime: external_exports.string() });
|
|
41610
42063
|
var SentDetailSchema = external_exports.looseObject({
|
|
41611
42064
|
subject: external_exports.string().optional(),
|
|
@@ -41741,7 +42194,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
|
|
|
41741
42194
|
return jsonResponse({ folders: data, freshness });
|
|
41742
42195
|
});
|
|
41743
42196
|
server.registerTool("ofw_list_messages", {
|
|
41744
|
-
description: 'List messages from the local OurFamilyWizard cache. Supports filtering by folder, date range, and a substring query on subject+body. Pagination is offset-based but if you know what you want (a date range, a topic), prefer the filters over walking pages \u2014 the cache may have 1000+ messages. Returns an explicit `complete` boolean describing the RESULT SET: true means "this is every message on OurFamilyWizard matching these filters as of freshness.asOf" \u2014 check it before asserting a count. An empty result from a cache that is not verified-fresh is REFUSED (result:"UNVERIFIED_EMPTY") rather than reported as an absence; pass autoRefresh:true to sync and answer instead.',
|
|
42197
|
+
description: 'List messages from the local OurFamilyWizard cache. Supports filtering by folder, date range, and a substring query on subject+body. Pagination is offset-based (1-based `page`) but if you know what you want (a date range, a topic), prefer the filters over walking pages \u2014 the cache may have 1000+ messages. Results are newest-first by default; `sort:"oldest"` starts at the old end of a range instead of paging to it. Returns an explicit `complete` boolean describing the RESULT SET: true means "this is every message on OurFamilyWizard matching these filters as of freshness.asOf" \u2014 check it before asserting a count. An empty result from a cache that is not verified-fresh is REFUSED (result:"UNVERIFIED_EMPTY") rather than reported as an absence; pass autoRefresh:true to sync and answer instead.',
|
|
41745
42198
|
annotations: { readOnlyHint: false },
|
|
41746
42199
|
inputSchema: {
|
|
41747
42200
|
folderId: external_exports.string().describe('Folder name: "inbox", "sent", or "both" (default "both")').optional(),
|
|
@@ -41750,11 +42203,13 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
|
|
|
41750
42203
|
since: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at >= since (inclusive)").optional(),
|
|
41751
42204
|
until: external_exports.string().describe("ISO date or datetime \u2014 only messages with sent_at < until (exclusive)").optional(),
|
|
41752
42205
|
q: external_exports.string().describe("Substring match on subject AND body (case-insensitive). Use to find messages on a specific topic.").optional(),
|
|
42206
|
+
sort: external_exports.enum(["newest", "oldest"]).describe('Result order: "newest" (default, newest first) or "oldest" (oldest first). This decides which end a truncated page keeps \u2014 with "newest" page 1 of a wide date range holds its most RECENT slice, with "oldest" its earliest. Use "oldest" to start at the old end of a range instead of paging to it.').optional(),
|
|
41753
42207
|
autoRefresh: external_exports.boolean().describe(AUTO_REFRESH_DESC).optional()
|
|
41754
42208
|
}
|
|
41755
42209
|
}, async (args) => {
|
|
41756
42210
|
const page = args.page ?? 1;
|
|
41757
42211
|
const size = args.size ?? 50;
|
|
42212
|
+
const sort = args.sort ?? "newest";
|
|
41758
42213
|
const folderArg = args.folderId ?? "both";
|
|
41759
42214
|
let folder;
|
|
41760
42215
|
if (folderArg === "inbox") folder = "inbox";
|
|
@@ -41780,7 +42235,7 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
|
|
|
41780
42235
|
isEmpty: (v) => v.total === 0,
|
|
41781
42236
|
read: async () => {
|
|
41782
42237
|
const total2 = await cache.countMessages(filter);
|
|
41783
|
-
const messages2 = (await cache.listMessages({ ...filter, page, size })).map((m) => withReadState(m));
|
|
42238
|
+
const messages2 = (await cache.listMessages({ ...filter, page, size, sort })).map((m) => withReadState(m));
|
|
41784
42239
|
const freshness2 = await buildFreshness(cache, { source: "cache", folders });
|
|
41785
42240
|
return { messages: messages2, total: total2, freshness: freshness2 };
|
|
41786
42241
|
}
|
|
@@ -41797,7 +42252,19 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
|
|
|
41797
42252
|
const { messages, total, freshness } = value;
|
|
41798
42253
|
const fullSlice = page === 1 && messages.length === total;
|
|
41799
42254
|
const complete = fullSlice && freshness.staleness === "fresh" && freshness.historyComplete;
|
|
41800
|
-
const
|
|
42255
|
+
const { hasMore, nextPage } = pageState({ page, size, total });
|
|
42256
|
+
const payload = {
|
|
42257
|
+
complete,
|
|
42258
|
+
hasMore,
|
|
42259
|
+
nextPage,
|
|
42260
|
+
// The honest record count, as a scalar and ahead of the array — a
|
|
42261
|
+
// consumer never has to reach `messages` to learn how many came back.
|
|
42262
|
+
returned: messages.length,
|
|
42263
|
+
total,
|
|
42264
|
+
page,
|
|
42265
|
+
size,
|
|
42266
|
+
sort
|
|
42267
|
+
};
|
|
41801
42268
|
if (!complete) {
|
|
41802
42269
|
payload.completeNote = [
|
|
41803
42270
|
!fullSlice ? `this page holds ${messages.length} of ${total} matching cached messages` : null,
|
|
@@ -41808,11 +42275,13 @@ function registerMessageTools(server, client2, cacheProvider, attachmentIO) {
|
|
|
41808
42275
|
if (total === 0) {
|
|
41809
42276
|
payload.note = 'No messages match these filters, and the cache IS verified-fresh for these folders \u2014 so this is a real "nothing matched", not a stale-cache artefact. If you expected results, relax the filters.';
|
|
41810
42277
|
} else if (page * size < total) {
|
|
41811
|
-
payload.note = `Showing ${(page - 1) * size + 1}\u2013${(page - 1) * size + messages.length} of ${total}. Increase 'page' to see more,
|
|
42278
|
+
payload.note = `Showing ${(page - 1) * size + 1}\u2013${(page - 1) * size + messages.length} of ${total}, ${sort} first. Increase 'page' to see more, narrow with since/until/q, or set sort:"${sort === "newest" ? "oldest" : "newest"}" to start from the other end.`;
|
|
41812
42279
|
}
|
|
41813
42280
|
if (refreshed) {
|
|
41814
42281
|
payload.autoRefreshed = true;
|
|
41815
42282
|
}
|
|
42283
|
+
payload.freshness = freshness;
|
|
42284
|
+
payload.messages = messages;
|
|
41816
42285
|
return jsonResponse(payload);
|
|
41817
42286
|
});
|
|
41818
42287
|
server.registerTool("ofw_get_message", {
|
|
@@ -42259,7 +42728,16 @@ ${JSON.stringify(
|
|
|
42259
42728
|
const { drafts, total, freshness, serverConfirmed } = value;
|
|
42260
42729
|
const fullSlice = page === 1 && drafts.length === total;
|
|
42261
42730
|
const complete = serverConfirmed && fullSlice;
|
|
42262
|
-
const
|
|
42731
|
+
const { hasMore, nextPage } = pageState({ page, size, total });
|
|
42732
|
+
const payload = {
|
|
42733
|
+
complete,
|
|
42734
|
+
hasMore,
|
|
42735
|
+
nextPage,
|
|
42736
|
+
returned: drafts.length,
|
|
42737
|
+
total,
|
|
42738
|
+
page,
|
|
42739
|
+
size
|
|
42740
|
+
};
|
|
42263
42741
|
if (!complete) {
|
|
42264
42742
|
payload.completeNote = [
|
|
42265
42743
|
!fullSlice ? `this page holds ${drafts.length} of ${total} cached drafts` : null,
|
|
@@ -42278,6 +42756,8 @@ ${JSON.stringify(
|
|
|
42278
42756
|
if (verifyNote !== null) {
|
|
42279
42757
|
payload.verifyNote = verifyNote;
|
|
42280
42758
|
}
|
|
42759
|
+
payload.freshness = freshness;
|
|
42760
|
+
payload.drafts = drafts;
|
|
42281
42761
|
return jsonResponse(payload);
|
|
42282
42762
|
});
|
|
42283
42763
|
if (allowDrafts) server.registerTool("ofw_save_draft", {
|
|
@@ -42509,7 +42989,16 @@ ${text}` : text);
|
|
|
42509
42989
|
unread.push({ id: msg.id, subject: msg.subject, sentAt: msg.sentAt, unreadBy });
|
|
42510
42990
|
}
|
|
42511
42991
|
}
|
|
42512
|
-
const
|
|
42992
|
+
const { hasMore, nextPage } = pageState({ page, size, total });
|
|
42993
|
+
const payload = {
|
|
42994
|
+
complete,
|
|
42995
|
+
hasMore,
|
|
42996
|
+
nextPage,
|
|
42997
|
+
total,
|
|
42998
|
+
scanned: sent.length,
|
|
42999
|
+
page,
|
|
43000
|
+
size
|
|
43001
|
+
};
|
|
42513
43002
|
if (!complete) {
|
|
42514
43003
|
payload.completeNote = `This verdict covers the ${sent.length} of ${total} cached sent messages on this page${freshness.staleness === "fresh" ? "" : `, from a cache that is "${freshness.staleness}"`}. It is not a statement about every message you have sent.`;
|
|
42515
43004
|
}
|
|
@@ -42519,6 +43008,8 @@ ${text}` : text);
|
|
|
42519
43008
|
if (refreshed) {
|
|
42520
43009
|
payload.autoRefreshed = true;
|
|
42521
43010
|
}
|
|
43011
|
+
payload.freshness = freshness;
|
|
43012
|
+
payload.unread = unread;
|
|
42522
43013
|
return jsonResponse(payload);
|
|
42523
43014
|
});
|
|
42524
43015
|
if (allowDrafts) server.registerTool("ofw_upload_attachment", {
|
|
@@ -42615,9 +43106,9 @@ ${text}` : text);
|
|
|
42615
43106
|
if (args.saveTo) {
|
|
42616
43107
|
const isDirArg = args.saveTo.endsWith("/") || args.saveTo.endsWith("\\");
|
|
42617
43108
|
const abs = expandPath2(args.saveTo);
|
|
42618
|
-
dest = isDirArg ?
|
|
43109
|
+
dest = isDirArg ? join7(abs, `${fileId}-${safeName}`) : abs;
|
|
42619
43110
|
} else {
|
|
42620
|
-
dest =
|
|
43111
|
+
dest = join7(getAttachmentsDir(), `${fileId}-${safeName}`);
|
|
42621
43112
|
}
|
|
42622
43113
|
const extractOnDisk = args.extract === true;
|
|
42623
43114
|
if (!args.force && cached2.downloadedPath === dest) {
|
|
@@ -43037,17 +43528,27 @@ function registerExpenseTools(server, client2) {
|
|
|
43037
43528
|
return jsonResponse(data);
|
|
43038
43529
|
});
|
|
43039
43530
|
server.registerTool("ofw_list_expenses", {
|
|
43040
|
-
description: "List OurFamilyWizard expenses with
|
|
43531
|
+
description: "List OurFamilyWizard expenses. Offset-paged via start/max. The response leads with its paging state \u2014 `hasMore` and `nextStart` (null when the list is exhausted) \u2014 BEFORE the records, so a truncated or partially-read response still says whether more remain. Never state an expense total or an absence from one page.",
|
|
43041
43532
|
annotations: { readOnlyHint: true },
|
|
43042
43533
|
inputSchema: {
|
|
43043
|
-
start: external_exports.number().int().min(0).describe("Start offset (default 0)").optional(),
|
|
43534
|
+
start: external_exports.number().int().min(0).describe("Start offset, 0-based (default 0). To continue a listing, pass the `nextStart` from the previous response.").optional(),
|
|
43044
43535
|
max: external_exports.number().int().min(1).describe("Max results (default 20)").optional()
|
|
43045
43536
|
}
|
|
43046
43537
|
}, async (args) => {
|
|
43047
43538
|
const start = args.start ?? 0;
|
|
43048
43539
|
const max = args.max ?? 20;
|
|
43049
43540
|
const data = await client2.request("GET", `/pub/v2/expense/expenses?start=${start}&max=${max}`);
|
|
43050
|
-
|
|
43541
|
+
const { returned, total, last } = readUpstreamPaging(data);
|
|
43542
|
+
const wrapped = withPaginationFirst({
|
|
43543
|
+
state: offsetState({ start, max, returned, total, last, base: 0 }),
|
|
43544
|
+
start,
|
|
43545
|
+
max,
|
|
43546
|
+
returned,
|
|
43547
|
+
total,
|
|
43548
|
+
hint: `Re-call ofw_list_expenses with start:${start + max}.`,
|
|
43549
|
+
payload: data
|
|
43550
|
+
});
|
|
43551
|
+
return jsonResponse(wrapped ?? data);
|
|
43051
43552
|
});
|
|
43052
43553
|
if (allowWrites) server.registerTool("ofw_create_expense", {
|
|
43053
43554
|
description: "Log a new expense in OurFamilyWizard",
|
|
@@ -43066,17 +43567,27 @@ function registerExpenseTools(server, client2) {
|
|
|
43066
43567
|
function registerJournalTools(server, client2) {
|
|
43067
43568
|
const allowWrites = getWriteMode() === "all";
|
|
43068
43569
|
server.registerTool("ofw_list_journal_entries", {
|
|
43069
|
-
description: "List OurFamilyWizard journal entries",
|
|
43570
|
+
description: "List OurFamilyWizard journal entries. Offset-paged via start/max (1-based). The response leads with its paging state \u2014 `hasMore` and `nextStart` (null when the list is exhausted) \u2014 BEFORE the records, so a truncated or partially-read response still says whether more remain. Never state an entry count or an absence from one page.",
|
|
43070
43571
|
annotations: { readOnlyHint: true },
|
|
43071
43572
|
inputSchema: {
|
|
43072
|
-
start: external_exports.number().int().min(1).describe("Start offset (default 1)").optional(),
|
|
43573
|
+
start: external_exports.number().int().min(1).describe("Start offset, 1-based (default 1). To continue a listing, pass the `nextStart` from the previous response.").optional(),
|
|
43073
43574
|
max: external_exports.number().int().min(1).describe("Max results (default 10)").optional()
|
|
43074
43575
|
}
|
|
43075
43576
|
}, async (args) => {
|
|
43076
43577
|
const start = args.start ?? 1;
|
|
43077
43578
|
const max = args.max ?? 10;
|
|
43078
43579
|
const data = await client2.request("GET", `/pub/v1/journals?start=${start}&max=${max}`);
|
|
43079
|
-
|
|
43580
|
+
const { returned, total, last } = readUpstreamPaging(data);
|
|
43581
|
+
const wrapped = withPaginationFirst({
|
|
43582
|
+
state: offsetState({ start, max, returned, total, last, base: 1 }),
|
|
43583
|
+
start,
|
|
43584
|
+
max,
|
|
43585
|
+
returned,
|
|
43586
|
+
total,
|
|
43587
|
+
hint: `Re-call ofw_list_journal_entries with start:${start + max}.`,
|
|
43588
|
+
payload: data
|
|
43589
|
+
});
|
|
43590
|
+
return jsonResponse(wrapped ?? data);
|
|
43080
43591
|
});
|
|
43081
43592
|
if (allowWrites) server.registerTool("ofw_create_journal_entry", {
|
|
43082
43593
|
description: "Create a new journal entry in OurFamilyWizard",
|
|
@@ -43093,8 +43604,8 @@ function registerJournalTools(server, client2) {
|
|
|
43093
43604
|
|
|
43094
43605
|
// src/cache/node.ts
|
|
43095
43606
|
import { DatabaseSync } from "node:sqlite";
|
|
43096
|
-
import { mkdirSync as
|
|
43097
|
-
import { dirname as
|
|
43607
|
+
import { mkdirSync as mkdirSync3, chmodSync as chmodSync2, existsSync as existsSync2 } from "node:fs";
|
|
43608
|
+
import { dirname as dirname4 } from "node:path";
|
|
43098
43609
|
|
|
43099
43610
|
// src/cache/store.ts
|
|
43100
43611
|
function rowFromDb(r) {
|
|
@@ -43332,9 +43843,10 @@ var OFWCacheCore = class {
|
|
|
43332
43843
|
listMessages(opts) {
|
|
43333
43844
|
const { where, params } = buildMessageFilter(opts);
|
|
43334
43845
|
const offset = (opts.page - 1) * opts.size;
|
|
43846
|
+
const dir = opts.sort === "oldest" ? "ASC" : "DESC";
|
|
43335
43847
|
const rows = this.db.all(
|
|
43336
43848
|
`SELECT * FROM messages ${where}
|
|
43337
|
-
ORDER BY sent_at
|
|
43849
|
+
ORDER BY sent_at ${dir}, id ${dir}
|
|
43338
43850
|
LIMIT ? OFFSET ?`,
|
|
43339
43851
|
[...params, opts.size, offset]
|
|
43340
43852
|
);
|
|
@@ -43677,10 +44189,10 @@ var NodeSqlDriver = class {
|
|
|
43677
44189
|
}
|
|
43678
44190
|
};
|
|
43679
44191
|
function enforceCachePermissions(dbPath) {
|
|
43680
|
-
|
|
43681
|
-
|
|
44192
|
+
chmodSync2(dirname4(dbPath), 448);
|
|
44193
|
+
chmodSync2(dbPath, 384);
|
|
43682
44194
|
for (const sibling of [`${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
43683
|
-
if (
|
|
44195
|
+
if (existsSync2(sibling)) chmodSync2(sibling, 384);
|
|
43684
44196
|
}
|
|
43685
44197
|
}
|
|
43686
44198
|
var OFWCache = class _OFWCache extends LocalCacheStore {
|
|
@@ -43691,7 +44203,7 @@ var OFWCache = class _OFWCache extends LocalCacheStore {
|
|
|
43691
44203
|
db;
|
|
43692
44204
|
static open(path) {
|
|
43693
44205
|
const memory = path === ":memory:";
|
|
43694
|
-
if (!memory)
|
|
44206
|
+
if (!memory) mkdirSync3(dirname4(path), { recursive: true });
|
|
43695
44207
|
const db = new DatabaseSync(path);
|
|
43696
44208
|
if (!memory) enforceCachePermissions(path);
|
|
43697
44209
|
db.exec("PRAGMA journal_mode = WAL");
|
|
@@ -43721,7 +44233,7 @@ var nodeCacheProvider = () => nodeCache ??= OFWCache.open(getCacheDbPath());
|
|
|
43721
44233
|
var nodeAttachmentIO = new NodeAttachmentIO();
|
|
43722
44234
|
await runMcp({
|
|
43723
44235
|
name: "ofw",
|
|
43724
|
-
version: "2.
|
|
44236
|
+
version: "2.12.0",
|
|
43725
44237
|
// x-release-please-version
|
|
43726
44238
|
deps: client,
|
|
43727
44239
|
tools: [
|