@utilix-tech/mcp 0.49.0 → 0.51.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,13 @@
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.51.0
6
+ - Add `calculate_moving_averages` tool: compute SMA and EMA series from a list of numbers and a window size.
7
+ - Add `calculate_markdown_task_list_progress` tool: parse a Markdown document's checkbox task list and report completion percentage overall and per section.
8
+
9
+ ## 0.50.0
10
+ - Add `calculate_cash_rounding` tool: round a cashless total to the nearest payable cash denomination ("Swedish rounding"), with round-nearest, round-up, and round-down modes.
11
+
5
12
  ## 0.49.0
6
13
  - Add `resolve_yaml_anchors` tool: expand YAML anchors, aliases, and merge keys inline, and list which anchors were referenced vs. left unused.
7
14
  - Add `split_sitemap` tool: split a URL list exceeding the sitemaps.org 50,000-URL cap into multiple sitemap.xml files plus a sitemap index file.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 188 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 191 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
4
4
 
5
5
  No API key required. All tools run locally. Requires Node.js 18 or later.
6
6
 
@@ -160,7 +160,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
160
160
  | `convert_timezone` | Convert a date/time between timezones |
161
161
  | `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
162
162
 
163
- ### Units (29 tools)
163
+ ### Units (31 tools)
164
164
  | Tool | Description |
165
165
  |------|-------------|
166
166
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -192,6 +192,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
192
192
  | `calculate_payback_period` | Compute the simple and discounted payback period from an initial investment and a series of periodic cash flows |
193
193
  | `calculate_cd_maturity` | Compute a certificate of deposit's maturity value and effective annual yield (APY) from a principal, nominal rate, term, and compounding frequency, plus the net early-withdrawal payout |
194
194
  | `calculate_depreciation_schedule` | Build a year-by-year fixed-asset depreciation schedule from cost, salvage value, and useful life, using straight-line, double-declining-balance, or sum-of-years-digits |
195
+ | `calculate_cash_rounding` | Round a cashless total to the nearest payable cash denomination ("Swedish rounding"), with round-nearest, round-up, and round-down modes |
196
+ | `calculate_moving_averages` | Compute simple moving average (SMA) and exponential moving average (EMA) series from a list of numbers and a window size |
195
197
 
196
198
  ### API / Network (28 tools)
197
199
  | Tool | Description |
@@ -249,7 +251,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
249
251
  | `lint_conventional_commit` | Lint a commit message against the Conventional Commits v1.0.0 spec: header format, blank-line separation, footer tokens, and BREAKING CHANGE casing |
250
252
  | `test_gitignore_patterns` | Test a list of paths against a .gitignore file's patterns, following git's last-match-wins and negation rules |
251
253
 
252
- ### Data (11 tools)
254
+ ### Data (12 tools)
253
255
  | Tool | Description |
254
256
  |------|-------------|
255
257
  | `validate_yaml` | Validate YAML syntax |
@@ -263,6 +265,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
263
265
  | `generate_markdown_toc` | Scan a Markdown document's ATX headings and build a nested table of contents with GitHub-style anchor slugs |
264
266
  | `csv_to_sql_insert` | Convert CSV data with a header row into parameterless SQL INSERT statements, with basic type inference and ANSI/MySQL identifier quoting |
265
267
  | `find_csv_duplicate_rows` | Group CSV/TSV rows that share the same value across a chosen set of key columns, or the entire row |
268
+ | `calculate_markdown_task_list_progress` | Parse a Markdown document's checkbox task list and report completion percentage overall and per section, grouped by the nearest preceding heading |
266
269
 
267
270
  ### CSS (2 tools)
268
271
  | Tool | Description |
package/dist/index.js CHANGED
@@ -8304,5 +8304,141 @@ server.tool(
8304
8304
  return text(JSON.stringify(result, null, 2));
8305
8305
  }
8306
8306
  );
8307
+ function _calculateCashRoundingMcp(total, roundingUnit, mode = "nearest") {
8308
+ const round2 = (n) => Math.round(n * 100) / 100;
8309
+ if (typeof total !== "number" || !Number.isFinite(total)) {
8310
+ return { error: "total must be a finite number" };
8311
+ }
8312
+ if (typeof roundingUnit !== "number" || !Number.isFinite(roundingUnit) || roundingUnit <= 0) {
8313
+ return { error: "roundingUnit must be a finite number greater than zero" };
8314
+ }
8315
+ if (mode !== "nearest" && mode !== "up" && mode !== "down") {
8316
+ return { error: 'mode must be one of: "nearest", "up", "down"' };
8317
+ }
8318
+ const steps = total / roundingUnit;
8319
+ const roundedSteps = mode === "up" ? Math.ceil(steps) : mode === "down" ? Math.floor(steps) : Math.round(steps);
8320
+ const roundedTotal = round2(roundedSteps * roundingUnit);
8321
+ return {
8322
+ total: round2(total),
8323
+ roundingUnit,
8324
+ mode,
8325
+ roundedTotal,
8326
+ adjustment: round2(roundedTotal - total)
8327
+ };
8328
+ }
8329
+ server.tool(
8330
+ "calculate_cash_rounding",
8331
+ 'Round a cashless total to the nearest payable cash denomination, the way a till rounds a card-priced total to the nearest 5 cents (or whatever the smallest circulating coin is) when settling in physical cash, commonly called "Swedish rounding" or "cash rounding".',
8332
+ {
8333
+ total: z.number().describe("The cashless total to round"),
8334
+ roundingUnit: z.number().describe("The smallest payable cash denomination to round to, e.g. 0.05"),
8335
+ mode: z.enum(["nearest", "up", "down"]).optional().describe('Rounding mode (default "nearest")')
8336
+ },
8337
+ async ({ total, roundingUnit, mode }) => {
8338
+ const result = _calculateCashRoundingMcp(total, roundingUnit, mode);
8339
+ if ("error" in result) return errText(result);
8340
+ return text(JSON.stringify(result, null, 2));
8341
+ }
8342
+ );
8343
+ function _calculateMovingAveragesMcp(values, windowSize) {
8344
+ if (!Array.isArray(values) || values.length === 0 || !values.every((v) => typeof v === "number" && Number.isFinite(v))) {
8345
+ return { error: "values must be a non-empty array of finite numbers" };
8346
+ }
8347
+ if (!Number.isInteger(windowSize) || windowSize < 1) {
8348
+ return { error: "windowSize must be a positive integer" };
8349
+ }
8350
+ if (windowSize > values.length) {
8351
+ return { error: "windowSize must be less than or equal to the number of values" };
8352
+ }
8353
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
8354
+ const multiplier = 2 / (windowSize + 1);
8355
+ const points = [];
8356
+ let emaPrev = null;
8357
+ for (let i = 0; i < values.length; i++) {
8358
+ let sma = null;
8359
+ if (i >= windowSize - 1) {
8360
+ let sum = 0;
8361
+ for (let j = i - windowSize + 1; j <= i; j++) sum += values[j];
8362
+ sma = round4(sum / windowSize);
8363
+ }
8364
+ let ema = null;
8365
+ if (i === windowSize - 1) {
8366
+ ema = sma;
8367
+ emaPrev = ema;
8368
+ } else if (i > windowSize - 1 && emaPrev !== null) {
8369
+ ema = round4(values[i] * multiplier + emaPrev * (1 - multiplier));
8370
+ emaPrev = ema;
8371
+ }
8372
+ points.push({ index: i, value: values[i], sma, ema });
8373
+ }
8374
+ const last = points[points.length - 1];
8375
+ return { windowSize, multiplier: round4(multiplier), points, latestSma: last.sma, latestEma: last.ema };
8376
+ }
8377
+ server.tool(
8378
+ "calculate_moving_averages",
8379
+ "Compute simple moving average (SMA) and exponential moving average (EMA) series from a list of numbers and a window size, a foundational technical-indicator building block for price/volume data. No live market-data feed is involved: this operates only on numbers you supply.",
8380
+ {
8381
+ values: z.array(z.number()).min(1).describe("A non-empty series of finite numbers, e.g. daily closing prices"),
8382
+ windowSize: z.number().int().min(1).describe("The trailing window size for both SMA and EMA, must not exceed values.length")
8383
+ },
8384
+ async ({ values, windowSize }) => {
8385
+ const result = _calculateMovingAveragesMcp(values, windowSize);
8386
+ if ("error" in result) return errText(result);
8387
+ return text(JSON.stringify(result, null, 2));
8388
+ }
8389
+ );
8390
+ function _calculateMarkdownTaskListProgressMcp(markdown) {
8391
+ const round2 = (n) => Math.round(n * 100) / 100;
8392
+ const headingRe = /^(#{1,6})\s+(.*)$/;
8393
+ const itemRe = /^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/;
8394
+ let currentSection = "(no heading)";
8395
+ const items = [];
8396
+ for (const line of markdown.replace(/\r\n/g, "\n").split("\n")) {
8397
+ const headingMatch = line.match(headingRe);
8398
+ if (headingMatch) {
8399
+ currentSection = headingMatch[2].trim();
8400
+ continue;
8401
+ }
8402
+ const itemMatch = line.match(itemRe);
8403
+ if (itemMatch) {
8404
+ items.push({
8405
+ text: itemMatch[3].trim(),
8406
+ checked: itemMatch[2].toLowerCase() === "x",
8407
+ indent: itemMatch[1].length,
8408
+ section: currentSection
8409
+ });
8410
+ }
8411
+ }
8412
+ const totalItems = items.length;
8413
+ const completedItems = items.filter((i) => i.checked).length;
8414
+ const percentComplete = totalItems === 0 ? 0 : round2(completedItems / totalItems * 100);
8415
+ const sectionOrder = [];
8416
+ const sectionTotals = /* @__PURE__ */ new Map();
8417
+ for (const item of items) {
8418
+ if (!sectionTotals.has(item.section)) {
8419
+ sectionTotals.set(item.section, { total: 0, completed: 0 });
8420
+ sectionOrder.push(item.section);
8421
+ }
8422
+ const s = sectionTotals.get(item.section);
8423
+ s.total++;
8424
+ if (item.checked) s.completed++;
8425
+ }
8426
+ const sections = sectionOrder.map((section) => {
8427
+ const s = sectionTotals.get(section);
8428
+ return { section, total: s.total, completed: s.completed, percentComplete: s.total === 0 ? 0 : round2(s.completed / s.total * 100) };
8429
+ });
8430
+ return { totalItems, completedItems, percentComplete, sections, items };
8431
+ }
8432
+ server.tool(
8433
+ "calculate_markdown_task_list_progress",
8434
+ "Parse a Markdown document's `- [ ]`/`- [x]` task list checkboxes and report completion percentage overall and per section, where a section is the nearest preceding ATX heading of any level.",
8435
+ {
8436
+ markdown: z.string().describe("The Markdown document to scan for task list checkboxes")
8437
+ },
8438
+ async ({ markdown }) => {
8439
+ const result = _calculateMarkdownTaskListProgressMcp(markdown);
8440
+ return text(JSON.stringify(result, null, 2));
8441
+ }
8442
+ );
8307
8443
  var transport = new StdioServerTransport();
8308
8444
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "description": "MCP server exposing 188 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",