@utilix-tech/mcp 0.72.0 → 0.73.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.73.0
6
+ - Add `calculate_mortgage_points` tool: compare a loan with no discount points against the same loan at a reduced rate bought with points, and compute the monthly savings and break-even month.
7
+ - Add `calculate_cookie_size_budget` tool: parse a Cookie header's name=value pairs and compute each cookie's and the total header's byte size against RFC 6265's per-cookie limit and a configurable budget.
8
+
5
9
  ## 0.72.0
6
10
  - Add `validate_vin` tool: validate a 17-character VIN via the ISO 3779 / NHTSA weighted mod-11 check digit and split it into its WMI, VDS, and VIS sections.
7
11
  - Add `validate_isin` tool: validate a 12-character ISIN via its Luhn-style mod-10 check digit and break it into its country code and security identifier.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 239 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 241 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
 
@@ -162,7 +162,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
162
162
  | `convert_timezone` | Convert a date/time between timezones |
163
163
  | `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
164
164
 
165
- ### Units (42 tools)
165
+ ### Units (43 tools)
166
166
  | Tool | Description |
167
167
  |------|-------------|
168
168
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -207,8 +207,9 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
207
207
  | `calculate_loan_refinance` | Compare a current loan's remaining payments against a new refinanced loan on the same outstanding balance, and compute the monthly savings and the break-even month where cumulative savings recoup the closing costs |
208
208
  | `validate_vin` | Validate a 17-character VIN via the ISO 3779 / NHTSA weighted mod-11 check digit and split it into its WMI, VDS, and VIS sections |
209
209
  | `validate_isin` | Validate a 12-character ISIN via its Luhn-style mod-10 check digit and break it into its country code and security identifier |
210
+ | `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 |
210
211
 
211
- ### API / Network (48 tools)
212
+ ### API / Network (49 tools)
212
213
  | Tool | Description |
213
214
  |------|-------------|
214
215
  | `decode_jwt` | Decode a JWT: header, payload, and expiry |
@@ -261,6 +262,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
261
262
  | `list_websocket_opcodes` | List every WebSocket frame opcode (0x0-0xF) |
262
263
  | `build_robots_meta_tag` | Build a `<meta name="robots" content="...">` tag from directives (noindex, nofollow, max-snippet, etc.), the same vocabulary as X-Robots-Tag, optionally scoped to a crawler via the name attribute |
263
264
  | `parse_robots_meta_tag` | Parse an existing robots meta tag (or bare content value) into the same shape |
265
+ | `calculate_cookie_size_budget` | Parse a Cookie header's name=value pairs and compute each cookie's and the total header's byte size against RFC 6265's 4096-byte per-cookie guarantee and a configurable budget |
264
266
 
265
267
  ### Color (4 tools)
266
268
  | Tool | Description |
package/dist/index.js CHANGED
@@ -11505,5 +11505,123 @@ server.tool(
11505
11505
  return text(JSON.stringify(result, null, 2));
11506
11506
  }
11507
11507
  );
