@utilix-tech/mcp 0.55.0 → 0.56.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.56.0
6
+ - Add `calculate_debt_to_income_ratio` tool: compute the debt-to-income ratio (total monthly debt payments / gross monthly income) from one or more monthly debts, plus a qualitative tier matching common mortgage-lending guidance.
7
+ - Add `infer_csv_column_types` tool: parse CSV/TSV data and infer each column's data type (integer, float, boolean, ISO 8601 date, or string), flagging nullable columns and empty-cell counts.
8
+
5
9
  ## 0.55.0
6
10
  - Add `validate_package_exports` tool: parse a package.json's exports field and flag common dual-package (ESM/CJS) misconfigurations, a types condition not listed first, a missing default fallback, mismatched require/import extensions, mixed subpath/condition keys, and pattern mismatches.
7
11
  - Add `build_x_content_type_options_header` tool: build the fixed X-Content-Type-Options: nosniff header, validate a pasted value, and assess MIME-sniffing risk for a given Content-Type.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 198 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 200 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 (33 tools)
163
+ ### Units (34 tools)
164
164
  | Tool | Description |
165
165
  |------|-------------|
166
166
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -196,6 +196,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
196
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
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 |
198
198
  | `calculate_credit_utilization` | Compute per-account and overall credit utilization (balance / limit) from one or more credit accounts, plus a qualitative tier matching common credit-scoring guidance |
199
+ | `calculate_debt_to_income_ratio` | Compute the debt-to-income ratio (total monthly debt payments / gross monthly income) from one or more monthly debts, plus a qualitative tier matching common mortgage-lending guidance |
199
200
 
200
201
  ### API / Network (29 tools)
201
202
  | Tool | Description |
@@ -257,7 +258,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
257
258
  | `validate_tsconfig` | Validate a tsconfig.json file (JSONC-aware): unknown keys, unknown compilerOptions, and type mismatches |
258
259
  | `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 |
259
260
 
260
- ### Data (13 tools)
261
+ ### Data (14 tools)
261
262
  | Tool | Description |
262
263
  |------|-------------|
263
264
  | `validate_yaml` | Validate YAML syntax |
@@ -273,6 +274,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
273
274
  | `find_csv_duplicate_rows` | Group CSV/TSV rows that share the same value across a chosen set of key columns, or the entire row |
274
275
  | `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 |
275
276
  | `analyze_caption_speed` | Parse an SRT or WebVTT file and report the words-per-minute reading speed of every cue, flagging ones that exceed common broadcast accessibility guidelines |
277
+ | `infer_csv_column_types` | Parse CSV/TSV data and infer each column's data type (integer, float, boolean, ISO 8601 date, or string), flagging nullable columns and empty-cell counts |
276
278
 
277
279
  ### CSS (2 tools)
278
280
  | Tool | Description |
package/dist/index.js CHANGED
@@ -7271,6 +7271,71 @@ server.tool(
7271
7271
  return text(JSON.stringify(result, null, 2));
7272
7272
  }
7273
7273
  );
7274
+ var _CSV_TYPE_ISO_DATE_RE_MCP = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
7275
+ function _classifyCsvValueMcp(raw) {
7276
+ const v = raw.trim();
7277
+ if (v === "") return "empty";
7278
+ if (/^(true|false)$/i.test(v)) return "boolean";
7279
+ if (/^-?\d+$/.test(v)) return "integer";
7280
+ if (/^-?\d+\.\d+$/.test(v)) return "float";
7281
+ if (_CSV_TYPE_ISO_DATE_RE_MCP.test(v)) return "date";
7282
+ return "string";
7283
+ }
7284
+ function _inferCsvColumnTypesMcp(input, delimiter, hasHeader) {
7285
+ if (!input || input.trim() === "") return { error: "Input is empty" };
7286
+ const delim = delimiter || ",";
7287
+ const header = hasHeader !== false;
7288
+ const lines = input.replace(/\r\n/g, "\n").split("\n").filter((l) => l.trim() !== "");
7289
+ if (lines.length === 0) return { error: "No data found" };
7290
+ const allRows = lines.map((l) => _parseCsvRowForSqlMcp(l, delim));
7291
+ const headers = header ? allRows[0].map((h) => h.trim()) : allRows[0].map((_, idx) => `Column ${idx + 1}`);
7292
+ const rows = header ? allRows.slice(1) : allRows;
7293
+ const columns = headers.map((name, colIdx) => {
7294
+ const seen = /* @__PURE__ */ new Set();
7295
+ let sawString = false;
7296
+ let nullCount = 0;
7297
+ const sampleValues = [];
7298
+ for (const row of rows) {
7299
+ const raw = row[colIdx] ?? "";
7300
+ const kind = _classifyCsvValueMcp(raw);
7301
+ if (kind === "empty") {
7302
+ nullCount++;
7303
+ continue;
7304
+ }
7305
+ if (sampleValues.length < 3) sampleValues.push(raw);
7306
+ if (kind === "string") sawString = true;
7307
+ else seen.add(kind);
7308
+ }
7309
+ let inferredType;
7310
+ if (sawString || seen.size === 0) inferredType = "string";
7311
+ else if (seen.size === 1) inferredType = [...seen][0];
7312
+ else if (seen.has("integer") && seen.has("float") && seen.size === 2) inferredType = "float";
7313
+ else inferredType = "string";
7314
+ return {
7315
+ name,
7316
+ inferredType,
7317
+ nullable: nullCount > 0,
7318
+ nullCount,
7319
+ totalCount: rows.length,
7320
+ sampleValues
7321
+ };
7322
+ });
7323
+ return { columns, rowCount: rows.length };
7324
+ }
7325
+ server.tool(
7326
+ "infer_csv_column_types",
7327
+ "Parse CSV/TSV data and infer each column's data type (integer, float, boolean, ISO 8601 date, or string) by classifying every non-empty value and finding the tightest type that fits them all: all-integer values infer as integer, a mix of integers and decimals infers as float, and anything that doesn't fit a narrower type falls back to string. Empty cells mark a column nullable but don't affect its inferred type.",
7328
+ {
7329
+ csv: z.string().min(1).describe("CSV/TSV data to infer column types from"),
7330
+ delimiter: z.string().optional().describe('Field delimiter (default ",")'),
7331
+ hasHeader: z.boolean().default(true).describe("Whether the first row is a header")
7332
+ },
7333
+ async ({ csv, delimiter, hasHeader }) => {
7334
+ const result = _inferCsvColumnTypesMcp(csv, delimiter, hasHeader);
7335
+ if ("error" in result) return errText(result);
7336
+ return text(JSON.stringify(result, null, 2));
7337
+ }
7338
+ );
7274
7339
  var _VARY_HEADER_NOTES_MCP = {
7275
7340
  "accept-encoding": "Caches must store a separate response per compression scheme (gzip, br, etc.) the client accepts.",
7276
7341
  "accept-language": "Caches must store a separate response per requested language, which can fragment a shared cache badly if Accept-Language values are highly varied.",
@@ -8922,6 +8987,59 @@ server.tool(
8922
8987
  return text(JSON.stringify(result, null, 2));
8923
8988
  }
8924
8989
  );
