@utilix-tech/mcp 0.60.0 → 0.62.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,13 @@
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.62.0
6
+ - 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
+ - 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.
8
+
9
+ ## 0.61.0
10
+ - Add `calculate_extra_payment_payoff` tool: 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.
11
+
5
12
  ## 0.60.0
6
13
  - Add `build_retry_after_header`, `parse_retry_after_header` tools: build an HTTP Retry-After response header from a delay in seconds or a target date, or parse an existing one, per RFC 9110.
7
14
  - Add `calculate_freelancer_rate` tool: 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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 211 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 216 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 (36 tools)
165
+ ### Units (37 tools)
166
166
  | Tool | Description |
167
167
  |------|-------------|
168
168
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -201,8 +201,9 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
201
201
  | `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 |
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
+ | `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 |
204
205
 
205
- ### API / Network (33 tools)
206
+ ### API / Network (37 tools)
206
207
  | Tool | Description |
207
208
  |------|-------------|
208
209
  | `decode_jwt` | Decode a JWT: header, payload, and expiry |
@@ -240,6 +241,10 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
240
241
  | `parse_prefer_header` | Parse an existing Prefer header value into its list of preferences, values, and parameters |
241
242
  | `build_retry_after_header` | Build a Retry-After response header from a delay in seconds or a target date, per RFC 9110 |
242
243
  | `parse_retry_after_header` | Parse an existing Retry-After header value into the same shape |
244
+ | `build_priority_header` | Build an HTTP Priority request/response-header value from an urgency (0-7) and/or an incremental flag, per RFC 9218 |
245
+ | `parse_priority_header` | Parse an existing Priority header value into its urgency and incremental fields, with spec defaults applied |
246
+ | `lookup_close_code` | Look up what a WebSocket close code means, per RFC 6455 and the IANA reserved ranges |
247
+ | `list_close_codes` | List every well-known WebSocket close code (1000-1015) |
243
248
 
244
249
  ### Color (4 tools)
245
250
  | Tool | Description |
package/dist/index.js CHANGED
@@ -2091,6 +2091,55 @@ server.tool(
2091
2091
  return text(JSON.stringify(result, null, 2));
2092
2092
  }
2093
2093
  );
2094
+ function _calculateExtraPaymentPayoff(principal, annualRatePercent, termMonths, extraMonthlyPayment) {
2095
+ const baseline = _calculateLoan(principal, annualRatePercent, termMonths);
2096
+ if ("error" in baseline) return baseline;
2097
+ if (!Number.isFinite(extraMonthlyPayment) || extraMonthlyPayment < 0) {
2098
+ return { error: "Extra monthly payment must be zero or a positive number" };
2099
+ }
2100
+ const monthlyRate = annualRatePercent / 100 / 12;
2101
+ const payment = _roundMoneyMcp(baseline.monthlyPayment) + _roundMoneyMcp(extraMonthlyPayment);
2102
+ let balance = _roundMoneyMcp(principal);
2103
+ let totalInterestPaid = 0;
2104
+ let payoffMonths = termMonths;
2105
+ for (let month = 1; month <= termMonths; month++) {
2106
+ const interest = monthlyRate === 0 ? 0 : _roundMoneyMcp(balance * monthlyRate);
2107
+ let principalPortion = payment - interest;
2108
+ if (principalPortion > balance) principalPortion = balance;
2109
+ totalInterestPaid += interest;
2110
+ balance = _roundMoneyMcp(balance - principalPortion);
2111
+ if (balance <= 0) {
2112
+ payoffMonths = month;
2113
+ break;
2114
+ }
2115
+ }
2116
+ const baselineTotalInterest = baseline.totalInterest;
2117
+ return {
2118
+ monthlyPayment: _roundMoneyMcp(baseline.monthlyPayment),
2119
+ extraMonthlyPayment: _roundMoneyMcp(extraMonthlyPayment),
2120
+ baselinePayoffMonths: termMonths,
2121
+ baselineTotalInterest,
2122
+ newPayoffMonths: payoffMonths,
2123
+ newTotalInterest: _roundMoneyMcp(totalInterestPaid),
2124
+ monthsSaved: termMonths - payoffMonths,
2125
+ interestSaved: _roundMoneyMcp(baselineTotalInterest - totalInterestPaid)
2126
+ };
2127
+ }
2128
+ server.tool(
2129
+ "calculate_extra_payment_payoff",
2130
+ "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 on top of the standard amortized payment.",
2131
+ {
2132
+ principal: z.number().describe("Loan principal amount"),
2133
+ annualRatePercent: z.number().describe("Annual interest rate as a percent, e.g. 6.5"),
2134
+ termMonths: z.number().int().describe("Original loan term in months"),
2135
+ extraMonthlyPayment: z.number().describe("Fixed extra amount added to every monthly payment")
2136
+ },
2137
+ async ({ principal, annualRatePercent, termMonths, extraMonthlyPayment }) => {
2138
+ const result = _calculateExtraPaymentPayoff(principal, annualRatePercent, termMonths, extraMonthlyPayment);
2139
+ if ("error" in result) return errText(result);
2140
+ return text(JSON.stringify(result, null, 2));
2141
+ }
2142
+ );
2094
2143
  var _BARCODE_FORMAT_BY_LENGTH = { 8: "EAN-8", 12: "UPC-A", 13: "EAN-13" };
