@utilix-tech/mcp 0.95.0 → 0.96.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 +153 -0
- package/package.json +2 -2
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.96.0
|
|
6
|
+
- Add `validate_routing_number` tool: validate a 9-digit US bank routing number via its weighted mod-10 checksum, identifying the routing number's type and Federal Reserve district from its first two digits.
|
|
7
|
+
- Add `audit_markdown_image_alt_text` tool: scan a Markdown document's inline images and flag alt text accessibility smells, missing/empty alt text, generic placeholder words, a redundant "image of" prefix, and alt text that's just the image's own filename.
|
|
8
|
+
|
|
5
9
|
## 0.95.0
|
|
6
10
|
- Add `read_png_chunks` tool: walk a PNG's chunk stream directly from its bytes to report every chunk's type, offset, size, and CRC-32 validity, plus the decoded IHDR header and common ancillary chunks (tEXt, pHYs, gAMA, tIME, acTL).
|
|
7
11
|
- Add `estimate_audio_file_size` tool: estimate an audio file's size before recording or exporting it, uncompressed PCM size from sample rate/bit depth/channels or compressed size from a target bitrate.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 285 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
|
|
|
@@ -169,7 +169,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
169
169
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
170
170
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
171
171
|
|
|
172
|
-
### Units (
|
|
172
|
+
### Units (52 tools)
|
|
173
173
|
| Tool | Description |
|
|
174
174
|
|------|-------------|
|
|
175
175
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -223,6 +223,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
223
223
|
| `detect_negative_amortization` | Given a loan's balance, annual rate, and a proposed monthly payment, check whether that payment covers the interest accruing each period; if not, report the shortfall and project how the balance grows instead of shrinking |
|
|
224
224
|
| `calculate_sales_commission` | Compute a sales commission from either a flat percent rate applied to the whole sale amount, or a tiered (marginal) rate structure where each tier applies only to the slice of sales within it, plus an optional base salary added to the total payout |
|
|
225
225
|
| `intersect_semver_ranges` | Determine whether two semver ranges overlap and, if so, the intersecting range, supporting the same caret/tilde/comparison-operator/exact-version syntax as the semver tool |
|
|
226
|
+
| `validate_routing_number` | Validate a 9-digit US bank routing number (ABA/RTN) via its weighted mod-10 checksum, and identify its routing number type and Federal Reserve district |
|
|
226
227
|
|
|
227
228
|
### API / Network (55 tools)
|
|
228
229
|
| Tool | Description |
|
|
@@ -328,7 +329,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
328
329
|
| `find_duplicate_lockfile_versions` | Parse an npm, yarn, or pnpm lockfile and report every package installed at more than one resolved version, with a count of install locations per version |
|
|
329
330
|
| `summarize_git_diff_stat` | Parse `git diff --stat` / `git log --stat` output into structured per-file insertion/deletion counts plus overall totals |
|
|
330
331
|
|
|
331
|
-
### Data (
|
|
332
|
+
### Data (22 tools)
|
|
332
333
|
| Tool | Description |
|
|
333
334
|
|------|-------------|
|
|
334
335
|
| `validate_yaml` | Validate YAML syntax |
|
|
@@ -352,6 +353,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
352
353
|
| `validate_markdown_heading_hierarchy` | Scan a Markdown document's ATX headings and flag heading-structure issues: skipped levels, multiple H1s, empty headings, duplicate headings, and a document that doesn't start at H1 |
|
|
353
354
|
| `check_markdown_internal_links` | Scan a Markdown document's ATX headings, compute the GitHub-style anchor slug for each, and check every [text](#slug) internal link against them, flagging links that point at a heading that doesn't exist |
|
|
354
355
|
| `summarize_csv_column_stats` | Parse CSV/TSV data and compute per-column summary statistics: null/unique counts for every column, plus min/max/mean/median/standard deviation for numeric columns or the most common value for categorical ones |
|
|
356
|
+
| `audit_markdown_image_alt_text` | Scan a Markdown document's inline images and flag alt text accessibility smells: missing/empty alt text, generic placeholder words, a redundant "image of"/"photo of" prefix, and alt text that's just the image's own filename |
|
|
355
357
|
|
|
356
358
|
### CSS (2 tools)
|
|
357
359
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -15254,5 +15254,158 @@ server.tool(
|
|
|
15254
15254
|
return text(JSON.stringify(result, null, 2));
|
|
15255
15255
|
}
|
|
15256
15256
|
);
|
|
15257
|
+
var _ABA_FEDERAL_RESERVE_BANKS_MCP = {
|
|
15258
|
+
1: "Boston",
|
|
15259
|
+
2: "New York",
|
|
15260
|
+
3: "Philadelphia",
|
|
15261
|
+
4: "Cleveland",
|
|
15262
|
+
5: "Richmond",
|
|
15263
|
+
6: "Atlanta",
|
|
15264
|
+
7: "Chicago",
|
|
15265
|
+
8: "St. Louis",
|
|
15266
|
+
9: "Minneapolis",
|
|
15267
|
+
10: "Kansas City",
|
|
15268
|
+
11: "Dallas",
|
|
15269
|
+
12: "San Francisco"
|
|
15270
|
+
};
|
|
15271
|
+
function _abaDistrictAndTypeMcp(prefix) {
|
|
15272
|
+
if (prefix >= 1 && prefix <= 12) {
|
|
15273
|
+
return { type: "primary", district: { code: prefix, bank: _ABA_FEDERAL_RESERVE_BANKS_MCP[prefix] } };
|
|
15274
|
+
}
|
|
15275
|
+
if (prefix >= 21 && prefix <= 32) {
|
|
15276
|
+
const code = prefix - 20;
|
|
15277
|
+
return { type: "thrift", district: { code, bank: _ABA_FEDERAL_RESERVE_BANKS_MCP[code] } };
|
|
15278
|
+
}
|
|
15279
|
+
if (prefix >= 61 && prefix <= 72) {
|
|
15280
|
+
const code = prefix - 60;
|
|
15281
|
+
return { type: "electronic", district: { code, bank: _ABA_FEDERAL_RESERVE_BANKS_MCP[code] } };
|
|
15282
|
+
}
|
|
15283
|
+
if (prefix === 80) return { type: "traveler-check", district: null };
|
|
15284
|
+
return { type: "unknown", district: null };
|
|
15285
|
+
}
|
|
15286
|
+
function _formatRoutingNumberMcp(digits) {
|
|
15287
|
+
return `${digits.slice(0, 4)} ${digits.slice(4, 8)} ${digits.slice(8)}`;
|
|
15288
|
+
}
|
|
15289
|
+
function _validateRoutingNumberMcp(input) {
|
|
15290
|
+
if (!input || !input.trim()) return { error: "Input is empty" };
|
|
15291
|
+
const digits = input.replace(/[\s-]/g, "");
|
|
15292
|
+
if (!/^\d+$/.test(digits)) return { error: "Routing number must contain only digits, spaces, or hyphens" };
|
|
15293
|
+
if (digits.length !== 9) return { error: `Routing number length (${digits.length}) must be exactly 9 digits` };
|
|
15294
|
+
const d = digits.split("").map(Number);
|
|
15295
|
+
const sum = 3 * (d[0] + d[3] + d[6]) + 7 * (d[1] + d[4] + d[7]) + 1 * (d[2] + d[5] + d[8]);
|
|
15296
|
+
const checksumValid = sum % 10 === 0;
|
|
15297
|
+
const prefix = Number(digits.slice(0, 2));
|
|
15298
|
+
const { type, district } = _abaDistrictAndTypeMcp(prefix);
|
|
15299
|
+
return {
|
|
15300
|
+
valid: checksumValid,
|
|
15301
|
+
checksumValid,
|
|
15302
|
+
length: digits.length,
|
|
15303
|
+
formatted: _formatRoutingNumberMcp(digits),
|
|
15304
|
+
routingNumberType: type,
|
|
15305
|
+
federalReserveDistrict: district
|
|
15306
|
+
};
|
|
15307
|
+
}
|
|
15308
|
+
server.tool(
|
|
15309
|
+
"validate_routing_number",
|
|
15310
|
+
"Validate a 9-digit US bank routing number (ABA/RTN) via the weighted mod-10 checksum from the ABA's routing number specification. Also identifies the routing number's type and Federal Reserve district from its first two digits.",
|
|
15311
|
+
{ routingNumber: z.string().describe("The 9-digit routing number, with optional spaces or hyphens") },
|
|
15312
|
+
async ({ routingNumber }) => {
|
|
15313
|
+
const result = _validateRoutingNumberMcp(routingNumber);
|
|
15314
|
+
if ("error" in result) return errText(result);
|
|
15315
|
+
return text(JSON.stringify(result, null, 2));
|
|
15316
|
+
}
|
|
15317
|
+
);
|
|
15318
|
+
var _MARKDOWN_IMAGE_RE_MCP = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
15319
|
+
var _MARKDOWN_IMAGE_GENERIC_ALT_WORDS_MCP = /* @__PURE__ */ new Set([
|
|
15320
|
+
"image",
|
|
15321
|
+
"picture",
|
|
15322
|
+
"photo",
|
|
15323
|
+
"photograph",
|
|
15324
|
+
"pic",
|
|
15325
|
+
"img",
|
|
15326
|
+
"graphic",
|
|
15327
|
+
"screenshot",
|
|
15328
|
+
"icon",
|
|
15329
|
+
"logo"
|
|
15330
|
+
]);
|
|
15331
|
+
var _MARKDOWN_IMAGE_REDUNDANT_PREFIX_RE_MCP = /^(image|picture|photo|photograph)\s+of\s+/i;
|
|
15332
|
+
var _MARKDOWN_IMAGE_FILENAME_EXT_RE_MCP = /\.(png|jpe?g|gif|svg|webp|bmp|avif|tiff?)$/i;
|
|
15333
|
+
function _markdownImageBasenameWithoutExtMcp(src) {
|
|
15334
|
+
const withoutQuery = src.split(/[?#]/)[0];
|
|
15335
|
+
const file = withoutQuery.split("/").pop() ?? withoutQuery;
|
|
15336
|
+
return file.replace(_MARKDOWN_IMAGE_FILENAME_EXT_RE_MCP, "");
|
|
15337
|
+
}
|
|
15338
|
+
function _auditMarkdownImageAltTextMcp(markdown) {
|
|
15339
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
15340
|
+
const images = [];
|
|
15341
|
+
const issues = [];
|
|
15342
|
+
let inFence = false;
|
|
15343
|
+
let fenceMarker = "";
|
|
15344
|
+
lines.forEach((line, idx) => {
|
|
15345
|
+
const fenceMatch = line.match(/^\s*(```+|~~~+)/);
|
|
15346
|
+
if (fenceMatch) {
|
|
15347
|
+
if (!inFence) {
|
|
15348
|
+
inFence = true;
|
|
15349
|
+
fenceMarker = fenceMatch[1][0];
|
|
15350
|
+
} else if (fenceMatch[1][0] === fenceMarker) {
|
|
15351
|
+
inFence = false;
|
|
15352
|
+
}
|
|
15353
|
+
return;
|
|
15354
|
+
}
|
|
15355
|
+
if (inFence) return;
|
|
15356
|
+
const lineNo = idx + 1;
|
|
15357
|
+
_MARKDOWN_IMAGE_RE_MCP.lastIndex = 0;
|
|
15358
|
+
let match;
|
|
15359
|
+
while ((match = _MARKDOWN_IMAGE_RE_MCP.exec(line)) !== null) {
|
|
15360
|
+
const alt = match[1].trim();
|
|
15361
|
+
const src = match[2];
|
|
15362
|
+
images.push({ line: lineNo, alt, src });
|
|
15363
|
+
if (alt === "") {
|
|
15364
|
+
issues.push({
|
|
15365
|
+
line: lineNo,
|
|
15366
|
+
type: "empty-alt",
|
|
15367
|
+
src,
|
|
15368
|
+
message: `Image at line ${lineNo} has no alt text (src: ${src}). If this image is purely decorative, an intentionally empty alt is fine; otherwise it needs a description.`
|
|
15369
|
+
});
|
|
15370
|
+
continue;
|
|
15371
|
+
}
|
|
15372
|
+
if (_MARKDOWN_IMAGE_GENERIC_ALT_WORDS_MCP.has(alt.toLowerCase())) {
|
|
15373
|
+
issues.push({
|
|
15374
|
+
line: lineNo,
|
|
15375
|
+
type: "generic-alt",
|
|
15376
|
+
src,
|
|
15377
|
+
message: `Alt text "${alt}" at line ${lineNo} is a generic placeholder and doesn't describe the image's content.`
|
|
15378
|
+
});
|
|
15379
|
+
}
|
|
15380
|
+
if (_MARKDOWN_IMAGE_REDUNDANT_PREFIX_RE_MCP.test(alt)) {
|
|
15381
|
+
issues.push({
|
|
15382
|
+
line: lineNo,
|
|
15383
|
+
type: "redundant-prefix",
|
|
15384
|
+
src,
|
|
15385
|
+
message: `Alt text "${alt}" at line ${lineNo} starts with a redundant prefix; screen readers already announce images, so "${alt.replace(_MARKDOWN_IMAGE_REDUNDANT_PREFIX_RE_MCP, "")}" reads better.`
|
|
15386
|
+
});
|
|
15387
|
+
}
|
|
15388
|
+
const baseName = _markdownImageBasenameWithoutExtMcp(src);
|
|
15389
|
+
if (_MARKDOWN_IMAGE_FILENAME_EXT_RE_MCP.test(alt) || baseName !== "" && alt.toLowerCase() === baseName.toLowerCase()) {
|
|
15390
|
+
issues.push({
|
|
15391
|
+
line: lineNo,
|
|
15392
|
+
type: "filename-as-alt",
|
|
15393
|
+
src,
|
|
15394
|
+
message: `Alt text "${alt}" at line ${lineNo} looks like the image's own filename rather than a description.`
|
|
15395
|
+
});
|
|
15396
|
+
}
|
|
15397
|
+
}
|
|
15398
|
+
});
|
|
15399
|
+
return { images, issues, imageCount: images.length };
|
|
15400
|
+
}
|
|
15401
|
+
server.tool(
|
|
15402
|
+
"audit_markdown_image_alt_text",
|
|
15403
|
+
'Scan a Markdown document\'s inline images (``) and flag alt text accessibility smells: missing/empty alt text, a generic placeholder word used alone ("image", "photo"), alt text that\'s just the image\'s own filename, and a redundant "image of"/"photo of" prefix. Only inline image syntax is scanned; reference-style images and raw HTML `<img>` tags are not detected.',
|
|
15404
|
+
{ markdown: z.string().describe("The Markdown document to scan") },
|
|
15405
|
+
async ({ markdown }) => {
|
|
15406
|
+
const result = _auditMarkdownImageAltTextMcp(markdown);
|
|
15407
|
+
return text(JSON.stringify(result, null, 2));
|
|
15408
|
+
}
|
|
15409
|
+
);
|
|
15257
15410
|
var transport = new StdioServerTransport();
|
|
15258
15411
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilix-tech/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server exposing
|
|
3
|
+
"version": "0.96.0",
|
|
4
|
+
"description": "MCP server exposing 285 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",
|