@utilix-tech/mcp 0.78.0 → 0.79.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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
4
4
 
5
+ ## 0.79.0
6
+ - Add `calculate_real_rate_of_return` tool: apply the Fisher equation to solve for whichever of the nominal rate, inflation rate, or inflation-adjusted real rate is missing, given exactly two of the three.
7
+ - Add `calculate_retry_backoff_schedule` tool: compute the full sequence of delays an exponential-backoff retry policy would actually produce, optionally capped and with full/equal jitter ranges, plus the total best-case and worst-case wait across all retries.
8
+
5
9
  ## 0.78.0
6
10
  - Add `ndjson_to_csv` tool: convert newline-delimited JSON (one flat object per line) into CSV, inferring the header row from the union of keys across all lines.
7
11
 
package/README.md CHANGED
@@ -164,7 +164,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
164
164
  | `convert_timezone` | Convert a date/time between timezones |
165
165
  | `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
166
166
 
167
- ### Units (45 tools)
167
+ ### Units (46 tools)
168
168
  | Tool | Description |
169
169
  |------|-------------|
170
170
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -212,6 +212,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
212
212
  | `calculate_mortgage_points` | Compare a loan with no discount points against the same loan at a reduced rate bought with points, and compute the monthly savings and the break-even month where cumulative savings recoup the points cost |
213
213
  | `calculate_loan_prepayment_penalty` | Compute the dollar penalty cost of paying a loan off early, given its remaining payoff balance and a prepayment penalty structure: either a flat percent of the payoff balance, or a fixed number of months of interest at the loan's annual rate |
214
214
  | `estimate_data_transfer_time` | Estimate the theoretical time to transfer a file of a given size over a connection of a given speed, or (given a target time instead) the bandwidth required to hit it |
215
+ | `calculate_real_rate_of_return` | Apply the Fisher equation to solve for whichever of the nominal rate, inflation rate, or inflation-adjusted real rate is missing, given exactly two of the three |
215
216
 
216
217
  ### API / Network (51 tools)
217
218
  | Tool | Description |
@@ -337,7 +338,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
337
338
  | `unicode_inspect` | Inspect Unicode code points of a string |
338
339
  | `optimize_svg` | Optimize and minify an SVG string |
339
340
 
340
- ### AI / Agent (28 tools)
341
+ ### AI / Agent (29 tools)
341
342
  | Tool | Description |
342
343
  |------|-------------|
343
344
  | `estimate_tokens` | Estimate token count and cost for an LLM prompt |
@@ -368,6 +369,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
368
369
  | `vector_similarity` | Cosine similarity, dot product, and Euclidean / Manhattan distance between two numeric vectors |
369
370
  | `format_few_shot_examples` | Format input/output example pairs into a numbered, XML-tagged, or delimiter-separated few-shot prompt template |
370
371
  | `lint_tool_schema` | Lint an OpenAI, Anthropic, or MCP tool/function-calling definition for missing descriptions, invalid names, empty enums, and required/properties mismatches |
372
+ | `calculate_retry_backoff_schedule` | Compute the full sequence of delays an exponential-backoff retry policy would actually produce, given a base delay, multiplier, and max retry count, optionally capped at a max delay and with full/equal jitter ranges |
371
373
 
372
374
  ### Image (4 tools)
373
375
  | Tool | Description |
package/dist/index.js CHANGED
@@ -1243,6 +1243,64 @@ server.tool("vector_similarity", "Compute cosine similarity, dot product, Euclid
1243
1243
  if ("error" in result) return errText(result);
1244
1244
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1245
1245
  });
1246
+ function _calculateRetryBackoffSchedule(input) {
1247
+ const { baseDelayMs, multiplier, maxRetries, maxDelayMs, jitter = "none" } = input;
1248
+ if (!Number.isFinite(baseDelayMs) || baseDelayMs <= 0) {
1249
+ return { error: "baseDelayMs must be a finite number greater than zero" };
1250
+ }
1251
+ if (!Number.isFinite(multiplier) || multiplier < 1) {
1252
+ return { error: "multiplier must be a finite number of at least 1" };
1253
+ }
1254
+ if (!Number.isInteger(maxRetries) || maxRetries < 1 || maxRetries > 50) {
1255
+ return { error: "maxRetries must be a whole number between 1 and 50" };
1256
+ }
1257
+ if (maxDelayMs !== void 0 && (!Number.isFinite(maxDelayMs) || maxDelayMs <= 0)) {
1258
+ return { error: "maxDelayMs must be a finite number greater than zero when provided" };
1259
+ }
1260
+ if (jitter !== "none" && jitter !== "full" && jitter !== "equal") {
1261
+ return { error: "jitter must be one of 'none', 'full', or 'equal'" };
1262
+ }
1263
+ const round0 = (n) => Math.round(n);
1264
+ const attempts = [];
1265
+ let totalBestCaseMs = 0;
1266
+ let totalWorstCaseMs = 0;
1267
+ for (let i = 1; i <= maxRetries; i++) {
1268
+ let nominal = baseDelayMs * Math.pow(multiplier, i - 1);
1269
+ if (maxDelayMs !== void 0) nominal = Math.min(nominal, maxDelayMs);
1270
+ let minDelay;
1271
+ let maxDelay;
1272
+ if (jitter === "full") {
1273
+ minDelay = 0;
1274
+ maxDelay = nominal;
1275
+ } else if (jitter === "equal") {
1276
+ minDelay = nominal / 2;
1277
+ maxDelay = nominal;
1278
+ } else {
1279
+ minDelay = nominal;
1280
+ maxDelay = nominal;
1281
+ }
1282
+ attempts.push({ attempt: i, nominalDelayMs: round0(nominal), minDelayMs: round0(minDelay), maxDelayMs: round0(maxDelay) });
1283
+ totalBestCaseMs += minDelay;
1284
+ totalWorstCaseMs += maxDelay;
1285
+ }
1286
+ return { attempts, jitter, totalBestCaseMs: round0(totalBestCaseMs), totalWorstCaseMs: round0(totalWorstCaseMs) };
1287
+ }
1288
+ server.tool(
1289
+ "calculate_retry_backoff_schedule",
1290
+ "Compute the full sequence of delays an exponential-backoff retry policy would actually produce, given a base delay, multiplier, and max retry count, optionally capped at a max delay and with full/equal jitter ranges, plus the total best-case and worst-case wait across all retries. Distinct from build_retry_after_header, which builds a single HTTP Retry-After response header.",
1291
+ {
1292
+ baseDelayMs: z.number().describe("Delay before the first retry, in milliseconds"),
1293
+ multiplier: z.number().describe("Factor the delay is multiplied by after each attempt"),
1294
+ maxRetries: z.number().int().describe("Number of retry attempts to compute (1-50)"),
1295
+ maxDelayMs: z.number().optional().describe("Optional cap applied to every computed delay"),
1296
+ jitter: z.enum(["none", "full", "equal"]).default("none").describe('"full" randomizes uniformly between 0 and the nominal delay; "equal" randomizes between half the nominal delay and the full value')
1297
+ },
1298
+ async ({ baseDelayMs, multiplier, maxRetries, maxDelayMs, jitter }) => {
1299
+ const result = _calculateRetryBackoffSchedule({ baseDelayMs, multiplier, maxRetries, maxDelayMs, jitter });
1300
+ if ("error" in result) return errText(result);
1301
+ return text(JSON.stringify(result, null, 2));
1302
+ }
1303
+ );
1246
1304
  server.tool("summarize_for_llm", "Extractive summarization: pick key sentences to fit a token budget.", { text: z.string(), max_tokens: z.number().int().min(10).default(200), strategy: z.enum(["extractive", "first", "last"]).default("extractive") }, async ({ text: text2, max_tokens, strategy }) => {
1247
1305
  return { content: [{ type: "text", text: JSON.stringify(summarizeForLlm(text2, max_tokens, strategy), null, 2) }] };
1248
1306
  });
@@ -2259,6 +2317,64 @@ server.tool(
2259
2317
  return text(JSON.stringify(result, null, 2));
2260
2318
  }
2261
2319
  );
2320
+ var _REAL_RATE_FIELDS_MCP = ["nominalRatePercent", "inflationRatePercent", "realRatePercent"];
2321
+ function _calculateRealRateOfReturn(input) {
2322
+ const provided = (v) => typeof v === "number" && Number.isFinite(v);
2323
+ const given = _REAL_RATE_FIELDS_MCP.filter((f) => provided(input[f]));
2324
+ if (given.length !== 2) {
2325
+ return { error: `Provide exactly two of nominalRatePercent, inflationRatePercent, realRatePercent (got ${given.length})` };
2326
+ }
2327
+ for (const f of given) {
2328
+ const v = input[f];
2329
+ if (v <= -100) return { error: `${f} must be greater than -100` };
2330
+ }
2331
+ const has = (f) => given.includes(f);
2332
+ const nominal0 = input.nominalRatePercent;
2333
+ const inflation0 = input.inflationRatePercent;
2334
+ const real0 = input.realRatePercent;
2335
+ let nominalRatePercent;
2336
+ let inflationRatePercent;
2337
+ let realRatePercent;
2338
+ let solvedFor;
2339
+ if (has("nominalRatePercent") && has("inflationRatePercent")) {
2340
+ nominalRatePercent = nominal0;
2341
+ inflationRatePercent = inflation0;
2342
+ realRatePercent = ((1 + nominal0 / 100) / (1 + inflation0 / 100) - 1) * 100;
2343
+ solvedFor = "realRatePercent";
2344
+ } else if (has("realRatePercent") && has("inflationRatePercent")) {
2345
+ realRatePercent = real0;
2346
+ inflationRatePercent = inflation0;
2347
+ nominalRatePercent = ((1 + real0 / 100) * (1 + inflation0 / 100) - 1) * 100;
2348
+ solvedFor = "nominalRatePercent";
2349
+ } else {
2350
+ nominalRatePercent = nominal0;
2351
+ realRatePercent = real0;
2352
+ inflationRatePercent = ((1 + nominal0 / 100) / (1 + real0 / 100) - 1) * 100;
2353
+ solvedFor = "inflationRatePercent";
2354
+ }
2355
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
2356
+ return {
2357
+ nominalRatePercent: round4(nominalRatePercent),
2358
+ inflationRatePercent: round4(inflationRatePercent),
2359
+ realRatePercent: round4(realRatePercent),
2360
+ given,
2361
+ solvedFor
2362
+ };
2363
+ }
2364
+ server.tool(
2365
+ "calculate_real_rate_of_return",
2366
+ "Apply the Fisher equation, (1 + nominal) = (1 + real) * (1 + inflation), to solve for whichever of the nominal rate, inflation rate, or inflation-adjusted real rate is missing, given exactly two of the three (all as percents).",
2367
+ {
2368
+ nominalRatePercent: z.number().optional().describe("Nominal (stated) annual rate as a percent"),
2369
+ inflationRatePercent: z.number().optional().describe("Annual inflation rate as a percent"),
2370
+ realRatePercent: z.number().optional().describe("Inflation-adjusted real rate as a percent")
2371
+ },
2372
+ async ({ nominalRatePercent, inflationRatePercent, realRatePercent }) => {
2373
+ const result = _calculateRealRateOfReturn({ nominalRatePercent, inflationRatePercent, realRatePercent });
2374
+ if ("error" in result) return errText(result);
2375
+ return text(JSON.stringify(result, null, 2));
2376
+ }
2377
+ );
2262
2378
  function _computeIsbn10CheckDigitMcp(dataDigits) {
2263
2379
  let sum = 0;
2264
2380
  for (let i = 0; i < 9; i++) sum += dataDigits[i] * (10 - i);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "MCP server exposing 198 Utilix developer tools to Claude, Cursor, VS Code Copilot, and any MCP-compatible AI assistant.",
5
5
  "author": "Utilix <hello@utilix.tech>",
6
6
  "license": "MIT",