8990
+ function _classifyDtiMcp(percent) {
8991
+ if (percent < 36) return "healthy";
8992
+ if (percent < 43) return "moderate";
8993
+ if (percent < 50) return "high";
8994
+ return "severe";
8995
+ }
8996
+ function _calculateDebtToIncomeRatioMcp(debts, grossMonthlyIncome) {
8997
+ if (!Array.isArray(debts) || debts.length === 0) {
8998
+ return { error: "debts must be a non-empty array" };
8999
+ }
9000
+ if (typeof grossMonthlyIncome !== "number" || !Number.isFinite(grossMonthlyIncome) || grossMonthlyIncome <= 0) {
9001
+ return { error: "grossMonthlyIncome must be a finite number greater than zero" };
9002
+ }
9003
+ const round2 = (n) => Math.round(n * 100) / 100;
9004
+ let totalMonthlyDebt = 0;
9005
+ const results = [];
9006
+ for (let i = 0; i < debts.length; i++) {
9007
+ const debt = debts[i];
9008
+ if (typeof debt.amount !== "number" || !Number.isFinite(debt.amount) || debt.amount < 0) {
9009
+ return { error: `debt ${i + 1}: amount must be a finite, non-negative number` };
9010
+ }
9011
+ totalMonthlyDebt += debt.amount;
9012
+ results.push({
9013
+ label: debt.label?.trim() || `Debt ${i + 1}`,
9014
+ amount: debt.amount,
9015
+ percentOfIncome: round2(debt.amount / grossMonthlyIncome * 100)
9016
+ });
9017
+ }
9018
+ const dtiPercent = round2(totalMonthlyDebt / grossMonthlyIncome * 100);
9019
+ return {
9020
+ debts: results,
9021
+ totalMonthlyDebt: round2(totalMonthlyDebt),
9022
+ grossMonthlyIncome,
9023
+ dtiPercent,
9024
+ tier: _classifyDtiMcp(dtiPercent)
9025
+ };
9026
+ }
9027
+ server.tool(
9028
+ "calculate_debt_to_income_ratio",
9029
+ "Compute the debt-to-income ratio (total monthly debt payments / gross monthly income) from one or more monthly debt items, plus a qualitative tier matching common mortgage-lending guidance: under 36% is healthy, under 43% is moderate (43% is the back-end ratio ceiling many mortgage lenders use), under 50% is high, and 50%+ is severe. This is a back-end DTI (all monthly debt payments over gross income), not a front-end housing-only ratio.",
9030
+ {
9031
+ debts: z.array(z.object({
9032
+ label: z.string().optional().describe("Optional debt label"),
9033
+ amount: z.number().min(0).describe("Monthly payment amount for this debt")
9034
+ })).min(1).describe("A non-empty list of monthly debt payments"),
9035
+ grossMonthlyIncome: z.number().positive().describe("Gross (pre-tax) monthly income")
9036
+ },
9037
+ async ({ debts, grossMonthlyIncome }) => {
9038
+ const result = _calculateDebtToIncomeRatioMcp(debts, grossMonthlyIncome);
9039
+ if ("error" in result) return errText(result);
9040
+ return text(JSON.stringify(result, null, 2));
9041
+ }
9042
+ );
8925
9043
  var _CAPTION_TIMESTAMP_RE_MCP = /(\d+):(\d{1,2}):(\d{1,2})[.,](\d{1,3})|(\d{1,3}):(\d{1,2})[.,](\d{1,3})/;
8926
9044
  var _CAPTION_SKIP_BLOCK_KEYWORDS_MCP = ["NOTE", "STYLE", "REGION"];
8927
9045
  function _parseCaptionTimestampMcp(raw) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.55.0",
3
+ "version": "0.56.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",