@utilix-tech/mcp 0.50.0 → 0.52.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.52.0
6
+ - Add `calculate_gpa` tool: compute a weighted GPA from a list of courses, each with a letter grade and credit hours, using the standard 4.0-capped scale.
7
+
8
+ ## 0.51.0
9
+ - Add `calculate_moving_averages` tool: compute SMA and EMA series from a list of numbers and a window size.
10
+ - Add `calculate_markdown_task_list_progress` tool: parse a Markdown document's checkbox task list and report completion percentage overall and per section.
11
+
5
12
  ## 0.50.0
6
13
  - 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.
7
14
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 189 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 192 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 (30 tools)
163
+ ### Units (32 tools)
164
164
  | Tool | Description |
165
165
  |------|-------------|
166
166
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -193,6 +193,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
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
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 |
197
+ | `calculate_gpa` | Compute a weighted GPA from a list of courses, each with a letter grade and credit hours, using the standard 4.0-capped scale |
196
198
 
197
199
  ### API / Network (28 tools)
198
200
  | Tool | Description |
@@ -250,7 +252,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
250
252
  | `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 |
251
253
  | `test_gitignore_patterns` | Test a list of paths against a .gitignore file's patterns, following git's last-match-wins and negation rules |
252
254
 
253
- ### Data (11 tools)
255
+ ### Data (12 tools)
254
256
  | Tool | Description |
255
257
  |------|-------------|
256
258
  | `validate_yaml` | Validate YAML syntax |
@@ -264,6 +266,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
264
266
  | `generate_markdown_toc` | Scan a Markdown document's ATX headings and build a nested table of contents with GitHub-style anchor slugs |
265
267
  | `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 |
266
268
  | `find_csv_duplicate_rows` | Group CSV/TSV rows that share the same value across a chosen set of key columns, or the entire row |
269
+ | `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 |
267
270
 
268
271
  ### CSS (2 tools)
269
272
  | Tool | Description |
package/dist/index.js CHANGED
@@ -8340,5 +8340,171 @@ server.tool(
8340
8340
  return text(JSON.stringify(result, null, 2));
8341
8341
  }
8342
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
+ );
8443
+ var GPA_GRADE_POINTS_MCP = {
8444
+ "A+": 4,
8445
+ A: 4,
8446
+ "A-": 3.7,
8447
+ "B+": 3.3,
8448
+ B: 3,
8449
+ "B-": 2.7,
8450
+ "C+": 2.3,
8451
+ C: 2,
8452
+ "C-": 1.7,
8453
+ "D+": 1.3,
8454
+ D: 1,
8455
+ "D-": 0.7,
8456
+ F: 0
8457
+ };
8458
+ function _calculateGpaMcp(courses) {
8459
+ if (!Array.isArray(courses) || courses.length === 0) {
8460
+ return { error: "courses must be a non-empty array" };
8461
+ }
8462
+ const round2 = (n) => Math.round(n * 100) / 100;
8463
+ let totalCredits = 0;
8464
+ let totalQualityPoints = 0;
8465
+ const results = [];
8466
+ for (let i = 0; i < courses.length; i++) {
8467
+ const course = courses[i];
8468
+ if (typeof course.credits !== "number" || !Number.isFinite(course.credits) || course.credits <= 0) {
8469
+ return { error: `course ${i + 1}: credits must be a finite number greater than zero` };
8470
+ }
8471
+ const gradeKey = typeof course.grade === "string" ? course.grade.trim().toUpperCase() : "";
8472
+ const points = GPA_GRADE_POINTS_MCP[gradeKey];
8473
+ if (points === void 0) {
8474
+ return { error: `course ${i + 1}: unrecognized grade "${course.grade}" (expected one of ${Object.keys(GPA_GRADE_POINTS_MCP).join(", ")})` };
8475
+ }
8476
+ const qualityPoints = round2(points * course.credits);
8477
+ totalCredits += course.credits;
8478
+ totalQualityPoints += qualityPoints;
8479
+ results.push({
8480
+ name: course.name?.trim() || `Course ${i + 1}`,
8481
+ grade: gradeKey,
8482
+ credits: course.credits,
8483
+ qualityPoints
8484
+ });
8485
+ }
8486
+ return {
8487
+ courses: results,
8488
+ totalCredits: round2(totalCredits),
8489
+ totalQualityPoints: round2(totalQualityPoints),
8490
+ gpa: round2(totalQualityPoints / totalCredits)
8491
+ };
8492
+ }
8493
+ server.tool(
8494
+ "calculate_gpa",
8495
+ "Compute a weighted GPA from a list of courses, each with a letter grade and credit hours, using the standard 4.0-capped scale (A+ counts the same as A).",
8496
+ {
8497
+ courses: z.array(z.object({
8498
+ name: z.string().optional().describe("Optional course name/label"),
8499
+ grade: z.string().describe('Letter grade, e.g. "A", "B+", "C-"'),
8500
+ credits: z.number().positive().describe("Credit hours for this course, must be greater than zero")
8501
+ })).min(1).describe("A non-empty list of courses to average")
8502
+ },
8503
+ async ({ courses }) => {
8504
+ const result = _calculateGpaMcp(courses);
8505
+ if ("error" in result) return errText(result);
8506
+ return text(JSON.stringify(result, null, 2));
8507
+ }
8508
+ );
8343
8509
  var transport = new StdioServerTransport();
8344
8510
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.50.0",
3
+ "version": "0.52.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",