@utilix-tech/mcp 0.31.0 → 0.32.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 +4 -0
- package/README.md +5 -3
- package/dist/index.js +139 -0
- package/package.json +1 -1
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.32.0
|
|
6
|
+
- Add `calculate_emi` tool: equated monthly installment, total interest, and a year-by-year principal/interest breakdown from principal, annual rate, and tenure in years or months.
|
|
7
|
+
- Add `parse_markdown_front_matter` tool: split a Markdown document into its YAML front matter fields and body content, parsing scalars, inline arrays, and block lists.
|
|
8
|
+
|
|
5
9
|
## 0.31.0
|
|
6
10
|
- Add `json_schema_to_typescript` tool: convert a JSON Schema document into TypeScript interfaces/types, reading `required`/`enum`/`oneOf`/`anyOf`/`allOf`/`$ref` directly instead of inferring from an example payload.
|
|
7
11
|
- Add `estimate_password_entropy` tool: bits of entropy from a password's character pool, a strength label, and crack-time estimates across four guess-rate scenarios.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 150 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
|
|
|
@@ -155,7 +155,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
155
155
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
156
156
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
157
157
|
|
|
158
|
-
### Units (
|
|
158
|
+
### Units (21 tools)
|
|
159
159
|
| Tool | Description |
|
|
160
160
|
|------|-------------|
|
|
161
161
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -178,6 +178,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
178
178
|
| `calculate_break_even` | Compute the break-even unit count and revenue from fixed costs, variable cost per unit, and price per unit |
|
|
179
179
|
| `calculate_video_bitrate` | Calculate a video's file size from a bitrate and duration, or the bitrate needed to hit a target file size |
|
|
180
180
|
| `calculate_rule_of_72` | Estimate years to double an investment from a rate, or the rate needed to double in a given number of years, via the Rule of 72 shortcut and the exact logarithmic result |
|
|
181
|
+
| `calculate_emi` | Compute the Equated Monthly Installment for a loan from principal, annual rate, and tenure in years or months, with a year-by-year principal/interest breakdown |
|
|
181
182
|
|
|
182
183
|
### API / Network (12 tools)
|
|
183
184
|
| Tool | Description |
|
|
@@ -217,7 +218,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
217
218
|
| `parse_github_url` | Parse a github.com URL (repo, pull request, issue, commit, file, or release) into structured components |
|
|
218
219
|
| `detect_redos` | Statically flag catastrophic-backtracking (ReDoS) risk in a regex: nested quantifiers, overlapping alternation, adjacent overlapping quantifiers |
|
|
219
220
|
|
|
220
|
-
### Data (
|
|
221
|
+
### Data (7 tools)
|
|
221
222
|
| Tool | Description |
|
|
222
223
|
|------|-------------|
|
|
223
224
|
| `validate_yaml` | Validate YAML syntax |
|
|
@@ -226,6 +227,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
226
227
|
| `parse_csv` | Parse a CSV string and return the data as JSON |
|
|
227
228
|
| `format_ndjson` | Validate and pretty-print newline-delimited JSON (NDJSON) |
|
|
228
229
|
| `diff_env_example` | Diff a .env file against its .env.example template: flags keys missing from either side, plus empty placeholder values |
|
|
230
|
+
| `parse_markdown_front_matter` | Split a Markdown document into its YAML front matter fields and body content |
|
|
229
231
|
|
|
230
232
|
### CSS (2 tools)
|
|
231
233
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -5407,5 +5407,144 @@ server.tool(
|
|
|
5407
5407
|
return text(JSON.stringify(result, null, 2));
|
|
5408
5408
|
}
|
|
5409
5409
|
);
|
|
5410
|
+
function _calculateEmiMcp(principal, annualRatePercent, tenure, tenureUnit) {
|
|
5411
|
+
if (!Number.isFinite(principal) || principal <= 0) return { error: "Principal must be a positive number" };
|
|
5412
|
+
if (!Number.isFinite(annualRatePercent) || annualRatePercent < 0) {
|
|
5413
|
+
return { error: "Annual interest rate must be zero or a positive number" };
|
|
5414
|
+
}
|
|
5415
|
+
if (tenureUnit !== "years" && tenureUnit !== "months") {
|
|
5416
|
+
return { error: 'tenureUnit must be "years" or "months"' };
|
|
5417
|
+
}
|
|
5418
|
+
if (!Number.isFinite(tenure) || tenure <= 0) return { error: "Tenure must be a positive number" };
|
|
5419
|
+
const tenureMonths = Math.round(tenureUnit === "years" ? tenure * 12 : tenure);
|
|
5420
|
+
if (tenureMonths <= 0) return { error: "Tenure must resolve to at least 1 month" };
|
|
5421
|
+
const monthlyRate = annualRatePercent / 100 / 12;
|
|
5422
|
+
let emi;
|
|
5423
|
+
if (monthlyRate === 0) {
|
|
5424
|
+
emi = principal / tenureMonths;
|
|
5425
|
+
} else {
|
|
5426
|
+
const factor = Math.pow(1 + monthlyRate, tenureMonths);
|
|
5427
|
+
emi = principal * monthlyRate * factor / (factor - 1);
|
|
5428
|
+
}
|
|
5429
|
+
const yearlyBreakdown = [];
|
|
5430
|
+
let balance = _roundMoneyMcp(principal);
|
|
5431
|
+
let totalPayment = 0;
|
|
5432
|
+
let yearPrincipal = 0;
|
|
5433
|
+
let yearInterest = 0;
|
|
5434
|
+
let year = 1;
|
|
5435
|
+
for (let month = 1; month <= tenureMonths; month++) {
|
|
5436
|
+
const interest = monthlyRate === 0 ? 0 : _roundMoneyMcp(balance * monthlyRate);
|
|
5437
|
+
let principalPortion = _roundMoneyMcp(emi) - interest;
|
|
5438
|
+
if (month === tenureMonths || principalPortion > balance) principalPortion = balance;
|
|
5439
|
+
const payment = _roundMoneyMcp(principalPortion + interest);
|
|
5440
|
+
balance = Math.max(_roundMoneyMcp(balance - principalPortion), 0);
|
|
5441
|
+
totalPayment += payment;
|
|
5442
|
+
yearPrincipal += principalPortion;
|
|
5443
|
+
yearInterest += interest;
|
|
5444
|
+
const isYearEnd = month % 12 === 0 || month === tenureMonths;
|
|
5445
|
+
if (isYearEnd) {
|
|
5446
|
+
yearlyBreakdown.push({ year, principalPaid: _roundMoneyMcp(yearPrincipal), interestPaid: _roundMoneyMcp(yearInterest), balance });
|
|
5447
|
+
year += 1;
|
|
5448
|
+
yearPrincipal = 0;
|
|
5449
|
+
yearInterest = 0;
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5452
|
+
return {
|
|
5453
|
+
emi: _roundMoneyMcp(emi),
|
|
5454
|
+
totalInterest: _roundMoneyMcp(totalPayment - principal),
|
|
5455
|
+
totalPayment: _roundMoneyMcp(totalPayment),
|
|
5456
|
+
tenureMonths,
|
|
5457
|
+
yearlyBreakdown
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
server.tool(
|
|
5461
|
+
"calculate_emi",
|
|
5462
|
+
"Compute the Equated Monthly Installment (EMI) for a loan given principal, annual interest rate (as a percent), and tenure in years or months. Same reducing-balance math as calculate_loan, but reports a year-by-year principal/interest breakdown instead of a full monthly schedule.",
|
|
5463
|
+
{
|
|
5464
|
+
principal: z.number().describe("Loan principal amount"),
|
|
5465
|
+
annualRatePercent: z.number().describe("Annual interest rate as a percent, e.g. 8.5"),
|
|
5466
|
+
tenure: z.number().describe("Loan tenure, in the unit given by tenureUnit"),
|
|
5467
|
+
tenureUnit: z.enum(["years", "months"]).optional().describe('Unit for the tenure field, defaults to "years"')
|
|
5468
|
+
},
|
|
5469
|
+
async ({ principal, annualRatePercent, tenure, tenureUnit }) => {
|
|
5470
|
+
const result = _calculateEmiMcp(principal, annualRatePercent, tenure, tenureUnit ?? "years");
|
|
5471
|
+
if ("error" in result) return errText(result);
|
|
5472
|
+
return text(JSON.stringify(result, null, 2));
|
|
5473
|
+
}
|
|
5474
|
+
);
|
|
5475
|
+
function _parseFrontMatterScalarMcp(value) {
|
|
5476
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
5477
|
+
return value.slice(1, -1);
|
|
5478
|
+
}
|
|
5479
|
+
if (value === "true") return true;
|
|
5480
|
+
if (value === "false") return false;
|
|
5481
|
+
if (value === "null" || value === "~") return null;
|
|
5482
|
+
if (value !== "" && !Number.isNaN(Number(value))) return Number(value);
|
|
5483
|
+
return value;
|
|
5484
|
+
}
|
|
5485
|
+
function _parseFrontMatterMcp(input) {
|
|
5486
|
+
if (typeof input !== "string" || input.length === 0) return { error: "input is required" };
|
|
5487
|
+
const normalized = input.replace(/\r\n/g, "\n");
|
|
5488
|
+
if (!normalized.startsWith("---\n") && normalized !== "---") {
|
|
5489
|
+
return { frontMatter: {}, body: input, hasFrontMatter: false, raw: "" };
|
|
5490
|
+
}
|
|
5491
|
+
const closingIdx = normalized.indexOf("\n---", 4);
|
|
5492
|
+
if (closingIdx === -1) {
|
|
5493
|
+
return { error: 'Front matter opening delimiter found but no closing "---" delimiter' };
|
|
5494
|
+
}
|
|
5495
|
+
const raw = normalized.slice(4, closingIdx);
|
|
5496
|
+
let afterClose = normalized.slice(closingIdx + 4);
|
|
5497
|
+
if (afterClose.startsWith("\n")) afterClose = afterClose.slice(1);
|
|
5498
|
+
const frontMatter = {};
|
|
5499
|
+
const rawLines = raw.split("\n");
|
|
5500
|
+
let i = 0;
|
|
5501
|
+
while (i < rawLines.length) {
|
|
5502
|
+
const line = rawLines[i];
|
|
5503
|
+
if (line.trim() === "" || line.trim().startsWith("#")) {
|
|
5504
|
+
i++;
|
|
5505
|
+
continue;
|
|
5506
|
+
}
|
|
5507
|
+
const colonIdx = line.indexOf(":");
|
|
5508
|
+
if (colonIdx === -1) return { error: `Invalid front matter line ${i + 1}: expected "key: value"` };
|
|
5509
|
+
const key = line.slice(0, colonIdx).trim();
|
|
5510
|
+
if (!key) return { error: `Invalid front matter line ${i + 1}: empty key` };
|
|
5511
|
+
const rest = line.slice(colonIdx + 1).trim();
|
|
5512
|
+
if (rest === "") {
|
|
5513
|
+
const listItems = [];
|
|
5514
|
+
let j = i + 1;
|
|
5515
|
+
while (j < rawLines.length && /^\s*-\s+/.test(rawLines[j])) {
|
|
5516
|
+
listItems.push(rawLines[j].replace(/^\s*-\s+/, "").trim());
|
|
5517
|
+
j++;
|
|
5518
|
+
}
|
|
5519
|
+
if (listItems.length > 0) {
|
|
5520
|
+
frontMatter[key] = listItems.map(_parseFrontMatterScalarMcp);
|
|
5521
|
+
i = j;
|
|
5522
|
+
continue;
|
|
5523
|
+
}
|
|
5524
|
+
frontMatter[key] = null;
|
|
5525
|
+
i++;
|
|
5526
|
+
continue;
|
|
5527
|
+
}
|
|
5528
|
+
if (rest.startsWith("[") && rest.endsWith("]")) {
|
|
5529
|
+
const inner = rest.slice(1, -1).trim();
|
|
5530
|
+
frontMatter[key] = inner === "" ? [] : inner.split(",").map((s) => _parseFrontMatterScalarMcp(s.trim()));
|
|
5531
|
+
i++;
|
|
5532
|
+
continue;
|
|
5533
|
+
}
|
|
5534
|
+
frontMatter[key] = _parseFrontMatterScalarMcp(rest);
|
|
5535
|
+
i++;
|
|
5536
|
+
}
|
|
5537
|
+
return { frontMatter, body: afterClose, hasFrontMatter: true, raw };
|
|
5538
|
+
}
|
|
5539
|
+
server.tool(
|
|
5540
|
+
"parse_markdown_front_matter",
|
|
5541
|
+
"Split a Markdown document into its YAML front matter (delimited by --- lines at the top) and body content, parsing simple scalar, list, and inline-array front matter values. Not a full YAML parser: nested mappings and multi-line block scalars are not supported.",
|
|
5542
|
+
{ input: z.string().describe("The Markdown document contents") },
|
|
5543
|
+
async ({ input }) => {
|
|
5544
|
+
const result = _parseFrontMatterMcp(input);
|
|
5545
|
+
if ("error" in result) return errText(result);
|
|
5546
|
+
return text(JSON.stringify(result, null, 2));
|
|
5547
|
+
}
|
|
5548
|
+
);
|
|
5410
5549
|
var transport = new StdioServerTransport();
|
|
5411
5550
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilix-tech/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "MCP server exposing 143 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",
|