@utilix-tech/mcp 0.14.0 → 0.15.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,9 @@
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.15.0
6
+ - Added Compound Interest Calculator (`calculate_compound_interest`) and ISBN Validator / Converter (`validate_isbn`).
7
+
5
8
  ## 0.14.0
6
9
  - Added VAT / Sales Tax Calculator (`calculate_tax`).
7
10
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 114 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 116 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
 
@@ -145,7 +145,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
145
145
  | `date_diff` | Calculate the difference between two dates |
146
146
  | `convert_timezone` | Convert a date/time between timezones |
147
147
 
148
- ### Units (7 tools)
148
+ ### Units (9 tools)
149
149
  | Tool | Description |
150
150
  |------|-------------|
151
151
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -155,6 +155,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
155
155
  | `calculate_loan` | Compute a loan's monthly payment, total interest, and amortization schedule |
156
156
  | `validate_barcode` | Validate an EAN-13, EAN-8, or UPC-A barcode via its weighted mod-10 check digit |
157
157
  | `calculate_tax` | Add or remove VAT/sales tax from a price, in either direction |
158
+ | `calculate_compound_interest` | Compute the future value of a principal plus an optional recurring contribution under compound interest |
159
+ | `validate_isbn` | Validate an ISBN-10 or ISBN-13 checksum and convert it to the other format where possible |
158
160
 
159
161
  ### API / Network (8 tools)
160
162
  | Tool | Description |
package/dist/index.js CHANGED
@@ -1988,6 +1988,119 @@ server.tool(
1988
1988
  return text(JSON.stringify(result, null, 2));
1989
1989
  }
1990
1990
  );
