splitwise-mcp 3.4.0 → 3.4.1
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/.mcp.json +3 -2
- package/dist/bundle.js +244 -37
- package/dist/index.js +1 -1
- package/dist/tools/receipts.js +3 -20
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "MCP server for Splitwise — natural-language expense and group management via the Splitwise API",
|
|
10
|
-
"version": "3.4.
|
|
10
|
+
"version": "3.4.1"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "Splitwise",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "MCP server for Splitwise — manage expenses, groups, and friends via natural language",
|
|
18
|
-
"version": "3.4.
|
|
18
|
+
"version": "3.4.1",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/.mcp.json
CHANGED
package/dist/bundle.js
CHANGED
|
@@ -92,6 +92,7 @@ var require_util = __commonJS({
|
|
|
92
92
|
exports.explicitlyAborted = explicitlyAborted2;
|
|
93
93
|
exports.prefixIssues = prefixIssues2;
|
|
94
94
|
exports.unwrapMessage = unwrapMessage2;
|
|
95
|
+
exports.sizeCaps = sizeCaps2;
|
|
95
96
|
exports.attachSchema = attachSchema2;
|
|
96
97
|
exports.finalizeIssue = finalizeIssue2;
|
|
97
98
|
exports.getSizableOrigin = getSizableOrigin2;
|
|
@@ -626,6 +627,18 @@ var require_util = __commonJS({
|
|
|
626
627
|
function unwrapMessage2(message) {
|
|
627
628
|
return typeof message === "string" ? message : message?.message;
|
|
628
629
|
}
|
|
630
|
+
function sizeCaps2(defChecks) {
|
|
631
|
+
const caps = [];
|
|
632
|
+
for (const ch2 of defChecks ?? []) {
|
|
633
|
+
const d2 = ch2._zod.def;
|
|
634
|
+
if (!/_(length|size)$|_equals$/.test(d2.check))
|
|
635
|
+
break;
|
|
636
|
+
const max = d2.maximum ?? d2.length ?? d2.size;
|
|
637
|
+
if (max >= 0)
|
|
638
|
+
caps.push([max, ch2]);
|
|
639
|
+
}
|
|
640
|
+
return caps;
|
|
641
|
+
}
|
|
629
642
|
function attachSchema2(issues, start, inst) {
|
|
630
643
|
var _a4;
|
|
631
644
|
for (let i2 = start; i2 < issues.length; i2++) {
|
|
@@ -2290,7 +2303,7 @@ var require_versions = __commonJS({
|
|
|
2290
2303
|
exports.version = {
|
|
2291
2304
|
major: 4,
|
|
2292
2305
|
minor: 6,
|
|
2293
|
-
patch:
|
|
2306
|
+
patch: 7
|
|
2294
2307
|
};
|
|
2295
2308
|
(function() {
|
|
2296
2309
|
var keys = Object.getOwnPropertyNames(exports);
|
|
@@ -3124,6 +3137,31 @@ var require_schemas = __commonJS({
|
|
|
3124
3137
|
return payload;
|
|
3125
3138
|
};
|
|
3126
3139
|
});
|
|
3140
|
+
function capSize2(inst, size) {
|
|
3141
|
+
const caps = util.sizeCaps(inst._zod.def.checks);
|
|
3142
|
+
if (!caps.length)
|
|
3143
|
+
return;
|
|
3144
|
+
const cap = Math.min(...caps.map((c2) => c2[0]));
|
|
3145
|
+
const parse3 = inst._zod.parse;
|
|
3146
|
+
inst._zod.parse = (payload, ctx) => {
|
|
3147
|
+
const n2 = size(payload.value);
|
|
3148
|
+
if (n2 > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
3149
|
+
for (const [max, ch2] of util.sizeCaps(inst._zod.def.checks)) {
|
|
3150
|
+
if (n2 > max && ch2._zod.def.when(payload)) {
|
|
3151
|
+
const start = payload.issues.length;
|
|
3152
|
+
ch2._zod.check(payload);
|
|
3153
|
+
payload.issues[start].continue = false;
|
|
3154
|
+
util.attachSchema(payload.issues, start, inst);
|
|
3155
|
+
return payload;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
return parse3(payload, ctx);
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
var arraySize2 = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
3163
|
+
var mapSize2 = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
3164
|
+
var setSize2 = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
3127
3165
|
function handleArrayResult2(result, final, index) {
|
|
3128
3166
|
if (result.issues.length) {
|
|
3129
3167
|
final.issues.push(...util.prefixIssues(index, result.issues));
|
|
@@ -3167,6 +3205,7 @@ var require_schemas = __commonJS({
|
|
|
3167
3205
|
}
|
|
3168
3206
|
return payload;
|
|
3169
3207
|
};
|
|
3208
|
+
capSize2(inst, arraySize2);
|
|
3170
3209
|
});
|
|
3171
3210
|
function handlePropertyResult2(result, final, key, input2, optin, optout) {
|
|
3172
3211
|
const isPresent = key in input2;
|
|
@@ -4123,6 +4162,7 @@ var require_schemas = __commonJS({
|
|
|
4123
4162
|
return Promise.all(proms).then(() => payload);
|
|
4124
4163
|
return payload;
|
|
4125
4164
|
};
|
|
4165
|
+
capSize2(inst, mapSize2);
|
|
4126
4166
|
});
|
|
4127
4167
|
function handleMapResult2(keyResult, valueResult, final, key, input2, inst, ctx) {
|
|
4128
4168
|
if (keyResult.issues.length) {
|
|
@@ -4189,6 +4229,7 @@ var require_schemas = __commonJS({
|
|
|
4189
4229
|
return Promise.all(proms).then(() => payload);
|
|
4190
4230
|
return payload;
|
|
4191
4231
|
};
|
|
4232
|
+
capSize2(inst, setSize2);
|
|
4192
4233
|
});
|
|
4193
4234
|
function handleSetResult2(result, final) {
|
|
4194
4235
|
if (result.issues.length) {
|
|
@@ -16293,9 +16334,15 @@ ${code2}
|
|
|
16293
16334
|
doc.write(`}`);
|
|
16294
16335
|
return outputVar;
|
|
16295
16336
|
}
|
|
16337
|
+
function generateSizeGate2(doc, schema, size) {
|
|
16338
|
+
for (const [max, check2] of util.sizeCaps(schema._zod.def.checks)) {
|
|
16339
|
+
doc.write(`if (${size} > ${numericOperand2(max, check2._zod.def.check)}) return INVALID;`);
|
|
16340
|
+
}
|
|
16341
|
+
}
|
|
16296
16342
|
function generateArrayCheck2(doc, ctx, schema, accessor, buildsValue = true) {
|
|
16297
16343
|
const def = schema._zod.def;
|
|
16298
16344
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
16345
|
+
generateSizeGate2(doc, schema, `${accessor}.length`);
|
|
16299
16346
|
const outputVar = buildsValue ? newVar2(ctx) : null;
|
|
16300
16347
|
const iVar = newVar2(ctx);
|
|
16301
16348
|
const elemVar = newVar2(ctx);
|
|
@@ -16695,6 +16742,7 @@ ${code2}
|
|
|
16695
16742
|
function generateMapCheck2(doc, ctx, schema, accessor) {
|
|
16696
16743
|
const def = schema._zod.def;
|
|
16697
16744
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
16745
|
+
generateSizeGate2(doc, schema, `${accessor}.size`);
|
|
16698
16746
|
const outputVar = newVar2(ctx);
|
|
16699
16747
|
const kVar = newVar2(ctx);
|
|
16700
16748
|
const valVar = newVar2(ctx);
|
|
@@ -16711,6 +16759,7 @@ ${code2}
|
|
|
16711
16759
|
function generateSetCheck2(doc, ctx, schema, accessor) {
|
|
16712
16760
|
const def = schema._zod.def;
|
|
16713
16761
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
16762
|
+
generateSizeGate2(doc, schema, `${accessor}.size`);
|
|
16714
16763
|
const outputVar = newVar2(ctx);
|
|
16715
16764
|
const valVar = newVar2(ctx);
|
|
16716
16765
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -78172,7 +78221,7 @@ var API_KEY_RE = new RegExp([
|
|
|
78172
78221
|
// webhook signing secret (Stripe-style)
|
|
78173
78222
|
].map((p2) => `\\b${p2}`).join("|"), "g");
|
|
78174
78223
|
var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
78175
|
-
var QUERY_SECRET_RE = new RegExp(`([
|
|
78224
|
+
var QUERY_SECRET_RE = new RegExp(`([?&#;](?:(?:access|refresh|id|auth|session|csrf|xsrf|api)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|secret|token|auth|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
78176
78225
|
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
78177
78226
|
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
78178
78227
|
var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -78183,6 +78232,8 @@ var JSON_SECRET_KEYS = [
|
|
|
78183
78232
|
"api[_-]?secret",
|
|
78184
78233
|
"(?:x[_-])?api[_-]?key",
|
|
78185
78234
|
"private[_-]?key",
|
|
78235
|
+
"aws[_-]?secret[_-]?access[_-]?key",
|
|
78236
|
+
// fleet-audit#1297
|
|
78186
78237
|
"code[_-]?verifier",
|
|
78187
78238
|
// PKCE; a bare `verifier` key is too generic for JSON
|
|
78188
78239
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
@@ -78880,6 +78931,7 @@ __export(util_exports, {
|
|
|
78880
78931
|
required: () => required,
|
|
78881
78932
|
safeExtend: () => safeExtend,
|
|
78882
78933
|
shallowClone: () => shallowClone,
|
|
78934
|
+
sizeCaps: () => sizeCaps,
|
|
78883
78935
|
slugify: () => slugify,
|
|
78884
78936
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
78885
78937
|
toZod: () => toZod,
|
|
@@ -79399,6 +79451,18 @@ function prefixIssues(path, issues) {
|
|
|
79399
79451
|
function unwrapMessage(message) {
|
|
79400
79452
|
return typeof message === "string" ? message : message?.message;
|
|
79401
79453
|
}
|
|
79454
|
+
function sizeCaps(defChecks) {
|
|
79455
|
+
const caps = [];
|
|
79456
|
+
for (const ch2 of defChecks ?? []) {
|
|
79457
|
+
const d2 = ch2._zod.def;
|
|
79458
|
+
if (!/_(length|size)$|_equals$/.test(d2.check))
|
|
79459
|
+
break;
|
|
79460
|
+
const max = d2.maximum ?? d2.length ?? d2.size;
|
|
79461
|
+
if (max >= 0)
|
|
79462
|
+
caps.push([max, ch2]);
|
|
79463
|
+
}
|
|
79464
|
+
return caps;
|
|
79465
|
+
}
|
|
79402
79466
|
function attachSchema(issues, start, inst) {
|
|
79403
79467
|
var _a4;
|
|
79404
79468
|
for (let i2 = start; i2 < issues.length; i2++) {
|
|
@@ -80819,7 +80883,7 @@ ${content.join("\n")}
|
|
|
80819
80883
|
var version = {
|
|
80820
80884
|
major: 4,
|
|
80821
80885
|
minor: 6,
|
|
80822
|
-
patch:
|
|
80886
|
+
patch: 7
|
|
80823
80887
|
};
|
|
80824
80888
|
|
|
80825
80889
|
// node_modules/zod/v4/core/schemas.js
|
|
@@ -81575,6 +81639,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
|
|
|
81575
81639
|
return payload;
|
|
81576
81640
|
};
|
|
81577
81641
|
});
|
|
81642
|
+
function capSize(inst, size) {
|
|
81643
|
+
const caps = sizeCaps(inst._zod.def.checks);
|
|
81644
|
+
if (!caps.length)
|
|
81645
|
+
return;
|
|
81646
|
+
const cap = Math.min(...caps.map((c2) => c2[0]));
|
|
81647
|
+
const parse3 = inst._zod.parse;
|
|
81648
|
+
inst._zod.parse = (payload, ctx) => {
|
|
81649
|
+
const n2 = size(payload.value);
|
|
81650
|
+
if (n2 > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
81651
|
+
for (const [max, ch2] of sizeCaps(inst._zod.def.checks)) {
|
|
81652
|
+
if (n2 > max && ch2._zod.def.when(payload)) {
|
|
81653
|
+
const start = payload.issues.length;
|
|
81654
|
+
ch2._zod.check(payload);
|
|
81655
|
+
payload.issues[start].continue = false;
|
|
81656
|
+
attachSchema(payload.issues, start, inst);
|
|
81657
|
+
return payload;
|
|
81658
|
+
}
|
|
81659
|
+
}
|
|
81660
|
+
}
|
|
81661
|
+
return parse3(payload, ctx);
|
|
81662
|
+
};
|
|
81663
|
+
}
|
|
81664
|
+
var arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
81665
|
+
var mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
81666
|
+
var setSize = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
81578
81667
|
function handleArrayResult(result, final, index) {
|
|
81579
81668
|
if (result.issues.length) {
|
|
81580
81669
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
@@ -81618,6 +81707,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
|
81618
81707
|
}
|
|
81619
81708
|
return payload;
|
|
81620
81709
|
};
|
|
81710
|
+
capSize(inst, arraySize);
|
|
81621
81711
|
});
|
|
81622
81712
|
function handlePropertyResult(result, final, key, input2, optin, optout) {
|
|
81623
81713
|
const isPresent = key in input2;
|
|
@@ -82574,6 +82664,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
|
|
|
82574
82664
|
return Promise.all(proms).then(() => payload);
|
|
82575
82665
|
return payload;
|
|
82576
82666
|
};
|
|
82667
|
+
capSize(inst, mapSize);
|
|
82577
82668
|
});
|
|
82578
82669
|
function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
|
|
82579
82670
|
if (keyResult.issues.length) {
|
|
@@ -82640,6 +82731,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
|
|
|
82640
82731
|
return Promise.all(proms).then(() => payload);
|
|
82641
82732
|
return payload;
|
|
82642
82733
|
};
|
|
82734
|
+
capSize(inst, setSize);
|
|
82643
82735
|
});
|
|
82644
82736
|
function handleSetResult(result, final) {
|
|
82645
82737
|
if (result.issues.length) {
|
|
@@ -92170,9 +92262,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
92170
92262
|
doc.write(`}`);
|
|
92171
92263
|
return outputVar;
|
|
92172
92264
|
}
|
|
92265
|
+
function generateSizeGate(doc, schema, size) {
|
|
92266
|
+
for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
|
|
92267
|
+
doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
|
|
92268
|
+
}
|
|
92269
|
+
}
|
|
92173
92270
|
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
92174
92271
|
const def = schema._zod.def;
|
|
92175
92272
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
92273
|
+
generateSizeGate(doc, schema, `${accessor}.length`);
|
|
92176
92274
|
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
92177
92275
|
const iVar = newVar(ctx);
|
|
92178
92276
|
const elemVar = newVar(ctx);
|
|
@@ -92572,6 +92670,7 @@ function literalPropertyKey(ctx, key) {
|
|
|
92572
92670
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
92573
92671
|
const def = schema._zod.def;
|
|
92574
92672
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
92673
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
92575
92674
|
const outputVar = newVar(ctx);
|
|
92576
92675
|
const kVar = newVar(ctx);
|
|
92577
92676
|
const valVar = newVar(ctx);
|
|
@@ -92588,6 +92687,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
|
|
|
92588
92687
|
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
92589
92688
|
const def = schema._zod.def;
|
|
92590
92689
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
92690
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
92591
92691
|
const outputVar = newVar(ctx);
|
|
92592
92692
|
const valVar = newVar(ctx);
|
|
92593
92693
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -98900,11 +99000,21 @@ function sanitizeBaseName(base) {
|
|
|
98900
99000
|
const safe = base.replace(/[/\\]+/g, "_").replace(/\.\.+/g, "_").replace(/^\.+/, "").trim();
|
|
98901
99001
|
return safe.length > 0 ? safe : "file";
|
|
98902
99002
|
}
|
|
99003
|
+
var SAFE_EXTENSION = /^[A-Za-z0-9][A-Za-z0-9._-]{0,15}$/;
|
|
99004
|
+
function isSafeExtension(ext) {
|
|
99005
|
+
return SAFE_EXTENSION.test(ext);
|
|
99006
|
+
}
|
|
99007
|
+
function assertSafeExtension(ext, caller) {
|
|
99008
|
+
if (!isSafeExtension(ext)) {
|
|
99009
|
+
throw new TypeError(`${caller}: extension must be 1-16 characters of A-Z a-z 0-9 . _ - starting with a letter or digit, got ${JSON.stringify(ext)}.`);
|
|
99010
|
+
}
|
|
99011
|
+
}
|
|
98903
99012
|
function candidate(dir, safeBase, ext, n2) {
|
|
98904
99013
|
return join4(dir, n2 === 1 ? `${safeBase}.${ext}` : `${safeBase}-${n2}.${ext}`);
|
|
98905
99014
|
}
|
|
98906
99015
|
var DEFAULT_MAX_ATTEMPTS = 1e3;
|
|
98907
99016
|
function writeUniqueFileSync(dir, baseName, ext, bytes, mode) {
|
|
99017
|
+
assertSafeExtension(ext, "writeBinaryOutput");
|
|
98908
99018
|
mkdirSync2(dir, { recursive: true });
|
|
98909
99019
|
const safe = sanitizeBaseName(baseName);
|
|
98910
99020
|
for (let n2 = 1; ; n2++) {
|
|
@@ -98959,19 +99069,61 @@ function defaultOutputDir(envVar, name) {
|
|
|
98959
99069
|
var MIME_EXT = {
|
|
98960
99070
|
"image/png": "png",
|
|
98961
99071
|
"image/jpeg": "jpg",
|
|
99072
|
+
"image/jpg": "jpg",
|
|
99073
|
+
"image/pjpeg": "jpg",
|
|
98962
99074
|
"image/webp": "webp",
|
|
98963
99075
|
"image/gif": "gif",
|
|
98964
99076
|
"image/heic": "heic",
|
|
99077
|
+
"image/heif": "heif",
|
|
98965
99078
|
"image/avif": "avif",
|
|
99079
|
+
"image/tiff": "tif",
|
|
99080
|
+
"image/bmp": "bmp",
|
|
99081
|
+
"image/svg+xml": "svg",
|
|
98966
99082
|
"application/pdf": "pdf",
|
|
98967
99083
|
"application/json": "json",
|
|
98968
99084
|
"application/zip": "zip",
|
|
99085
|
+
"application/gzip": "gz",
|
|
99086
|
+
"text/plain": "txt",
|
|
99087
|
+
"text/csv": "csv",
|
|
99088
|
+
"text/html": "html",
|
|
99089
|
+
"text/calendar": "ics",
|
|
98969
99090
|
"audio/midi": "mid",
|
|
99091
|
+
"audio/mpeg": "mp3",
|
|
99092
|
+
"audio/mp3": "mp3",
|
|
99093
|
+
"audio/wav": "wav",
|
|
99094
|
+
"audio/wave": "wav",
|
|
99095
|
+
"audio/x-wav": "wav",
|
|
99096
|
+
"audio/ogg": "ogg",
|
|
99097
|
+
"audio/webm": "webm",
|
|
99098
|
+
"audio/mp4": "m4a",
|
|
98970
99099
|
"video/mp4": "mp4",
|
|
98971
|
-
"video/quicktime": "mov"
|
|
99100
|
+
"video/quicktime": "mov",
|
|
99101
|
+
"video/webm": "webm"
|
|
98972
99102
|
};
|
|
99103
|
+
var PLAIN_EXT = /^[a-z0-9]{1,5}$/;
|
|
99104
|
+
function extensionForMime(mime, opts = {}) {
|
|
99105
|
+
const fallback = opts.fallback ?? "bin";
|
|
99106
|
+
assertSafeExtension(fallback, "extensionForMime");
|
|
99107
|
+
const type = (mime ?? "").split(";", 1)[0].trim().toLowerCase();
|
|
99108
|
+
const known = MIME_EXT[type];
|
|
99109
|
+
if (known !== void 0)
|
|
99110
|
+
return known;
|
|
99111
|
+
const slash = type.indexOf("/");
|
|
99112
|
+
const subtype = slash > 0 && type.indexOf("/", slash + 1) < 0 ? type.slice(slash + 1).replace(/^x-/, "") : "";
|
|
99113
|
+
if (PLAIN_EXT.test(subtype))
|
|
99114
|
+
return subtype;
|
|
99115
|
+
if (opts.url !== void 0) {
|
|
99116
|
+
try {
|
|
99117
|
+
const fromUrl = /\.([a-z0-9]{1,5})$/i.exec(new URL(opts.url).pathname)?.[1]?.toLowerCase();
|
|
99118
|
+
if (fromUrl !== void 0 && isSafeExtension(fromUrl))
|
|
99119
|
+
return fromUrl;
|
|
99120
|
+
} catch {
|
|
99121
|
+
}
|
|
99122
|
+
}
|
|
99123
|
+
return fallback;
|
|
99124
|
+
}
|
|
98973
99125
|
function writeBinaryOutput(opts) {
|
|
98974
|
-
const ext = opts.extension ?? (opts.mimeType
|
|
99126
|
+
const ext = opts.extension ?? extensionForMime(opts.mimeType);
|
|
98975
99127
|
return writeUniqueFileSync(opts.dir, opts.baseName, ext, Buffer.from(opts.base64, "base64"), opts.mode);
|
|
98976
99128
|
}
|
|
98977
99129
|
|
|
@@ -99040,10 +99192,34 @@ function getHeader(headers, name) {
|
|
|
99040
99192
|
return typeof value === "string" ? value : null;
|
|
99041
99193
|
}
|
|
99042
99194
|
|
|
99195
|
+
// node_modules/@chrischall/mcp-utils/dist/internal/ascii.js
|
|
99196
|
+
function asciiLower(s2) {
|
|
99197
|
+
return s2.replace(/[A-Z]+/g, (m2) => m2.toLowerCase());
|
|
99198
|
+
}
|
|
99199
|
+
|
|
99043
99200
|
// node_modules/@chrischall/mcp-utils/dist/scrape/index.js
|
|
99044
99201
|
var NEXT_DATA_MAX_CHARS = 16 * 1024 * 1024;
|
|
99045
99202
|
function isCloudflareChallenge(html) {
|
|
99046
|
-
return
|
|
99203
|
+
return html.includes("_cf_chl_opt") || hasJustAMomentTitle(html);
|
|
99204
|
+
}
|
|
99205
|
+
var JUST_A_MOMENT = "just a moment";
|
|
99206
|
+
function hasJustAMomentTitle(html) {
|
|
99207
|
+
const lower = asciiLower(html);
|
|
99208
|
+
let i2 = 0;
|
|
99209
|
+
for (; ; ) {
|
|
99210
|
+
const open2 = lower.indexOf("<title", i2);
|
|
99211
|
+
if (open2 < 0)
|
|
99212
|
+
return false;
|
|
99213
|
+
const gt2 = lower.indexOf(">", open2 + 6);
|
|
99214
|
+
if (gt2 < 0)
|
|
99215
|
+
return false;
|
|
99216
|
+
let j2 = gt2 + 1;
|
|
99217
|
+
while (j2 < lower.length && /\s/.test(lower[j2]))
|
|
99218
|
+
j2++;
|
|
99219
|
+
if (lower.startsWith(JUST_A_MOMENT, j2))
|
|
99220
|
+
return true;
|
|
99221
|
+
i2 = gt2 + 1;
|
|
99222
|
+
}
|
|
99047
99223
|
}
|
|
99048
99224
|
|
|
99049
99225
|
// node_modules/@chrischall/mcp-utils/dist/http/net-atoms.js
|
|
@@ -99072,7 +99248,7 @@ var ResponseTooLargeError = class extends McpToolError {
|
|
|
99072
99248
|
};
|
|
99073
99249
|
async function readBytesCapped(res, maxBytes, service, bounded) {
|
|
99074
99250
|
if (maxBytes !== void 0) {
|
|
99075
|
-
const declared = Number(res
|
|
99251
|
+
const declared = Number(responseHeader(res, "content-length") ?? NaN);
|
|
99076
99252
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
99077
99253
|
cancelStream(res.body);
|
|
99078
99254
|
throw new ResponseTooLargeError(service, maxBytes);
|
|
@@ -99162,6 +99338,29 @@ var DEFAULT_RETRY = { count: 1, delayMs: 2e3 };
|
|
|
99162
99338
|
var defaultSleep = (ms2) => new Promise((r2) => setTimeout(r2, ms2));
|
|
99163
99339
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
99164
99340
|
var DEFAULT_MAX_REDIRECTS = 5;
|
|
99341
|
+
var FETCH_MAX_REDIRECTS = 20;
|
|
99342
|
+
var CROSS_ORIGIN_DROPPED_HEADERS = ["authorization", "cookie", "proxy-authorization"];
|
|
99343
|
+
function withoutCredentials(headers, tokenHeader) {
|
|
99344
|
+
const drop = new Set(CROSS_ORIGIN_DROPPED_HEADERS);
|
|
99345
|
+
if (tokenHeader !== void 0)
|
|
99346
|
+
drop.add(tokenHeader.toLowerCase());
|
|
99347
|
+
const out = {};
|
|
99348
|
+
for (const [k2, v2] of Object.entries(headers))
|
|
99349
|
+
if (!drop.has(k2.toLowerCase()))
|
|
99350
|
+
out[k2] = v2;
|
|
99351
|
+
return out;
|
|
99352
|
+
}
|
|
99353
|
+
function joinQuery(path, query) {
|
|
99354
|
+
if (!query)
|
|
99355
|
+
return path;
|
|
99356
|
+
const hashAt = path.indexOf("#");
|
|
99357
|
+
const head = hashAt < 0 ? path : path.slice(0, hashAt);
|
|
99358
|
+
const fragment = hashAt < 0 ? "" : path.slice(hashAt);
|
|
99359
|
+
if (!head.includes("?"))
|
|
99360
|
+
return `${head}${query}${fragment}`;
|
|
99361
|
+
const sep2 = head.endsWith("?") || head.endsWith("&") ? "" : "&";
|
|
99362
|
+
return `${head}${sep2}${query.slice(1)}${fragment}`;
|
|
99363
|
+
}
|
|
99165
99364
|
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
99166
99365
|
function cancelBody(res) {
|
|
99167
99366
|
try {
|
|
@@ -99401,7 +99600,9 @@ function createApiClient(opts) {
|
|
|
99401
99600
|
const retryStatuses = retry.statuses ?? [429];
|
|
99402
99601
|
const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
99403
99602
|
const redirectMode = opts.redirect;
|
|
99404
|
-
const
|
|
99603
|
+
const stripCrossOrigin = redirectMode === void 0 && opts.tokenHeader !== void 0;
|
|
99604
|
+
const manualRedirects = redirectMode === "same-origin" || stripCrossOrigin;
|
|
99605
|
+
const maxRedirects = opts.maxRedirects ?? (stripCrossOrigin ? FETCH_MAX_REDIRECTS : DEFAULT_MAX_REDIRECTS);
|
|
99405
99606
|
const reportWriteOutcome = opts.writeOutcomeUnknown !== false;
|
|
99406
99607
|
const defaultMaxBytes = checkMaxBytes("maxResponseBytes", opts.maxResponseBytes);
|
|
99407
99608
|
if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0) {
|
|
@@ -99503,7 +99704,7 @@ function createApiClient(opts) {
|
|
|
99503
99704
|
`Refusing request to ${service}: path ${JSON.stringify(path.split(/[?#]/, 1)[0])} contains a ${hazard}, which URL normalisation would rewrite. Build paths with apiPath\`...\` so each value is one encoded segment.`
|
|
99504
99705
|
);
|
|
99505
99706
|
}
|
|
99506
|
-
const url4 = `${base}${path
|
|
99707
|
+
const url4 = `${base}${joinQuery(path, query)}`;
|
|
99507
99708
|
if (baseOrigin === void 0)
|
|
99508
99709
|
return url4;
|
|
99509
99710
|
let parsed;
|
|
@@ -99517,14 +99718,21 @@ function createApiClient(opts) {
|
|
|
99517
99718
|
}
|
|
99518
99719
|
return url4;
|
|
99519
99720
|
}
|
|
99520
|
-
function
|
|
99521
|
-
let next;
|
|
99721
|
+
function resolveLocation(location, from) {
|
|
99522
99722
|
try {
|
|
99523
|
-
|
|
99723
|
+
return new URL(location, from).href;
|
|
99524
99724
|
} catch {
|
|
99525
99725
|
throw new RedirectRefusedError(`Refusing redirect from ${service}: the Location header is not a valid URL.`);
|
|
99526
99726
|
}
|
|
99527
|
-
|
|
99727
|
+
}
|
|
99728
|
+
function onBaseOrigin(href) {
|
|
99729
|
+
const next = new URL(href);
|
|
99730
|
+
return baseOrigin !== void 0 && next.origin === baseOrigin && next.username === baseParts?.username && next.password === baseParts?.password;
|
|
99731
|
+
}
|
|
99732
|
+
function sameOriginHop(location, from) {
|
|
99733
|
+
const href = resolveLocation(location, from);
|
|
99734
|
+
const next = new URL(href);
|
|
99735
|
+
if (!onBaseOrigin(href)) {
|
|
99528
99736
|
throw new RedirectRefusedError(`Refusing redirect from ${service} to ${next.origin}: it leaves the base origin ${baseOrigin ?? "(unparseable)"}, and following it would re-send the credential there.`);
|
|
99529
99737
|
}
|
|
99530
99738
|
return next.href;
|
|
@@ -99534,20 +99742,21 @@ function createApiClient(opts) {
|
|
|
99534
99742
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
99535
99743
|
const url4 = resolveUrl(path, query);
|
|
99536
99744
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
99537
|
-
const fetchHop = (hopUrl, hopMethod, withBody, token, signal) => {
|
|
99745
|
+
const fetchHop = (hopUrl, hopMethod, withBody, token, signal, credentialed = true) => {
|
|
99538
99746
|
if (!signal?.aborted)
|
|
99539
99747
|
dispatch.sent = true;
|
|
99748
|
+
const headers = {
|
|
99749
|
+
Accept: "application/json",
|
|
99750
|
+
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
99751
|
+
...opts.baseHeaders,
|
|
99752
|
+
...authHeader(token || void 0),
|
|
99753
|
+
...opt.headers
|
|
99754
|
+
};
|
|
99540
99755
|
return doFetch(hopUrl, {
|
|
99541
99756
|
method: hopMethod,
|
|
99542
|
-
headers:
|
|
99543
|
-
Accept: "application/json",
|
|
99544
|
-
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
99545
|
-
...opts.baseHeaders,
|
|
99546
|
-
...authHeader(token || void 0),
|
|
99547
|
-
...opt.headers
|
|
99548
|
-
},
|
|
99757
|
+
headers: credentialed ? headers : withoutCredentials(headers, opts.tokenHeader),
|
|
99549
99758
|
...signal ? { signal } : {},
|
|
99550
|
-
...
|
|
99759
|
+
...manualRedirects ? { redirect: "manual" } : redirectMode !== void 0 ? { redirect: redirectMode } : {},
|
|
99551
99760
|
...withBody ? bodyInit : {}
|
|
99552
99761
|
}).catch((err) => {
|
|
99553
99762
|
markTransportFailure(err);
|
|
@@ -99555,13 +99764,14 @@ function createApiClient(opts) {
|
|
|
99555
99764
|
});
|
|
99556
99765
|
};
|
|
99557
99766
|
const fetchWith = async (token, signal) => {
|
|
99558
|
-
if (
|
|
99767
|
+
if (!manualRedirects)
|
|
99559
99768
|
return fetchHop(url4, method, true, token, signal);
|
|
99560
99769
|
let hopUrl = url4;
|
|
99561
99770
|
let hopMethod = method;
|
|
99562
99771
|
let withBody = true;
|
|
99772
|
+
let credentialed = true;
|
|
99563
99773
|
for (let hops = 0; ; hops += 1) {
|
|
99564
|
-
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal);
|
|
99774
|
+
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal, credentialed);
|
|
99565
99775
|
const location = REDIRECT_STATUSES.has(res.status) ? responseHeader(res, "location") : void 0;
|
|
99566
99776
|
if (location === void 0 || location === null)
|
|
99567
99777
|
return res;
|
|
@@ -99569,7 +99779,13 @@ function createApiClient(opts) {
|
|
|
99569
99779
|
if (hops >= maxRedirects) {
|
|
99570
99780
|
throw new RedirectRefusedError(`Refusing request to ${service}: too many redirects (more than ${maxRedirects}).`);
|
|
99571
99781
|
}
|
|
99572
|
-
|
|
99782
|
+
if (stripCrossOrigin) {
|
|
99783
|
+
hopUrl = resolveLocation(location, hopUrl);
|
|
99784
|
+
if (credentialed && !onBaseOrigin(hopUrl))
|
|
99785
|
+
credentialed = false;
|
|
99786
|
+
} else {
|
|
99787
|
+
hopUrl = sameOriginHop(location, hopUrl);
|
|
99788
|
+
}
|
|
99573
99789
|
if (res.status === 303 || (res.status === 301 || res.status === 302) && hopMethod === "POST") {
|
|
99574
99790
|
if (hopMethod !== "HEAD")
|
|
99575
99791
|
hopMethod = "GET";
|
|
@@ -101036,16 +101252,6 @@ function resolveMimeType(contentType, bytes) {
|
|
|
101036
101252
|
const declared = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
101037
101253
|
return declared && !GENERIC_TYPES.has(declared) ? declared : void 0;
|
|
101038
101254
|
}
|
|
101039
|
-
function extensionFromUrl(url4) {
|
|
101040
|
-
const match = /\.([a-z0-9]{1,5})$/i.exec(new URL(url4).pathname);
|
|
101041
|
-
return match ? match[1].toLowerCase() : void 0;
|
|
101042
|
-
}
|
|
101043
|
-
function extensionFor(mimeType, url4) {
|
|
101044
|
-
const subtype = mimeType?.split("/")[1];
|
|
101045
|
-
if (subtype === "jpeg") return "jpg";
|
|
101046
|
-
if (subtype && /^[a-z0-9]{1,5}$/.test(subtype)) return subtype;
|
|
101047
|
-
return extensionFromUrl(url4) ?? "bin";
|
|
101048
|
-
}
|
|
101049
101255
|
async function extractPdfText(bytes) {
|
|
101050
101256
|
const { extractText: extractText2, getDocumentProxy: getDocumentProxy2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
101051
101257
|
const doc = await getDocumentProxy2(Uint8Array.from(bytes));
|
|
@@ -101117,7 +101323,8 @@ function registerReceiptTools(server, client2) {
|
|
|
101117
101323
|
}),
|
|
101118
101324
|
baseName: `splitwise-receipt-${id}`,
|
|
101119
101325
|
base64: base643,
|
|
101120
|
-
|
|
101326
|
+
// The MIME type, else the receipt URL's own extension, else `bin`.
|
|
101327
|
+
extension: extensionForMime(mimeType, { url: url4 }),
|
|
101121
101328
|
mode: RECEIPT_FILE_MODE
|
|
101122
101329
|
});
|
|
101123
101330
|
} catch (err) {
|
|
@@ -101323,7 +101530,7 @@ function registerUtilityTools(server, client2) {
|
|
|
101323
101530
|
// src/index.ts
|
|
101324
101531
|
await runMcp({
|
|
101325
101532
|
name: "splitwise-mcp",
|
|
101326
|
-
version: "3.4.
|
|
101533
|
+
version: "3.4.1",
|
|
101327
101534
|
// x-release-please-version
|
|
101328
101535
|
deps: client,
|
|
101329
101536
|
banner: "[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.",
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { registerUtilityTools } from './tools/utilities.js';
|
|
|
19
19
|
// client into the same registrars instead of this singleton.
|
|
20
20
|
await runMcp({
|
|
21
21
|
name: 'splitwise-mcp',
|
|
22
|
-
version: '3.4.
|
|
22
|
+
version: '3.4.1', // x-release-please-version
|
|
23
23
|
deps: client,
|
|
24
24
|
banner: '[splitwise-mcp] This project was developed and is maintained by AI (Claude Sonnet 4.6). Use at your own discretion.',
|
|
25
25
|
tools: [
|
package/dist/tools/receipts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { minifiedResult, imageResult, readEnvVar, resolveOutputDir, writeBinaryOutput, sniffMimeBytes, } from '@chrischall/mcp-utils';
|
|
2
|
+
import { minifiedResult, imageResult, readEnvVar, resolveOutputDir, writeBinaryOutput, sniffMimeBytes, extensionForMime, } from '@chrischall/mcp-utils';
|
|
3
3
|
import { UNTRUSTED_DESCRIPTION_SUFFIX, swUntrustedEnvelope } from './_untrusted.js';
|
|
4
4
|
/** Cap on bytes returned as base64 in the tool result. */
|
|
5
5
|
const MAX_INLINE_BYTES = 4 * 1024 * 1024;
|
|
@@ -49,24 +49,6 @@ function resolveMimeType(contentType, bytes) {
|
|
|
49
49
|
const declared = (contentType ?? '').split(';')[0].trim().toLowerCase();
|
|
50
50
|
return declared && !GENERIC_TYPES.has(declared) ? declared : undefined;
|
|
51
51
|
}
|
|
52
|
-
/** Extension from a URL path (`…/receipt.jpg?sig=…` → `jpg`), when it has one. */
|
|
53
|
-
function extensionFromUrl(url) {
|
|
54
|
-
const match = /\.([a-z0-9]{1,5})$/i.exec(new URL(url).pathname);
|
|
55
|
-
return match ? match[1].toLowerCase() : undefined;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* File extension for the receipt: the MIME subtype when it looks like a plain
|
|
59
|
-
* one (`application/pdf` → `pdf`, `image/heic` → `heic`), else the URL's own
|
|
60
|
-
* extension, else `bin`.
|
|
61
|
-
*/
|
|
62
|
-
function extensionFor(mimeType, url) {
|
|
63
|
-
const subtype = mimeType?.split('/')[1];
|
|
64
|
-
if (subtype === 'jpeg')
|
|
65
|
-
return 'jpg';
|
|
66
|
-
if (subtype && /^[a-z0-9]{1,5}$/.test(subtype))
|
|
67
|
-
return subtype;
|
|
68
|
-
return extensionFromUrl(url) ?? 'bin';
|
|
69
|
-
}
|
|
70
52
|
/**
|
|
71
53
|
* Extract a PDF's text layer. `unpdf` is imported lazily so the cost lands only
|
|
72
54
|
* on calls that ask for text — it is a sizable pdf.js build, and every other
|
|
@@ -162,7 +144,8 @@ export function registerReceiptTools(server, client) {
|
|
|
162
144
|
}),
|
|
163
145
|
baseName: `splitwise-receipt-${id}`,
|
|
164
146
|
base64,
|
|
165
|
-
|
|
147
|
+
// The MIME type, else the receipt URL's own extension, else `bin`.
|
|
148
|
+
extension: extensionForMime(mimeType, { url }),
|
|
166
149
|
mode: RECEIPT_FILE_MODE,
|
|
167
150
|
});
|
|
168
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "splitwise-mcp",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"mcpName": "io.github.chrischall/splitwise-mcp",
|
|
5
5
|
"description": "Splitwise MCP server for Claude — developed and maintained by AI (Claude Code)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@chrischall/mcp-utils": "^3.0.
|
|
48
|
+
"@chrischall/mcp-utils": "^3.0.3",
|
|
49
49
|
"@modelcontextprotocol/server": "^2.3.0",
|
|
50
50
|
"dotenv": "^18.0.2",
|
|
51
51
|
"unpdf": "^1.8.1",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/chrischall/splitwise-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "3.4.
|
|
9
|
+
"version": "3.4.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "splitwise-mcp",
|
|
14
|
-
"version": "3.4.
|
|
14
|
+
"version": "3.4.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|