@utilix-tech/mcp 0.62.0 → 0.63.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 +219 -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.63.0
|
|
6
|
+
- Add `plan_debt_payoff` tool: simulate paying off a list of debts under both the snowball (smallest balance first) and avalanche (highest rate first) strategies given a fixed extra monthly payment, and compare months to payoff and total interest.
|
|
7
|
+
- Add `audit_dependency_ranges` tool: classify each dependency's version specifier range type and flag wildcard ranges, the "latest" tag, git/URL/file/link dependencies, and packages listed with conflicting or redundant ranges across sections.
|
|
8
|
+
|
|
5
9
|
## 0.62.0
|
|
6
10
|
- Add `build_priority_header`, `parse_priority_header` tools: build an HTTP Priority request/response-header value from an urgency (0-7) and/or an incremental flag per RFC 9218, or parse an existing one into the same shape.
|
|
7
11
|
- Add `lookup_close_code`, `list_close_codes` tools: look up what a WebSocket close code means per RFC 6455 and the IANA reserved ranges, or list every well-known code.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 218 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 (
|
|
165
|
+
### Units (38 tools)
|
|
166
166
|
| Tool | Description |
|
|
167
167
|
|------|-------------|
|
|
168
168
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -202,6 +202,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
202
202
|
| `calculate_blended_loan_rate` | Compute the single weighted-average interest rate across a list of loan balances, weighted by each loan's share of the combined balance, plus combined monthly and annual interest accrual |
|
|
203
203
|
| `calculate_freelancer_rate` | Solve for the minimum hourly rate a freelancer must charge to hit a target annual income, given billable hours, weeks worked, overhead, and a profit margin |
|
|
204
204
|
| `calculate_extra_payment_payoff` | Compute how much faster a fixed-rate loan pays off, and how much interest is saved, when a fixed extra amount is added to every monthly payment |
|
|
205
|
+
| `plan_debt_payoff` | Simulate paying off a list of debts under both the snowball (smallest balance first) and avalanche (highest rate first) strategies given a fixed extra monthly payment, and compare months to payoff and total interest |
|
|
205
206
|
|
|
206
207
|
### API / Network (37 tools)
|
|
207
208
|
| Tool | Description |
|
|
@@ -254,7 +255,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
254
255
|
| `color_palette` | Generate a color palette from a base color |
|
|
255
256
|
| `color_shades` | Generate shades or tints from a base color |
|
|
256
257
|
|
|
257
|
-
### Code (
|
|
258
|
+
### Code (16 tools)
|
|
258
259
|
| Tool | Description |
|
|
259
260
|
|------|-------------|
|
|
260
261
|
| `format_sql` | Format a SQL query with proper indentation |
|
|
@@ -272,6 +273,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
272
273
|
| `validate_editorconfig` | Validate an .editorconfig file: root flag, glob sections, and structural mistakes against the EditorConfig spec |
|
|
273
274
|
| `validate_tsconfig` | Validate a tsconfig.json file (JSONC-aware): unknown keys, unknown compilerOptions, and type mismatches |
|
|
274
275
|
| `validate_package_exports` | Validate a package.json "exports" field: dual-package (ESM/CJS) misconfigurations like a missing default fallback or a require/import pointing at the wrong extension |
|
|
276
|
+
| `audit_dependency_ranges` | Classify each package.json dependency's version range type and flag wildcard ranges, the "latest" tag, git/URL/file/link dependencies, and packages listed with conflicting or redundant ranges across sections |
|
|
275
277
|
|
|
276
278
|
### Data (14 tools)
|
|
277
279
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -9968,5 +9968,224 @@ server.tool(
|
|
|
9968
9968
|
return text(JSON.stringify({ codes }, null, 2));
|
|
9969
9969
|
}
|
|
9970
9970
|
);
|
|
9971
|
+
var _DEBT_PAYOFF_MAX_MONTHS_MCP = 1200;
|
|
9972
|
+
function _simulateDebtPayoffMcp(debts, order, extraMonthlyPayment) {
|
|
9973
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
9974
|
+
const balances = debts.map((d) => d.balance);
|
|
9975
|
+
const totalMinPayment = debts.reduce((sum, d) => sum + d.minPayment, 0);
|
|
9976
|
+
const totalBudget = totalMinPayment + extraMonthlyPayment;
|
|
9977
|
+
let totalInterestPaid = 0;
|
|
9978
|
+
let totalPaid = 0;
|
|
9979
|
+
const schedule = [];
|
|
9980
|
+
let month = 0;
|
|
9981
|
+
while (balances.some((b) => b > 5e-3) && month < _DEBT_PAYOFF_MAX_MONTHS_MCP) {
|
|
9982
|
+
month++;
|
|
9983
|
+
for (let i = 0; i < balances.length; i++) {
|
|
9984
|
+
if (balances[i] <= 0) continue;
|
|
9985
|
+
const interest = balances[i] * debts[i].ratePercent / 100 / 12;
|
|
9986
|
+
balances[i] += interest;
|
|
9987
|
+
totalInterestPaid += interest;
|
|
9988
|
+
}
|
|
9989
|
+
let remainingBudget = totalBudget;
|
|
9990
|
+
for (let i = 0; i < balances.length; i++) {
|
|
9991
|
+
if (balances[i] <= 0) continue;
|
|
9992
|
+
const pay = Math.min(balances[i], debts[i].minPayment);
|
|
9993
|
+
balances[i] -= pay;
|
|
9994
|
+
remainingBudget -= pay;
|
|
9995
|
+
totalPaid += pay;
|
|
9996
|
+
}
|
|
9997
|
+
for (const i of order) {
|
|
9998
|
+
if (remainingBudget <= 0) break;
|
|
9999
|
+
if (balances[i] <= 0) continue;
|
|
10000
|
+
const pay = Math.min(balances[i], remainingBudget);
|
|
10001
|
+
balances[i] -= pay;
|
|
10002
|
+
remainingBudget -= pay;
|
|
10003
|
+
totalPaid += pay;
|
|
10004
|
+
}
|
|
10005
|
+
for (const i of order) {
|
|
10006
|
+
if (balances[i] <= 5e-3 && !schedule.some((s) => s.label === debts[i].label)) {
|
|
10007
|
+
schedule.push({ label: debts[i].label, monthPaidOff: month });
|
|
10008
|
+
}
|
|
10009
|
+
}
|
|
10010
|
+
}
|
|
10011
|
+
if (balances.some((b) => b > 5e-3)) return null;
|
|
10012
|
+
return {
|
|
10013
|
+
order: order.map((i) => debts[i].label),
|
|
10014
|
+
monthsToPayoff: month,
|
|
10015
|
+
totalInterestPaid: round2(totalInterestPaid),
|
|
10016
|
+
totalPaid: round2(totalPaid),
|
|
10017
|
+
schedule
|
|
10018
|
+
};
|
|
10019
|
+
}
|
|
10020
|
+
function _planDebtPayoffMcp(debtsInput, extraMonthlyPayment) {
|
|
10021
|
+
if (!Array.isArray(debtsInput) || debtsInput.length === 0) {
|
|
10022
|
+
return { error: "debts must be a non-empty array" };
|
|
10023
|
+
}
|
|
10024
|
+
if (typeof extraMonthlyPayment !== "number" || !Number.isFinite(extraMonthlyPayment) || extraMonthlyPayment < 0) {
|
|
10025
|
+
return { error: "extraMonthlyPayment must be a finite, non-negative number" };
|
|
10026
|
+
}
|
|
10027
|
+
const normalized = [];
|
|
10028
|
+
for (let i = 0; i < debtsInput.length; i++) {
|
|
10029
|
+
const d = debtsInput[i];
|
|
10030
|
+
if (typeof d.balance !== "number" || !Number.isFinite(d.balance) || d.balance <= 0) {
|
|
10031
|
+
return { error: `debt ${i + 1}: balance must be a finite number greater than zero` };
|
|
10032
|
+
}
|
|
10033
|
+
if (typeof d.ratePercent !== "number" || !Number.isFinite(d.ratePercent) || d.ratePercent < 0) {
|
|
10034
|
+
return { error: `debt ${i + 1}: ratePercent must be a finite, non-negative number` };
|
|
10035
|
+
}
|
|
10036
|
+
if (typeof d.minPayment !== "number" || !Number.isFinite(d.minPayment) || d.minPayment <= 0) {
|
|
10037
|
+
return { error: `debt ${i + 1}: minPayment must be a finite number greater than zero` };
|
|
10038
|
+
}
|
|
10039
|
+
normalized.push({ label: d.label?.trim() || `Debt ${i + 1}`, balance: d.balance, ratePercent: d.ratePercent, minPayment: d.minPayment });
|
|
10040
|
+
}
|
|
10041
|
+
const snowballOrder = normalized.map((_, i) => i).sort((a, b) => normalized[a].balance - normalized[b].balance);
|
|
10042
|
+
const avalancheOrder = normalized.map((_, i) => i).sort((a, b) => normalized[b].ratePercent - normalized[a].ratePercent);
|
|
10043
|
+
const snowball = _simulateDebtPayoffMcp(normalized, snowballOrder, extraMonthlyPayment);
|
|
10044
|
+
const avalanche = _simulateDebtPayoffMcp(normalized, avalancheOrder, extraMonthlyPayment);
|
|
10045
|
+
if (!snowball || !avalanche) {
|
|
10046
|
+
return { error: `debts cannot be paid off within ${_DEBT_PAYOFF_MAX_MONTHS_MCP} months at this payment level; increase a minimum payment or the extra monthly payment` };
|
|
10047
|
+
}
|
|
10048
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
10049
|
+
const snowballMonths = snowball.monthsToPayoff;
|
|
10050
|
+
const avalancheMonths = avalanche.monthsToPayoff;
|
|
10051
|
+
const snowballInterest = snowball.totalInterestPaid;
|
|
10052
|
+
const avalancheInterest = avalanche.totalInterestPaid;
|
|
10053
|
+
const fasterStrategy = snowballMonths === avalancheMonths ? "tie" : snowballMonths < avalancheMonths ? "snowball" : "avalanche";
|
|
10054
|
+
const cheaperStrategy = snowballInterest === avalancheInterest ? "tie" : snowballInterest < avalancheInterest ? "snowball" : "avalanche";
|
|
10055
|
+
return {
|
|
10056
|
+
debts: normalized,
|
|
10057
|
+
totalMonthlyBudget: round2(normalized.reduce((sum, d) => sum + d.minPayment, 0) + extraMonthlyPayment),
|
|
10058
|
+
extraMonthlyPayment,
|
|
10059
|
+
snowball,
|
|
10060
|
+
avalanche,
|
|
10061
|
+
comparison: {
|
|
10062
|
+
fasterStrategy,
|
|
10063
|
+
monthsDifference: Math.abs(snowballMonths - avalancheMonths),
|
|
10064
|
+
cheaperStrategy,
|
|
10065
|
+
interestDifference: round2(Math.abs(snowballInterest - avalancheInterest))
|
|
10066
|
+
}
|
|
10067
|
+
};
|
|
10068
|
+
}
|
|
10069
|
+
server.tool(
|
|
10070
|
+
"plan_debt_payoff",
|
|
10071
|
+
"Simulate paying off a list of debts under both the snowball strategy (smallest balance first) and the avalanche strategy (highest interest rate first), given a fixed extra monthly payment stacked on top of everyone's minimum payment, and compare months to payoff and total interest between the two.",
|
|
10072
|
+
{
|
|
10073
|
+
debts: z.array(z.object({
|
|
10074
|
+
label: z.string().optional().describe("Optional debt label"),
|
|
10075
|
+
balance: z.number().positive().describe("Current balance owed"),
|
|
10076
|
+
ratePercent: z.number().min(0).describe("Annual interest rate for this debt, as a percent"),
|
|
10077
|
+
minPayment: z.number().positive().describe("Minimum monthly payment required on this debt")
|
|
10078
|
+
})).min(1).describe("A non-empty list of debts"),
|
|
10079
|
+
extraMonthlyPayment: z.number().min(0).optional().describe("Extra amount paid each month on top of every debt's minimum payment, default 0")
|
|
10080
|
+
},
|
|
10081
|
+
async ({ debts, extraMonthlyPayment }) => {
|
|
10082
|
+
const result = _planDebtPayoffMcp(debts, extraMonthlyPayment ?? 0);
|
|
10083
|
+
if ("error" in result) return errText(result);
|
|
10084
|
+
return text(JSON.stringify(result, null, 2));
|
|
10085
|
+
}
|
|
10086
|
+
);
|
|
10087
|
+
var _DEPENDENCY_SECTIONS_MCP = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
10088
|
+
var _EXACT_SEMVER_RE_MCP = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
|
10089
|
+
function _classifyDependencyRangeMcp(version) {
|
|
10090
|
+
const v = version.trim();
|
|
10091
|
+
if (v === "" || v === "*" || v.toLowerCase() === "x") return "wildcard";
|
|
10092
|
+
if (v.startsWith("workspace:")) return "workspace";
|
|
10093
|
+
if (v.startsWith("file:")) return "file";
|
|
10094
|
+
if (v.startsWith("link:")) return "link";
|
|
10095
|
+
if (v.startsWith("http://") || v.startsWith("https://")) return "url";
|
|
10096
|
+
if (v.startsWith("git+") || v.startsWith("git://") || v.startsWith("github:") || v.endsWith(".git") || /^[\w-]+\/[\w.-]+$/.test(v)) return "git";
|
|
10097
|
+
if (v.startsWith("^")) return "caret";
|
|
10098
|
+
if (v.startsWith("~")) return "tilde";
|
|
10099
|
+
if (v.includes("||") || v.includes(" ") || v.startsWith(">") || v.startsWith("<") || v.startsWith("=")) return "range";
|
|
10100
|
+
if (_EXACT_SEMVER_RE_MCP.test(v)) return "exact";
|
|
10101
|
+
if (/^[A-Za-z][\w.-]*$/.test(v)) return "tag";
|
|
10102
|
+
return "unknown";
|
|
10103
|
+
}
|
|
10104
|
+
function _auditDependencyRangesMcp(packageJsonText) {
|
|
10105
|
+
if (!packageJsonText || !packageJsonText.trim()) return { error: "Input is empty" };
|
|
10106
|
+
let parsed;
|
|
10107
|
+
try {
|
|
10108
|
+
parsed = JSON.parse(packageJsonText);
|
|
10109
|
+
} catch (e) {
|
|
10110
|
+
return { error: `Invalid JSON: ${e.message}` };
|
|
10111
|
+
}
|
|
10112
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10113
|
+
return { error: "Root of package.json must be an object" };
|
|
10114
|
+
}
|
|
10115
|
+
const obj = parsed;
|
|
10116
|
+
const presentSections = _DEPENDENCY_SECTIONS_MCP.filter((s) => obj[s] && typeof obj[s] === "object");
|
|
10117
|
+
if (presentSections.length === 0) {
|
|
10118
|
+
return { error: "package.json has no dependencies, devDependencies, peerDependencies, or optionalDependencies field" };
|
|
10119
|
+
}
|
|
10120
|
+
const dependencies = [];
|
|
10121
|
+
const issues = [];
|
|
10122
|
+
const byName = /* @__PURE__ */ new Map();
|
|
10123
|
+
for (const section of presentSections) {
|
|
10124
|
+
const map = obj[section];
|
|
10125
|
+
for (const [name, versionRaw] of Object.entries(map)) {
|
|
10126
|
+
const version = typeof versionRaw === "string" ? versionRaw : String(versionRaw);
|
|
10127
|
+
const rangeType = _classifyDependencyRangeMcp(version);
|
|
10128
|
+
const entryIssues = [];
|
|
10129
|
+
if (rangeType === "wildcard") {
|
|
10130
|
+
entryIssues.push("no real version constraint; any version, including breaking ones, could be installed");
|
|
10131
|
+
}
|
|
10132
|
+
if (rangeType === "tag" && version.trim().toLowerCase() === "latest") {
|
|
10133
|
+
entryIssues.push('"latest" is not reproducible; a fresh install can pull in a different, potentially breaking version at any time');
|
|
10134
|
+
}
|
|
10135
|
+
if (rangeType === "git" || rangeType === "url") {
|
|
10136
|
+
entryIssues.push(`not resolved from the npm registry (${rangeType}); can't be checked against registry advisories the same way`);
|
|
10137
|
+
}
|
|
10138
|
+
if (rangeType === "file" || rangeType === "link") {
|
|
10139
|
+
entryIssues.push(`local ${rangeType === "file" ? "path" : "symlink"} dependency; not portable to a machine or CI runner without the same local path`);
|
|
10140
|
+
}
|
|
10141
|
+
const entry = { name, version, section, rangeType, issues: entryIssues };
|
|
10142
|
+
dependencies.push(entry);
|
|
10143
|
+
for (const issue of entryIssues) {
|
|
10144
|
+
issues.push({ severity: "warning", name, message: issue });
|
|
10145
|
+
}
|
|
10146
|
+
const existing = byName.get(name);
|
|
10147
|
+
if (existing) existing.push(entry);
|
|
10148
|
+
else byName.set(name, [entry]);
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
const duplicates = [];
|
|
10152
|
+
for (const [name, entries] of byName) {
|
|
10153
|
+
if (entries.length < 2) continue;
|
|
10154
|
+
const versions = new Set(entries.map((e) => e.version));
|
|
10155
|
+
const sameVersion = versions.size === 1;
|
|
10156
|
+
duplicates.push({ name, sections: entries.map((e) => e.section), sameVersion });
|
|
10157
|
+
if (!sameVersion) {
|
|
10158
|
+
issues.push({ severity: "error", name, message: `listed with different version ranges in ${entries.map((e) => `${e.section} ("${e.version}")`).join(" and ")}` });
|
|
10159
|
+
} else {
|
|
10160
|
+
issues.push({ severity: "warning", name, message: `listed in more than one section (${entries.map((e) => e.section).join(", ")}) with the same range; redundant` });
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
const rangeTypeCounts = {};
|
|
10164
|
+
for (const dep of dependencies) {
|
|
10165
|
+
const rt = dep.rangeType;
|
|
10166
|
+
rangeTypeCounts[rt] = (rangeTypeCounts[rt] ?? 0) + 1;
|
|
10167
|
+
}
|
|
10168
|
+
return {
|
|
10169
|
+
parsed: true,
|
|
10170
|
+
sections: presentSections,
|
|
10171
|
+
totalDependencies: dependencies.length,
|
|
10172
|
+
dependencies,
|
|
10173
|
+
duplicates,
|
|
10174
|
+
rangeTypeCounts,
|
|
10175
|
+
issues
|
|
10176
|
+
};
|
|
10177
|
+
}
|
|
10178
|
+
server.tool(
|
|
10179
|
+
"audit_dependency_ranges",
|
|
10180
|
+
`Parse a package.json's dependencies, devDependencies, peerDependencies, and optionalDependencies fields, classify each version specifier's range type, and flag wildcard ranges, the non-reproducible "latest" tag, git/URL/file/link dependencies, and packages listed with conflicting or redundant ranges across sections.`,
|
|
10181
|
+
{
|
|
10182
|
+
text: z.string().min(1).describe("The full package.json file contents")
|
|
10183
|
+
},
|
|
10184
|
+
async ({ text: packageJsonText }) => {
|
|
10185
|
+
const result = _auditDependencyRangesMcp(packageJsonText);
|
|
10186
|
+
if ("error" in result) return errText(result);
|
|
10187
|
+
return text(JSON.stringify(result, null, 2));
|
|
10188
|
+
}
|
|
10189
|
+
);
|
|
9971
10190
|
var transport = new StdioServerTransport();
|
|
9972
10191
|
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.63.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",
|