@utilix-tech/mcp 0.74.0 → 0.75.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.75.0
6
+ - Add `verify_webhook_signature` tool: recompute an HMAC over a raw webhook payload and compare it to a provided signature, supporting the `sha256=` prefix style (Stripe/GitHub-style webhooks) plus raw hex or base64 encoding.
7
+ - Add `estimate_data_transfer_time` tool: estimate the theoretical time to transfer a file over a connection of a given speed, or the reverse: the bandwidth required to hit a target time.
8
+
5
9
  ## 0.74.0
6
10
  - Add `calculate_loan_prepayment_penalty` tool: compute the dollar penalty cost of paying a loan off early, given its payoff balance and a prepayment penalty structure (a flat percent of balance, or a fixed number of months of interest).
7
11
  - Add `validate_aria` tool: check every role and aria-* attribute in an HTML snippet against a curated WAI-ARIA 1.2 role table, flagging unknown roles, disallowed attributes, and missing required states.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 243 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 245 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 (44 tools)
165
+ ### Units (45 tools)
166
166
  | Tool | Description |
167
167
  |------|-------------|
168
168
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -209,8 +209,9 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
209
209
  | `validate_isin` | Validate a 12-character ISIN via its Luhn-style mod-10 check digit and break it into its country code and security identifier |
210
210
  | `calculate_mortgage_points` | Compare a loan with no discount points against the same loan at a reduced rate bought with points, and compute the monthly savings and the break-even month where cumulative savings recoup the points cost |
211
211
  | `calculate_loan_prepayment_penalty` | Compute the dollar penalty cost of paying a loan off early, given its remaining payoff balance and a prepayment penalty structure: either a flat percent of the payoff balance, or a fixed number of months of interest at the loan's annual rate |
212
+ | `estimate_data_transfer_time` | Estimate the theoretical time to transfer a file of a given size over a connection of a given speed, or (given a target time instead) the bandwidth required to hit it |
212
213
 
213
- ### API / Network (49 tools)
214
+ ### API / Network (50 tools)
214
215
  | Tool | Description |
215
216
  |------|-------------|
216
217
  | `decode_jwt` | Decode a JWT: header, payload, and expiry |
@@ -264,6 +265,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
264
265
  | `build_robots_meta_tag` | Build a `<meta name="robots" content="...">` tag from directives (noindex, nofollow, max-snippet, etc.), the same vocabulary as X-Robots-Tag, optionally scoped to a crawler via the name attribute |
265
266
  | `parse_robots_meta_tag` | Parse an existing robots meta tag (or bare content value) into the same shape |
266
267
  | `calculate_cookie_size_budget` | Parse a Cookie header's name=value pairs and compute each cookie's and the total header's byte size against RFC 6265's 4096-byte per-cookie guarantee and a configurable budget |
268
+ | `verify_webhook_signature` | Recompute an HMAC over a raw webhook payload and compare it to a provided signature, accepting a `sha256=` prefix style and matching against hex or base64 encoding |
267
269
 
268
270
  ### Color (4 tools)
269
271
  | Tool | Description |
package/dist/index.js CHANGED
@@ -11845,5 +11845,120 @@ server.tool(
11845
11845
  return text(JSON.stringify(result, null, 2));
11846
11846
  }
11847
11847
  );