1991
+ function _calculateCompoundInterest(principal, annualRatePercent, years, compoundsPerYear, contributionPerPeriod) {
1992
+ if (!Number.isFinite(principal) || principal < 0) return { error: "Principal must be zero or a positive number" };
1993
+ if (!Number.isFinite(annualRatePercent) || annualRatePercent < 0) {
1994
+ return { error: "Annual interest rate must be zero or a positive number" };
1995
+ }
1996
+ if (!Number.isInteger(years) || years <= 0) return { error: "Years must be a positive whole number" };
1997
+ if (!Number.isInteger(compoundsPerYear) || compoundsPerYear <= 0) {
1998
+ return { error: "Compounding frequency must be a positive whole number of periods per year" };
1999
+ }
2000
+ if (!Number.isFinite(contributionPerPeriod) || contributionPerPeriod < 0) {
2001
+ return { error: "Contribution per period must be zero or a positive number" };
2002
+ }
2003
+ if (principal === 0 && contributionPerPeriod === 0) return { error: "Enter a principal or a recurring contribution" };
2004
+ const ratePerPeriod = annualRatePercent / 100 / compoundsPerYear;
2005
+ function futureValueAtPeriod(periods) {
2006
+ const growth = ratePerPeriod === 0 ? 1 : Math.pow(1 + ratePerPeriod, periods);
2007
+ const fvPrincipal = principal * growth;
2008
+ const fvContributions = contributionPerPeriod === 0 ? 0 : ratePerPeriod === 0 ? contributionPerPeriod * periods : contributionPerPeriod * ((growth - 1) / ratePerPeriod);
2009
+ return fvPrincipal + fvContributions;
2010
+ }
2011
+ const schedule = [];
2012
+ for (let year = 1; year <= years; year++) {
2013
+ const periods = year * compoundsPerYear;
2014
+ const balance = _roundMoneyMcp(futureValueAtPeriod(periods));
2015
+ const contributions = _roundMoneyMcp(contributionPerPeriod * periods);
2016
+ schedule.push({ year, balance, contributions, interest: _roundMoneyMcp(balance - principal - contributions) });
2017
+ }
2018
+ const totalPeriods = years * compoundsPerYear;
2019
+ const futureValue = _roundMoneyMcp(futureValueAtPeriod(totalPeriods));
2020
+ const totalContributions = _roundMoneyMcp(contributionPerPeriod * totalPeriods);
2021
+ const totalInterest = _roundMoneyMcp(futureValue - principal - totalContributions);
2022
+ return {
2023
+ futureValue,
2024
+ totalPrincipal: _roundMoneyMcp(principal),
2025
+ totalContributions,
2026
+ totalInterest,
2027
+ years,
2028
+ compoundsPerYear,
2029
+ schedule
2030
+ };
2031
+ }
2032
+ server.tool(
2033
+ "calculate_compound_interest",
2034
+ "Compute the future value of a principal plus an optional recurring contribution under compound interest, given an annual rate (as a percent), a term in years, and a compounding frequency (periods per year), plus a year-by-year balance breakdown.",
2035
+ {
2036
+ principal: z.number().describe("Starting principal amount"),
2037
+ annualRatePercent: z.number().describe("Annual interest rate as a percent, e.g. 5"),
2038
+ years: z.number().int().describe("Term in years"),
2039
+ compoundsPerYear: z.number().int().describe("Compounding periods per year, e.g. 1 = annually, 12 = monthly, 365 = daily"),
2040
+ contributionPerPeriod: z.number().default(0).describe("Optional recurring contribution made at the end of each compounding period")
2041
+ },
2042
+ async ({ principal, annualRatePercent, years, compoundsPerYear, contributionPerPeriod }) => {
2043
+ const result = _calculateCompoundInterest(principal, annualRatePercent, years, compoundsPerYear, contributionPerPeriod);
2044
+ if ("error" in result) return errText(result);
2045
+ return text(JSON.stringify(result, null, 2));
2046
+ }
2047
+ );
2048
+ function _computeIsbn10CheckDigitMcp(dataDigits) {
2049
+ let sum = 0;
2050
+ for (let i = 0; i < 9; i++) sum += dataDigits[i] * (10 - i);
2051
+ const check = (11 - sum % 11) % 11;
2052
+ return check === 10 ? "X" : String(check);
2053
+ }
2054
+ function _computeIsbn13CheckDigitMcp(dataDigits) {
2055
+ let sum = 0;
2056
+ const n = dataDigits.length;
2057
+ for (let i = 0; i < n; i++) {
2058
+ const distanceFromEnd = n - 1 - i;
2059
+ const weight = distanceFromEnd % 2 === 0 ? 3 : 1;
2060
+ sum += dataDigits[i] * weight;
2061
+ }
2062
+ return String((10 - sum % 10) % 10);
2063
+ }
2064
+ function _validateIsbn(input) {
2065
+ if (!input || !input.trim()) return { error: "Input is empty" };
2066
+ const cleaned = input.replace(/[\s-]/g, "").toUpperCase();
2067
+ if (cleaned.length === 10) {
2068
+ if (!/^\d{9}[\dX]$/.test(cleaned)) {
2069
+ return { error: 'ISBN-10 must be 9 digits followed by a digit or "X" check digit' };
2070
+ }
2071
+ const dataDigits = cleaned.slice(0, 9).split("").map(Number);
2072
+ const checkDigit = cleaned[9];
2073
+ const expectedCheckDigit = _computeIsbn10CheckDigitMcp(dataDigits);
2074
+ const valid = checkDigit === expectedCheckDigit;
2075
+ const isbn13Digits = [9, 7, 8, ...dataDigits];
2076
+ const converted = valid ? isbn13Digits.join("") + _computeIsbn13CheckDigitMcp(isbn13Digits) : null;
2077
+ return { valid, format: "ISBN-10", digits: cleaned, checkDigit, expectedCheckDigit, converted };
2078
+ }
2079
+ if (cleaned.length === 13) {
2080
+ if (!/^\d{13}$/.test(cleaned)) return { error: "ISBN-13 must contain only digits" };
2081
+ const dataDigits = cleaned.slice(0, 12).split("").map(Number);
2082
+ const checkDigit = cleaned[12];
2083
+ const expectedCheckDigit = _computeIsbn13CheckDigitMcp(dataDigits);
2084
+ const valid = checkDigit === expectedCheckDigit;
2085
+ let converted = null;
2086
+ if (valid && cleaned.startsWith("978")) {
2087
+ const isbn10Digits = dataDigits.slice(3);
2088
+ converted = isbn10Digits.join("") + _computeIsbn10CheckDigitMcp(isbn10Digits);
2089
+ }
2090
+ return { valid, format: "ISBN-13", digits: cleaned, checkDigit, expectedCheckDigit, converted };
2091
+ }
2092
+ return { error: `Unsupported length (${cleaned.length} characters); expected 10 (ISBN-10) or 13 (ISBN-13)` };
2093
+ }
2094
+ server.tool(
2095
+ "validate_isbn",
2096
+ "Validate an ISBN-10 or ISBN-13 number via its checksum (inferred from the character count) and convert it to the other format where possible.",
2097
+ { isbn: z.string().describe("The ISBN-10 or ISBN-13, with or without hyphens/spaces") },
2098
+ async ({ isbn }) => {
2099
+ const result = _validateIsbn(isbn);
2100
+ if ("error" in result) return errText(result);
2101
+ return text(JSON.stringify(result, null, 2));
2102
+ }
2103
+ );
1991
2104
  server.tool(
1992
2105
  "format_ndjson",
1993
2106
  "Validate newline-delimited JSON (NDJSON/JSON Lines), report per-line errors, and pretty-print each valid line.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.14.0",
4
- "description": "MCP server exposing 114 Utilix developer tools to Claude, Cursor, VS Code Copilot, and any MCP-compatible AI assistant.",
3
+ "version": "0.15.0",
4
+ "description": "MCP server exposing 116 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",
7
7
  "homepage": "https://utilix.tech/mcp",