11508
+ function _roundMoneyMortgagePointsMcp(n) {
11509
+ return Math.round(n * 100) / 100;
11510
+ }
11511
+ function _calculateLoanForPointsMcp(principal, annualRatePercent, termMonths) {
11512
+ if (!Number.isFinite(principal) || principal <= 0) return { error: "Principal must be a positive number" };
11513
+ if (!Number.isFinite(annualRatePercent) || annualRatePercent < 0) {
11514
+ return { error: "Annual interest rate must be zero or a positive number" };
11515
+ }
11516
+ if (!Number.isInteger(termMonths) || termMonths <= 0) {
11517
+ return { error: "Term must be a positive whole number of months" };
11518
+ }
11519
+ const monthlyRate = annualRatePercent / 100 / 12;
11520
+ let monthlyPayment;
11521
+ if (monthlyRate === 0) {
11522
+ monthlyPayment = principal / termMonths;
11523
+ } else {
11524
+ const factor = Math.pow(1 + monthlyRate, termMonths);
11525
+ monthlyPayment = principal * monthlyRate * factor / (factor - 1);
11526
+ }
11527
+ let balance = _roundMoneyMortgagePointsMcp(principal);
11528
+ let totalPayment = 0;
11529
+ for (let month = 1; month <= termMonths; month++) {
11530
+ const interest = monthlyRate === 0 ? 0 : _roundMoneyMortgagePointsMcp(balance * monthlyRate);
11531
+ let principalPortion = _roundMoneyMortgagePointsMcp(monthlyPayment) - interest;
11532
+ if (month === termMonths || principalPortion > balance) principalPortion = balance;
11533
+ const payment = _roundMoneyMortgagePointsMcp(principalPortion + interest);
11534
+ balance = _roundMoneyMortgagePointsMcp(balance - principalPortion);
11535
+ totalPayment += payment;
11536
+ }
11537
+ return { monthlyPayment: _roundMoneyMortgagePointsMcp(monthlyPayment), totalInterest: _roundMoneyMortgagePointsMcp(totalPayment - principal) };
11538
+ }
11539
+ function _calculateMortgagePointsMcp(loanAmount, baseRatePercent, termMonths, pointsCostPercent, rateReductionPercent) {
11540
+ if (!Number.isFinite(pointsCostPercent) || pointsCostPercent < 0) {
11541
+ return { error: "Points cost must be zero or a positive percent of the loan amount" };
11542
+ }
11543
+ if (!Number.isFinite(rateReductionPercent) || rateReductionPercent < 0) {
11544
+ return { error: "Rate reduction must be zero or a positive number" };
11545
+ }
11546
+ if (rateReductionPercent > baseRatePercent) {
11547
+ return { error: "Rate reduction cannot exceed the base interest rate" };
11548
+ }
11549
+ const withoutPoints = _calculateLoanForPointsMcp(loanAmount, baseRatePercent, termMonths);
11550
+ if ("error" in withoutPoints) return withoutPoints;
11551
+ const withPoints = _calculateLoanForPointsMcp(loanAmount, baseRatePercent - rateReductionPercent, termMonths);
11552
+ if ("error" in withPoints) return withPoints;
11553
+ const pointsCost = _roundMoneyMortgagePointsMcp(loanAmount * (pointsCostPercent / 100));
11554
+ const monthlySavings = _roundMoneyMortgagePointsMcp(withoutPoints.monthlyPayment - withPoints.monthlyPayment);
11555
+ const breakEvenMonths = monthlySavings > 0 ? Math.ceil(pointsCost / monthlySavings) : null;
11556
+ return {
11557
+ pointsCost,
11558
+ monthlyPaymentWithoutPoints: withoutPoints.monthlyPayment,
11559
+ monthlyPaymentWithPoints: withPoints.monthlyPayment,
11560
+ monthlySavings,
11561
+ breakEvenMonths,
11562
+ totalInterestWithoutPoints: withoutPoints.totalInterest,
11563
+ totalInterestWithPoints: withPoints.totalInterest,
11564
+ totalInterestSavings: _roundMoneyMortgagePointsMcp(withoutPoints.totalInterest - withPoints.totalInterest)
11565
+ };
11566
+ }
11567
+ server.tool(
11568
+ "calculate_mortgage_points",
11569
+ "Compare a loan with no discount points against the same loan at a reduced rate bought with points costing a percentage of the loan amount, and compute the monthly savings and the break-even month where cumulative savings recoup the points cost.",
11570
+ {
11571
+ loanAmount: z.number().describe("The loan principal"),
11572
+ baseRatePercent: z.number().describe("The loan's annual interest rate with no points, as a percent, e.g. 6.5"),
11573
+ termMonths: z.number().int().describe("The loan term in months"),
11574
+ pointsCostPercent: z.number().describe("Cost of the discount points, as a percent of the loan amount, e.g. 1 for one point"),
11575
+ rateReductionPercent: z.number().describe("How many percentage points the rate drops when the points are bought, e.g. 0.25")
11576
+ },
11577
+ async ({ loanAmount, baseRatePercent, termMonths, pointsCostPercent, rateReductionPercent }) => {
11578
+ const result = _calculateMortgagePointsMcp(loanAmount, baseRatePercent, termMonths, pointsCostPercent, rateReductionPercent);
11579
+ if ("error" in result) return errText(result);
11580
+ return text(JSON.stringify(result, null, 2));
11581
+ }
11582
+ );
11583
+ var _PER_COOKIE_BYTE_LIMIT_MCP = 4096;
11584
+ var _DEFAULT_COOKIE_BUDGET_BYTES_MCP = 4096;
11585
+ function _calculateCookieSizeBudgetMcp(cookieHeader, budgetBytes = _DEFAULT_COOKIE_BUDGET_BYTES_MCP) {
11586
+ if (typeof cookieHeader !== "string" || !cookieHeader.trim()) {
11587
+ return { error: "A Cookie header value is required" };
11588
+ }
11589
+ if (!Number.isFinite(budgetBytes) || budgetBytes <= 0) {
11590
+ return { error: "Budget must be a positive number of bytes" };
11591
+ }
11592
+ const parts = cookieHeader.split(";").map((p) => p.trim()).filter(Boolean);
11593
+ if (parts.length === 0) return { error: "A Cookie header value is required" };
11594
+ const cookies = [];
11595
+ for (const part of parts) {
11596
+ const eq = part.indexOf("=");
11597
+ if (eq === -1) return { error: `Malformed cookie pair "${part}" is missing '='` };
11598
+ const name = part.slice(0, eq).trim();
11599
+ const value = part.slice(eq + 1).trim();
11600
+ if (!name) return { error: "Cookie name cannot be empty" };
11601
+ const bytes = Buffer.byteLength(`${name}=${value}`, "utf8");
11602
+ cookies.push({ name, value, bytes, exceedsPerCookieLimit: bytes > _PER_COOKIE_BYTE_LIMIT_MCP });
11603
+ }
11604
+ const totalBytes = cookies.reduce((sum, c) => sum + c.bytes, 0) + Math.max(cookies.length - 1, 0) * 2;
11605
+ return {
11606
+ cookies,
11607
+ cookieCount: cookies.length,
11608
+ totalBytes,
11609
+ budgetBytes,
11610
+ exceedsBudget: totalBytes > budgetBytes
11611
+ };
11612
+ }
11613
+ server.tool(
11614
+ "calculate_cookie_size_budget",
11615
+ "Parse a Cookie request header's name=value pairs and compute each cookie's serialized byte size and the total header size, flagging any cookie over RFC 6265's 4096-byte per-cookie guarantee and the total against a configurable budget (4096 bytes by default).",
11616
+ {
11617
+ cookieHeader: z.string().describe('The Cookie header value to parse, e.g. "session=abc123; theme=dark"'),
11618
+ budgetBytes: z.number().optional().describe("Total byte budget to check against; defaults to 4096")
11619
+ },
11620
+ async ({ cookieHeader, budgetBytes }) => {
11621
+ const result = _calculateCookieSizeBudgetMcp(cookieHeader, budgetBytes ?? _DEFAULT_COOKIE_BUDGET_BYTES_MCP);
11622
+ if ("error" in result) return errText(result);
11623
+ return text(JSON.stringify(result, null, 2));
11624
+ }
11625
+ );
11508
11626
  var transport = new StdioServerTransport();
11509
11627
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.72.0",
3
+ "version": "0.73.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",