2095
2144
  function _computeBarcodeCheckDigit(dataDigits) {
2096
2145
  let sum = 0;
@@ -9800,5 +9849,124 @@ server.tool(
9800
9849
  return text(JSON.stringify(result, null, 2));
9801
9850
  }
9802
9851
  );
9852
+ function _isValidUrgencyMcp(n) {
9853
+ return typeof n === "number" && Number.isInteger(n) && n >= 0 && n <= 7;
9854
+ }
9855
+ function _buildPriorityHeaderMcp(input) {
9856
+ const { urgency, incremental } = input;
9857
+ if (urgency === void 0 && incremental === void 0) {
9858
+ return { error: "Provide at least one of urgency or incremental" };
9859
+ }
9860
+ if (urgency !== void 0 && !_isValidUrgencyMcp(urgency)) {
9861
+ return { error: "urgency must be an integer between 0 and 7" };
9862
+ }
9863
+ const parts = [];
9864
+ if (urgency !== void 0) parts.push(`u=${urgency}`);
9865
+ if (incremental === true) parts.push("i");
9866
+ else if (incremental === false) parts.push("i=?0");
9867
+ const headerValue = parts.join(", ");
9868
+ return { header: `Priority: ${headerValue}`, headerValue, urgency: urgency ?? 3, incremental: incremental ?? false };
9869
+ }
9870
+ function _parsePriorityHeaderMcp(raw) {
9871
+ if (!raw || raw.trim() === "") return { error: "Input is empty" };
9872
+ const withoutLabel = raw.trim().replace(/^priority:\s*/i, "");
9873
+ const parts = withoutLabel.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
9874
+ if (parts.length === 0) return { error: "Input is empty" };
9875
+ let urgency = 3;
9876
+ let incremental = false;
9877
+ for (const part of parts) {
9878
+ const eq = part.indexOf("=");
9879
+ const key = (eq === -1 ? part : part.slice(0, eq)).trim();
9880
+ const rawValue = eq === -1 ? void 0 : part.slice(eq + 1).trim();
9881
+ if (key === "u") {
9882
+ if (rawValue === void 0 || !/^\d+$/.test(rawValue)) return { error: `Invalid urgency value in "${part}"` };
9883
+ const n = Number(rawValue);
9884
+ if (n < 0 || n > 7) return { error: `urgency must be between 0 and 7, got ${n}` };
9885
+ urgency = n;
9886
+ } else if (key === "i") {
9887
+ if (rawValue === void 0 || rawValue === "?1") incremental = true;
9888
+ else if (rawValue === "?0") incremental = false;
9889
+ else return { error: `Invalid incremental value in "${part}"` };
9890
+ }
9891
+ }
9892
+ const headerValue = incremental ? `u=${urgency}, i` : `u=${urgency}, i=?0`;
9893
+ return { header: `Priority: ${headerValue}`, headerValue, urgency, incremental };
9894
+ }
9895
+ server.tool(
9896
+ "build_priority_header",
9897
+ "Build a Priority request/response-header value from an urgency (0-7, default 3) and/or an incremental flag, per RFC 9218. At least one of the two must be given.",
9898
+ {
9899
+ urgency: z.number().int().min(0).max(7).optional().describe("Urgency from 0 (highest) to 7 (lowest), default 3"),
9900
+ incremental: z.boolean().optional().describe("Whether the response can be processed incrementally as it arrives, default false")
9901
+ },
9902
+ async ({ urgency, incremental }) => {
9903
+ const result = _buildPriorityHeaderMcp({ urgency, incremental });
9904
+ if ("error" in result) return errText(result);
9905
+ return text(JSON.stringify(result, null, 2));
9906
+ }
9907
+ );
9908
+ server.tool(
9909
+ "parse_priority_header",
9910
+ 'Parse an existing Priority header value (with or without a leading "Priority:" label) into its urgency and incremental fields, per RFC 9218. Missing fields fall back to their spec default (u=3, i=false).',
9911
+ {
9912
+ header: z.string().min(1).describe("The Priority header value to parse")
9913
+ },
9914
+ async ({ header }) => {
9915
+ const result = _parsePriorityHeaderMcp(header);
9916
+ if ("error" in result) return errText(result);
9917
+ return text(JSON.stringify(result, null, 2));
9918
+ }
9919
+ );
9920
+ var _WELL_KNOWN_CLOSE_CODES_MCP = {
9921
+ 1e3: { name: "Normal Closure", description: "The connection successfully completed the purpose for which it was created.", sentOnWire: true },
9922
+ 1001: { name: "Going Away", description: "An endpoint is going away, such as a server shutting down or a browser navigating away from the page.", sentOnWire: true },
9923
+ 1002: { name: "Protocol Error", description: "An endpoint is terminating the connection due to a protocol error.", sentOnWire: true },
9924
+ 1003: { name: "Unsupported Data", description: "An endpoint received a data type it cannot accept, such as a binary-only endpoint receiving a text message.", sentOnWire: true },
9925
+ 1004: { name: "Reserved", description: "Reserved. A meaning might be defined in the future.", sentOnWire: true },
9926
+ 1005: { name: "No Status Received", description: "A reserved value used internally by libraries to indicate no status code was actually present in the close frame. Must not be sent on the wire.", sentOnWire: false },
9927
+ 1006: { name: "Abnormal Closure", description: "A reserved value used internally by libraries to indicate the connection closed without a close frame, e.g. the TCP connection dropped. Must not be sent on the wire.", sentOnWire: false },
9928
+ 1007: { name: "Invalid frame payload data", description: "An endpoint is terminating the connection because it received data inconsistent with the message type, such as non-UTF-8 data in a text message.", sentOnWire: true },
9929
+ 1008: { name: "Policy Violation", description: "A generic status code used when no other code fits and the endpoint does not want to reveal further details, or a message violated its policy.", sentOnWire: true },
9930
+ 1009: { name: "Message Too Big", description: "An endpoint is terminating the connection because it received a message too large to process.", sentOnWire: true },
9931
+ 1010: { name: "Mandatory Extension", description: "A client is terminating the connection because the server did not negotiate one or more extensions the client required.", sentOnWire: true },
9932
+ 1011: { name: "Internal Error", description: "A server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.", sentOnWire: true },
9933
+ 1012: { name: "Service Restart", description: "The server is terminating the connection because it is restarting.", sentOnWire: true },
9934
+ 1013: { name: "Try Again Later", description: "The server is terminating the connection due to a temporary condition, such as being overloaded, and the client should reconnect later.", sentOnWire: true },
9935
+ 1014: { name: "Bad Gateway", description: "The server, acting as a gateway or proxy, received an invalid response from an upstream server it connected to while processing the request.", sentOnWire: true },
9936
+ 1015: { name: "TLS Handshake", description: "A reserved value used internally by libraries to indicate the TLS handshake failed. Must not be sent on the wire.", sentOnWire: false }
9937
+ };
9938
+ function _lookupCloseCodeMcp(code) {
9939
+ if (!Number.isInteger(code) || code < 0 || code > 65535) {
9940
+ return { error: "code must be an integer between 0 and 65535" };
9941
+ }
9942
+ const known = _WELL_KNOWN_CLOSE_CODES_MCP[code];
9943
+ if (known) return { code, name: known.name, description: known.description, range: "standard", sentOnWire: known.sentOnWire };
9944
+ if (code < 1e3) return { error: `${code} is not a valid WebSocket close code; codes below 1000 are not permitted on the wire` };
9945
+ if (code <= 2999) return { code, name: "Unassigned", description: "Reserved for future use by the WebSocket protocol specification. No meaning is defined yet.", range: "reserved-future", sentOnWire: true };
9946
+ if (code <= 3999) return { code, name: "Library/Framework Use", description: "Reserved for use by libraries, frameworks, and applications. Codes in this range are meant to be registered directly with IANA in the WebSocket Close Code Number Registry.", range: "library-framework", sentOnWire: true };
9947
+ if (code <= 4999) return { code, name: "Private Use", description: "Reserved for private use between a specific client and server implementation. These codes cannot be registered with IANA.", range: "private-use", sentOnWire: true };
9948
+ return { error: `${code} is outside the defined close code ranges (0-4999); codes 5000 and above have no assigned meaning` };
9949
+ }
9950
+ server.tool(
9951
+ "lookup_close_code",
9952
+ "Look up what a WebSocket close code means, per the RFC 6455 close code definitions and the IANA WebSocket Close Code Number Registry reserved ranges.",
9953
+ {
9954
+ code: z.number().int().min(0).max(65535).describe("The WebSocket close code to look up")
9955
+ },
9956
+ async ({ code }) => {
9957
+ const result = _lookupCloseCodeMcp(code);
9958
+ if ("error" in result) return errText(result);
9959
+ return text(JSON.stringify(result, null, 2));
9960
+ }
9961
+ );
9962
+ server.tool(
9963
+ "list_close_codes",
9964
+ "List every well-known WebSocket close code (1000-1015) defined directly by RFC 6455, in ascending order.",
9965
+ {},
9966
+ async () => {
9967
+ const codes = Object.entries(_WELL_KNOWN_CLOSE_CODES_MCP).map(([code, info]) => ({ code: Number(code), name: info.name, description: info.description, range: "standard", sentOnWire: info.sentOnWire })).sort((a, b) => a.code - b.code);
9968
+ return text(JSON.stringify({ codes }, null, 2));
9969
+ }
9970
+ );
9803
9971
  var transport = new StdioServerTransport();
9804
9972
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.60.0",
3
+ "version": "0.62.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",