11848
+ var _WEBHOOK_HASH_ALGORITHMS_MCP = ["sha1", "sha256", "sha512"];
11849
+ function _webhookConstantTimeEqualMcp(a, b) {
11850
+ if (a.length !== b.length) return false;
11851
+ let diff = 0;
11852
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
11853
+ return diff === 0;
11854
+ }
11855
+ function _verifyWebhookSignatureMcp(payload, secret, signature, algorithm) {
11856
+ if (!payload) return { error: "Payload is required" };
11857
+ if (!secret) return { error: "Secret is required" };
11858
+ if (!signature || !signature.trim()) return { error: "Signature is required" };
11859
+ if (!_WEBHOOK_HASH_ALGORITHMS_MCP.includes(algorithm)) {
11860
+ return { error: "Algorithm must be one of sha1, sha256, sha512" };
11861
+ }
11862
+ const trimmedSignature = signature.trim();
11863
+ const prefixMatch = trimmedSignature.match(/^(sha1|sha256|sha512)=(.+)$/i);
11864
+ const providedSignature = prefixMatch ? prefixMatch[2].trim() : trimmedSignature;
11865
+ const expectedHex = createHmac(algorithm, secret).update(payload, "utf8").digest("hex");
11866
+ const expectedBase64 = createHmac(algorithm, secret).update(payload, "utf8").digest("base64");
11867
+ let matchedEncoding = null;
11868
+ if (_webhookConstantTimeEqualMcp(providedSignature.toLowerCase(), expectedHex.toLowerCase())) {
11869
+ matchedEncoding = "hex";
11870
+ } else if (_webhookConstantTimeEqualMcp(providedSignature, expectedBase64)) {
11871
+ matchedEncoding = "base64";
11872
+ }
11873
+ return {
11874
+ valid: matchedEncoding !== null,
11875
+ algorithm,
11876
+ expectedHex,
11877
+ expectedBase64,
11878
+ providedSignature: trimmedSignature,
11879
+ matchedEncoding
11880
+ };
11881
+ }
11882
+ server.tool(
11883
+ "verify_webhook_signature",
11884
+ 'Recompute an HMAC over a raw webhook payload and compare it to a provided signature, the check every webhook receiver (Stripe, GitHub, Shopify, etc.) has to do before trusting a request. Accepts a signature with or without the common "sha256=" prefix style, and matches against either hex or base64 encoding since providers differ on which they send.',
11885
+ {
11886
+ payload: z.string().describe("The raw webhook request body, exactly as received (do not re-serialize JSON)"),
11887
+ secret: z.string().describe("The shared webhook signing secret"),
11888
+ signature: z.string().describe('The signature to verify, with or without a "sha256=" style prefix, hex or base64 encoded'),
11889
+ algorithm: z.enum(["sha1", "sha256", "sha512"]).optional().describe("The HMAC hash algorithm the signature was computed with (default sha256)")
11890
+ },
11891
+ async ({ payload, secret, signature, algorithm }) => {
11892
+ const result = _verifyWebhookSignatureMcp(payload, secret, signature, algorithm ?? "sha256");
11893
+ if ("error" in result) return errText(result);
11894
+ return text(JSON.stringify(result, null, 2));
11895
+ }
11896
+ );
11897
+ var _FILE_SIZE_UNIT_BYTES_MCP = { B: 1, KB: 1e3, MB: 1e6, GB: 1e9, TB: 1e12 };
11898
+ var _SPEED_UNIT_BITS_PER_SECOND_MCP = {
11899
+ Kbps: 1e3,
11900
+ Mbps: 1e6,
11901
+ Gbps: 1e9,
11902
+ KBps: 1e3 * 8,
11903
+ MBps: 1e6 * 8,
11904
+ GBps: 1e9 * 8
11905
+ };
11906
+ function _formatDurationSecondsMcp(totalSeconds) {
11907
+ if (totalSeconds < 1) return `${(totalSeconds * 1e3).toFixed(0)}ms`;
11908
+ if (totalSeconds < 60) {
11909
+ const decimals = totalSeconds < 10 && totalSeconds % 1 !== 0 ? 1 : 0;
11910
+ return `${totalSeconds.toFixed(decimals)}s`;
11911
+ }
11912
+ const days = Math.floor(totalSeconds / 86400);
11913
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
11914
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
11915
+ const seconds = Math.round(totalSeconds % 60);
11916
+ const parts = [];
11917
+ if (days > 0) parts.push(`${days}d`);
11918
+ if (hours > 0) parts.push(`${hours}h`);
11919
+ if (minutes > 0) parts.push(`${minutes}m`);
11920
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
11921
+ return parts.join(" ");
11922
+ }
11923
+ function _calculateTransferTimeMcp(fileSize, fileSizeUnit, speed, speedUnit) {
11924
+ if (!Number.isFinite(fileSize) || fileSize <= 0) return { error: "File size must be a positive number" };
11925
+ if (!Number.isFinite(speed) || speed <= 0) return { error: "Connection speed must be a positive number" };
11926
+ if (!(fileSizeUnit in _FILE_SIZE_UNIT_BYTES_MCP)) return { error: `Unsupported file size unit: ${fileSizeUnit}` };
11927
+ if (!(speedUnit in _SPEED_UNIT_BITS_PER_SECOND_MCP)) return { error: `Unsupported speed unit: ${speedUnit}` };
11928
+ const fileSizeBytes = fileSize * _FILE_SIZE_UNIT_BYTES_MCP[fileSizeUnit];
11929
+ const speedBitsPerSecond = speed * _SPEED_UNIT_BITS_PER_SECOND_MCP[speedUnit];
11930
+ const transferTimeSeconds = fileSizeBytes * 8 / speedBitsPerSecond;
11931
+ return { fileSizeBytes, speedBitsPerSecond, transferTimeSeconds, formattedTime: _formatDurationSecondsMcp(transferTimeSeconds) };
11932
+ }
11933
+ function _calculateRequiredBandwidthMcp(fileSize, fileSizeUnit, targetSeconds) {
11934
+ if (!Number.isFinite(fileSize) || fileSize <= 0) return { error: "File size must be a positive number" };
11935
+ if (!Number.isFinite(targetSeconds) || targetSeconds <= 0) return { error: "Target time must be a positive number of seconds" };
11936
+ if (!(fileSizeUnit in _FILE_SIZE_UNIT_BYTES_MCP)) return { error: `Unsupported file size unit: ${fileSizeUnit}` };
11937
+ const fileSizeBytes = fileSize * _FILE_SIZE_UNIT_BYTES_MCP[fileSizeUnit];
11938
+ const requiredBitsPerSecond = fileSizeBytes * 8 / targetSeconds;
11939
+ return {
11940
+ fileSizeBytes,
11941
+ targetSeconds,
11942
+ requiredBitsPerSecond,
11943
+ requiredMbps: requiredBitsPerSecond / 1e6,
11944
+ requiredMBps: requiredBitsPerSecond / 8 / 1e6
11945
+ };
11946
+ }
11947
+ server.tool(
11948
+ "estimate_data_transfer_time",
11949
+ "Estimate the theoretical time to transfer a file of a given size over a connection of a given speed (decimal/SI file size units, standard networking speed units where Mbps is megabits/second and MBps is megabytes/second). Pass targetSeconds instead of speed/speedUnit to run the reverse calculation: the bandwidth required to hit that target time.",
11950
+ {
11951
+ fileSize: z.number().positive().describe("The file size, in fileSizeUnit"),
11952
+ fileSizeUnit: z.enum(["B", "KB", "MB", "GB", "TB"]).describe("The unit fileSize is expressed in"),
11953
+ speed: z.number().positive().optional().describe("The connection speed, in speedUnit (omit and pass targetSeconds for the reverse calculation)"),
11954
+ speedUnit: z.enum(["Kbps", "Mbps", "Gbps", "KBps", "MBps", "GBps"]).optional().describe("The unit speed is expressed in"),
11955
+ targetSeconds: z.number().positive().optional().describe("Target transfer time in seconds; when set, computes the required bandwidth instead of the transfer time")
11956
+ },
11957
+ async ({ fileSize, fileSizeUnit, speed, speedUnit, targetSeconds }) => {
11958
+ const result = targetSeconds !== void 0 ? _calculateRequiredBandwidthMcp(fileSize, fileSizeUnit, targetSeconds) : speed !== void 0 && speedUnit !== void 0 ? _calculateTransferTimeMcp(fileSize, fileSizeUnit, speed, speedUnit) : { error: "Provide either (speed and speedUnit) or targetSeconds" };
11959
+ if ("error" in result) return errText(result);
11960
+ return text(JSON.stringify(result, null, 2));
11961
+ }
11962
+ );
11848
11963
  var transport = new StdioServerTransport();
11849
11964
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.74.0",
3
+ "version": "0.75.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",