@utilix-tech/mcp 0.84.0 → 0.86.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 +8 -0
- package/README.md +8 -4
- package/dist/index.js +319 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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.86.0
|
|
6
|
+
- Add `calculate_sales_commission` tool: compute a flat or tiered (marginal) sales commission from a sale amount, where tiered mode applies each rate only to the slice of sales within that tier, plus an optional base salary added to the total payout.
|
|
7
|
+
- Add `build_character_class` tool: assemble a `[...]` regex character class from named building blocks (lowercase, uppercase, digits, whitespace), custom ranges, and literal custom characters, with optional negation.
|
|
8
|
+
|
|
9
|
+
## 0.85.0
|
|
10
|
+
- Add `calculate_credit_card_minimum_payment` tool: simulate paying only the minimum payment on a revolving credit card balance, where the minimum is the larger of a percent of the balance or a fixed dollar floor, and report months/years to payoff, total interest, and total paid.
|
|
11
|
+
- Add `check_dash_usage` tool: scan text for likely dash misuse: a hyphen surrounded by spaces, an unconverted double hyphen, and inconsistent spacing around em dashes across the same document.
|
|
12
|
+
|
|
5
13
|
## 0.84.0
|
|
6
14
|
- Add `find_cron_overlaps` tool: walk minute-by-minute over a window of days and flag every minute where two or more cron schedules fire at once, listing which expressions collided.
|
|
7
15
|
- Add `lint_dockerfile` tool: lint a Dockerfile for an unpinned or `:latest` base image, a final stage missing WORKDIR or USER, and ADD used on a plain local file where COPY would do.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 266 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
|
|
|
@@ -134,7 +134,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
134
134
|
| `generate_random_data` | Generate random mock data: names, emails, UUIDs, IPs, etc. |
|
|
135
135
|
| `generate_csp_nonce` | Generate a cryptographically random CSP nonce with ready-to-paste header directive and script/style tag snippets |
|
|
136
136
|
|
|
137
|
-
### Text (
|
|
137
|
+
### Text (17 tools)
|
|
138
138
|
| Tool | Description |
|
|
139
139
|
|------|-------------|
|
|
140
140
|
| `convert_case` | Convert text case: camelCase, snake_case, PascalCase, etc. |
|
|
@@ -155,6 +155,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
155
155
|
| `analyze_sentence_lengths` | Split text into sentences and report a histogram of sentence lengths, plus mean, median, standard deviation, and a consistency label |
|
|
156
156
|
| `lookup_emoji` | Search a curated table of ~280 common emoji by name, keyword, or GitHub-style shortcode, or look one up exactly by shortcode, character, or category |
|
|
157
157
|
| `analyze_keyword_density` | Count keyword/phrase frequency and density (occurrences per 100 words) across a block of text, with single-word or 2/3-word phrase modes |
|
|
158
|
+
| `check_dash_usage` | Scan text for likely dash misuse: a hyphen surrounded by spaces, an unconverted double hyphen, and inconsistent spacing around em dashes across the same document |
|
|
158
159
|
|
|
159
160
|
### Time (6 tools)
|
|
160
161
|
| Tool | Description |
|
|
@@ -167,7 +168,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
167
168
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
168
169
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
169
170
|
|
|
170
|
-
### Units (
|
|
171
|
+
### Units (48 tools)
|
|
171
172
|
| Tool | Description |
|
|
172
173
|
|------|-------------|
|
|
173
174
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -216,6 +217,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
216
217
|
| `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 |
|
|
217
218
|
| `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 |
|
|
218
219
|
| `calculate_real_rate_of_return` | Apply the Fisher equation to solve for whichever of the nominal rate, inflation rate, or inflation-adjusted real rate is missing, given exactly two of the three |
|
|
220
|
+
| `calculate_credit_card_minimum_payment` | Simulate paying only the minimum payment on a revolving credit card balance, where the minimum is the larger of a percent of the balance or a fixed dollar floor, and report months/years to payoff, total interest, and total paid |
|
|
221
|
+
| `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 |
|
|
219
222
|
|
|
220
223
|
### API / Network (51 tools)
|
|
221
224
|
| Tool | Description |
|
|
@@ -283,7 +286,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
283
286
|
| `color_palette` | Generate a color palette from a base color |
|
|
284
287
|
| `color_shades` | Generate shades or tints from a base color |
|
|
285
288
|
|
|
286
|
-
### Code (
|
|
289
|
+
### Code (27 tools)
|
|
287
290
|
| Tool | Description |
|
|
288
291
|
|------|-------------|
|
|
289
292
|
| `format_sql` | Format a SQL query with proper indentation |
|
|
@@ -312,6 +315,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
312
315
|
| `validate_fence_language_tags` | Scan a Markdown document's fenced code blocks and flag a missing, unknown, or inconsistently-cased language tag against a curated list of common syntax-highlighter identifiers |
|
|
313
316
|
| `count_sql_placeholders` | Count positional (`?`, `$1`) and named (`:name`, `@name`) parameter placeholders in a SQL query, flagging mixed styles and numbered-sequence gaps or duplicates |
|
|
314
317
|
| `extract_markdown_link_references` | Extract every reference-style Markdown link ([text][ref], [text][], ![alt][ref]) and match it against its [ref]: url definition, flagging unresolved references and unused definitions |
|
|
318
|
+
| `build_character_class` | Assemble a [...] regex character class from named building blocks (lowercase, uppercase, digits, whitespace), custom ranges, and literal custom characters, with optional negation |
|
|
315
319
|
|
|
316
320
|
### Data (16 tools)
|
|
317
321
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -13533,5 +13533,324 @@ server.tool(
|
|
|
13533
13533
|
return text(JSON.stringify(result, null, 2));
|
|
13534
13534
|
}
|
|
13535
13535
|
);
|
|
13536
|
+
var _CC_MIN_PAYMENT_MAX_MONTHS_MCP = 1200;
|
|
13537
|
+
function _calculateCreditCardMinimumPaymentMcp(balance, aprPercent, minPaymentPercent, minPaymentFloor) {
|
|
13538
|
+
if (!Number.isFinite(balance) || balance <= 0) {
|
|
13539
|
+
return { error: "balance must be a finite number greater than zero" };
|
|
13540
|
+
}
|
|
13541
|
+
if (!Number.isFinite(aprPercent) || aprPercent < 0) {
|
|
13542
|
+
return { error: "aprPercent must be a finite, non-negative number" };
|
|
13543
|
+
}
|
|
13544
|
+
if (!Number.isFinite(minPaymentPercent) || minPaymentPercent <= 0) {
|
|
13545
|
+
return { error: "minPaymentPercent must be a finite number greater than zero" };
|
|
13546
|
+
}
|
|
13547
|
+
if (!Number.isFinite(minPaymentFloor) || minPaymentFloor < 0) {
|
|
13548
|
+
return { error: "minPaymentFloor must be a finite, non-negative number" };
|
|
13549
|
+
}
|
|
13550
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
13551
|
+
const monthlyRate = aprPercent / 100 / 12;
|
|
13552
|
+
let bal = balance;
|
|
13553
|
+
let totalInterestPaid = 0;
|
|
13554
|
+
let totalPaid = 0;
|
|
13555
|
+
let firstMonthPayment = 0;
|
|
13556
|
+
let finalMonthPayment = 0;
|
|
13557
|
+
let month = 0;
|
|
13558
|
+
while (bal > 5e-3 && month < _CC_MIN_PAYMENT_MAX_MONTHS_MCP) {
|
|
13559
|
+
month++;
|
|
13560
|
+
const interest = bal * monthlyRate;
|
|
13561
|
+
bal += interest;
|
|
13562
|
+
totalInterestPaid += interest;
|
|
13563
|
+
let payment = Math.max(bal * (minPaymentPercent / 100), minPaymentFloor);
|
|
13564
|
+
if (payment > bal) payment = bal;
|
|
13565
|
+
bal -= payment;
|
|
13566
|
+
totalPaid += payment;
|
|
13567
|
+
finalMonthPayment = round2(payment);
|
|
13568
|
+
if (month === 1) firstMonthPayment = round2(payment);
|
|
13569
|
+
}
|
|
13570
|
+
if (bal > 5e-3) {
|
|
13571
|
+
return { error: `balance cannot be paid off within ${_CC_MIN_PAYMENT_MAX_MONTHS_MCP} months at this minimum-payment formula; increase minPaymentPercent or minPaymentFloor` };
|
|
13572
|
+
}
|
|
13573
|
+
return {
|
|
13574
|
+
monthsToPayoff: month,
|
|
13575
|
+
yearsToPayoff: round2(month / 12),
|
|
13576
|
+
totalInterestPaid: round2(totalInterestPaid),
|
|
13577
|
+
totalPaid: round2(totalPaid),
|
|
13578
|
+
firstMonthPayment,
|
|
13579
|
+
finalMonthPayment
|
|
13580
|
+
};
|
|
13581
|
+
}
|
|
13582
|
+
server.tool(
|
|
13583
|
+
"calculate_credit_card_minimum_payment",
|
|
13584
|
+
`Simulate paying only the minimum payment on a revolving credit card balance every month, where the minimum payment is whichever is larger of a percent of the current balance or a fixed dollar floor. Reports months and years to payoff, total interest, total paid, and the first vs. final month's payment amount, so the "minimum payment trap" becomes visible.`,
|
|
13585
|
+
{
|
|
13586
|
+
balance: z.number().positive().describe("Current revolving balance owed"),
|
|
13587
|
+
aprPercent: z.number().min(0).describe("Annual percentage rate, as a percent"),
|
|
13588
|
+
minPaymentPercent: z.number().positive().describe("Minimum payment as a percent of the current balance, as a percent"),
|
|
13589
|
+
minPaymentFloor: z.number().min(0).optional().describe("Fixed dollar floor for the minimum payment, default 0")
|
|
13590
|
+
},
|
|
13591
|
+
async ({ balance, aprPercent, minPaymentPercent, minPaymentFloor }) => {
|
|
13592
|
+
const result = _calculateCreditCardMinimumPaymentMcp(balance, aprPercent, minPaymentPercent, minPaymentFloor ?? 0);
|
|
13593
|
+
if ("error" in result) return errText(result);
|
|
13594
|
+
return text(JSON.stringify(result, null, 2));
|
|
13595
|
+
}
|
|
13596
|
+
);
|
|
13597
|
+
function _dashContextSnippetMcp(input, index, matchLen) {
|
|
13598
|
+
const start = Math.max(0, index - 20);
|
|
13599
|
+
const end = Math.min(input.length, index + matchLen + 20);
|
|
13600
|
+
const body = input.slice(start, end).replace(/\s+/g, " ").trim();
|
|
13601
|
+
return (start > 0 ? "\u2026" : "") + body + (end < input.length ? "\u2026" : "");
|
|
13602
|
+
}
|
|
13603
|
+
function _checkDashUsageMcp(input) {
|
|
13604
|
+
const issues = [];
|
|
13605
|
+
const enDashCount = (input.match(/–/g) ?? []).length;
|
|
13606
|
+
const hyphenCount = (input.match(/-/g) ?? []).length;
|
|
13607
|
+
const doubleHyphenMatches = [...input.matchAll(/-{2,}/g)];
|
|
13608
|
+
for (const m of doubleHyphenMatches) {
|
|
13609
|
+
const index = m.index ?? 0;
|
|
13610
|
+
issues.push({
|
|
13611
|
+
type: "double-hyphen",
|
|
13612
|
+
index,
|
|
13613
|
+
context: _dashContextSnippetMcp(input, index, m[0].length),
|
|
13614
|
+
message: `Found ${m[0].length} consecutive hyphens ("${m[0]}"); likely meant to be an em dash (\u2014) or en dash (\u2013).`
|
|
13615
|
+
});
|
|
13616
|
+
}
|
|
13617
|
+
const spacedHyphenMatches = [...input.matchAll(/(?<=\S) - (?=\S)/g)];
|
|
13618
|
+
for (const m of spacedHyphenMatches) {
|
|
13619
|
+
const index = m.index ?? 0;
|
|
13620
|
+
issues.push({
|
|
13621
|
+
type: "spaced-hyphen",
|
|
13622
|
+
index,
|
|
13623
|
+
context: _dashContextSnippetMcp(input, index, m[0].length),
|
|
13624
|
+
message: "A hyphen surrounded by spaces usually signals an intended em dash (\u2014), not a hyphenated compound word."
|
|
13625
|
+
});
|
|
13626
|
+
}
|
|
13627
|
+
const emDashOccurrences = [...input.matchAll(/—/g)];
|
|
13628
|
+
let spacedEm = 0;
|
|
13629
|
+
let unspacedEm = 0;
|
|
13630
|
+
for (const m of emDashOccurrences) {
|
|
13631
|
+
const idx = m.index ?? 0;
|
|
13632
|
+
const before = input[idx - 1];
|
|
13633
|
+
const after = input[idx + 1];
|
|
13634
|
+
if (before === " " && after === " ") spacedEm++;
|
|
13635
|
+
else if (before !== " " && after !== " ") unspacedEm++;
|
|
13636
|
+
}
|
|
13637
|
+
let dominantEmDashStyle = "none";
|
|
13638
|
+
let emDashSpacingConsistent = true;
|
|
13639
|
+
if (emDashOccurrences.length > 0) {
|
|
13640
|
+
dominantEmDashStyle = spacedEm >= unspacedEm ? "spaced" : "unspaced";
|
|
13641
|
+
emDashSpacingConsistent = spacedEm === 0 || unspacedEm === 0;
|
|
13642
|
+
if (!emDashSpacingConsistent) {
|
|
13643
|
+
for (const m of emDashOccurrences) {
|
|
13644
|
+
const idx = m.index ?? 0;
|
|
13645
|
+
const before = input[idx - 1];
|
|
13646
|
+
const after = input[idx + 1];
|
|
13647
|
+
const isSpaced = before === " " && after === " ";
|
|
13648
|
+
const matchesDominant = dominantEmDashStyle === "spaced" ? isSpaced : !isSpaced;
|
|
13649
|
+
if (!matchesDominant) {
|
|
13650
|
+
issues.push({
|
|
13651
|
+
type: "inconsistent-em-dash-spacing",
|
|
13652
|
+
index: idx,
|
|
13653
|
+
context: _dashContextSnippetMcp(input, idx, 1),
|
|
13654
|
+
message: `This em dash is ${isSpaced ? "spaced" : "unspaced"}, but the document mostly uses ${dominantEmDashStyle} em dashes.`
|
|
13655
|
+
});
|
|
13656
|
+
}
|
|
13657
|
+
}
|
|
13658
|
+
}
|
|
13659
|
+
}
|
|
13660
|
+
issues.sort((a, b) => a.index - b.index);
|
|
13661
|
+
return {
|
|
13662
|
+
emDashCount: emDashOccurrences.length,
|
|
13663
|
+
enDashCount,
|
|
13664
|
+
hyphenCount,
|
|
13665
|
+
doubleHyphenCount: doubleHyphenMatches.length,
|
|
13666
|
+
spacedHyphenCount: spacedHyphenMatches.length,
|
|
13667
|
+
emDashSpacingConsistent,
|
|
13668
|
+
dominantEmDashStyle,
|
|
13669
|
+
issues
|
|
13670
|
+
};
|
|
13671
|
+
}
|
|
13672
|
+
server.tool(
|
|
13673
|
+
"check_dash_usage",
|
|
13674
|
+
'Scan text for likely dash misuse: a hyphen surrounded by spaces where an em dash is probably intended ("word - word"), a double hyphen ("--") left unconverted, and inconsistent spacing around em dashes across the same document (mixing "word \u2014 word" and "word\u2014word"). Pure regex heuristic, not a grammar parser.',
|
|
13675
|
+
{ text: z.string().describe("The text to scan for dash usage issues") },
|
|
13676
|
+
async ({ text: input }) => {
|
|
13677
|
+
return text(JSON.stringify(_checkDashUsageMcp(input), null, 2));
|
|
13678
|
+
}
|
|
13679
|
+
);
|
|
13680
|
+
var _CommissionTierSchema = z.object({
|
|
13681
|
+
upTo: z.number().nullable().describe("Upper bound of this tier (exclusive). null means unbounded and is only valid on the last tier."),
|
|
13682
|
+
ratePercent: z.number().min(0).describe("Commission rate as a percent applied to the slice of salesAmount within this tier")
|
|
13683
|
+
});
|
|
13684
|
+
function _calculateSalesCommissionMcp(salesAmount, flatRatePercent, tiers, baseSalary) {
|
|
13685
|
+
if (!Number.isFinite(salesAmount) || salesAmount < 0) {
|
|
13686
|
+
return { error: "salesAmount must be a finite, non-negative number" };
|
|
13687
|
+
}
|
|
13688
|
+
if (!Number.isFinite(baseSalary) || baseSalary < 0) {
|
|
13689
|
+
return { error: "baseSalary must be a finite, non-negative number when provided" };
|
|
13690
|
+
}
|
|
13691
|
+
const hasFlat = flatRatePercent !== void 0;
|
|
13692
|
+
const hasTiers = tiers !== void 0;
|
|
13693
|
+
if (hasFlat === hasTiers) {
|
|
13694
|
+
return { error: "Provide exactly one of flatRatePercent or tiers, not both and not neither" };
|
|
13695
|
+
}
|
|
13696
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
13697
|
+
if (hasFlat) {
|
|
13698
|
+
if (!Number.isFinite(flatRatePercent) || flatRatePercent < 0) {
|
|
13699
|
+
return { error: "flatRatePercent must be a finite, non-negative number" };
|
|
13700
|
+
}
|
|
13701
|
+
const commission2 = salesAmount * flatRatePercent / 100;
|
|
13702
|
+
return {
|
|
13703
|
+
salesAmount: round4(salesAmount),
|
|
13704
|
+
mode: "flat",
|
|
13705
|
+
commission: round4(commission2),
|
|
13706
|
+
baseSalary: round4(baseSalary),
|
|
13707
|
+
totalPayout: round4(baseSalary + commission2)
|
|
13708
|
+
};
|
|
13709
|
+
}
|
|
13710
|
+
const tierList = tiers;
|
|
13711
|
+
if (tierList.length === 0) {
|
|
13712
|
+
return { error: "tiers must contain at least one entry" };
|
|
13713
|
+
}
|
|
13714
|
+
let previousUpTo = 0;
|
|
13715
|
+
for (let i = 0; i < tierList.length; i++) {
|
|
13716
|
+
const tier = tierList[i];
|
|
13717
|
+
const isLast = i === tierList.length - 1;
|
|
13718
|
+
if (!Number.isFinite(tier.ratePercent) || tier.ratePercent < 0) {
|
|
13719
|
+
return { error: `tiers[${i}].ratePercent must be a finite, non-negative number` };
|
|
13720
|
+
}
|
|
13721
|
+
if (tier.upTo === null) {
|
|
13722
|
+
if (!isLast) {
|
|
13723
|
+
return { error: `tiers[${i}].upTo may only be null on the last tier (an unbounded top tier)` };
|
|
13724
|
+
}
|
|
13725
|
+
} else {
|
|
13726
|
+
if (!Number.isFinite(tier.upTo) || tier.upTo <= previousUpTo) {
|
|
13727
|
+
return { error: `tiers[${i}].upTo must be a finite number greater than the previous tier's upTo (${previousUpTo})` };
|
|
13728
|
+
}
|
|
13729
|
+
previousUpTo = tier.upTo;
|
|
13730
|
+
}
|
|
13731
|
+
}
|
|
13732
|
+
let lowerBound = 0;
|
|
13733
|
+
let commission = 0;
|
|
13734
|
+
const breakdown = [];
|
|
13735
|
+
for (const tier of tierList) {
|
|
13736
|
+
const upper = tier.upTo === null ? Infinity : tier.upTo;
|
|
13737
|
+
const amountInRange = Math.min(Math.max(salesAmount - lowerBound, 0), upper - lowerBound);
|
|
13738
|
+
const commissionForRange = amountInRange * tier.ratePercent / 100;
|
|
13739
|
+
commission += commissionForRange;
|
|
13740
|
+
breakdown.push({
|
|
13741
|
+
rangeStart: round4(lowerBound),
|
|
13742
|
+
rangeEnd: tier.upTo === null ? null : round4(tier.upTo),
|
|
13743
|
+
ratePercent: tier.ratePercent,
|
|
13744
|
+
amountInRange: round4(amountInRange),
|
|
13745
|
+
commissionForRange: round4(commissionForRange)
|
|
13746
|
+
});
|
|
13747
|
+
lowerBound = upper;
|
|
13748
|
+
}
|
|
13749
|
+
return {
|
|
13750
|
+
salesAmount: round4(salesAmount),
|
|
13751
|
+
mode: "tiered",
|
|
13752
|
+
commission: round4(commission),
|
|
13753
|
+
baseSalary: round4(baseSalary),
|
|
13754
|
+
totalPayout: round4(baseSalary + commission),
|
|
13755
|
+
breakdown
|
|
13756
|
+
};
|
|
13757
|
+
}
|
|
13758
|
+
server.tool(
|
|
13759
|
+
"calculate_sales_commission",
|
|
13760
|
+
"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 the sale amount within it, the same way progressive tax brackets work. An optional base salary is added to the commission to get a total payout. Pure arithmetic, no live data.",
|
|
13761
|
+
{
|
|
13762
|
+
salesAmount: z.number().min(0).describe("The total sale amount commission is computed on"),
|
|
13763
|
+
flatRatePercent: z.number().min(0).optional().describe("A flat commission rate as a percent, applied to the whole salesAmount. Mutually exclusive with tiers."),
|
|
13764
|
+
tiers: z.array(_CommissionTierSchema).optional().describe("Marginal commission tiers, sorted ascending by upTo. Mutually exclusive with flatRatePercent."),
|
|
13765
|
+
baseSalary: z.number().min(0).optional().describe("Optional base salary added to the commission to get totalPayout, default 0")
|
|
13766
|
+
},
|
|
13767
|
+
async ({ salesAmount, flatRatePercent, tiers, baseSalary }) => {
|
|
13768
|
+
const result = _calculateSalesCommissionMcp(salesAmount, flatRatePercent, tiers, baseSalary ?? 0);
|
|
13769
|
+
if ("error" in result) return errText(result);
|
|
13770
|
+
return text(JSON.stringify(result, null, 2));
|
|
13771
|
+
}
|
|
13772
|
+
);
|
|
13773
|
+
var _CHAR_CLASS_SPECIAL_CHARS_MCP = /* @__PURE__ */ new Set(["]", "\\", "^", "-"]);
|
|
13774
|
+
function _escapeForCharClassMcp(ch) {
|
|
13775
|
+
return _CHAR_CLASS_SPECIAL_CHARS_MCP.has(ch) ? `\\${ch}` : ch;
|
|
13776
|
+
}
|
|
13777
|
+
var _CHAR_CLASS_RANGE_RE_MCP = /^(.)-(.)$/u;
|
|
13778
|
+
function _buildCharacterClassMcp(lowercase, uppercase, digits, whitespace, customRanges, customChars, negate) {
|
|
13779
|
+
const hasAny = lowercase || uppercase || digits || whitespace || customRanges.length > 0 || customChars.length > 0;
|
|
13780
|
+
if (!hasAny) {
|
|
13781
|
+
return { error: "At least one character set, custom range, or custom character must be specified" };
|
|
13782
|
+
}
|
|
13783
|
+
const parts = [];
|
|
13784
|
+
const includes = [];
|
|
13785
|
+
if (lowercase) {
|
|
13786
|
+
parts.push("a-z");
|
|
13787
|
+
includes.push("a-z: lowercase letters");
|
|
13788
|
+
}
|
|
13789
|
+
if (uppercase) {
|
|
13790
|
+
parts.push("A-Z");
|
|
13791
|
+
includes.push("A-Z: uppercase letters");
|
|
13792
|
+
}
|
|
13793
|
+
if (digits) {
|
|
13794
|
+
parts.push("0-9");
|
|
13795
|
+
includes.push("0-9: digits");
|
|
13796
|
+
}
|
|
13797
|
+
if (whitespace) {
|
|
13798
|
+
parts.push("\\s");
|
|
13799
|
+
includes.push("\\s: whitespace characters (space, tab, newline, etc.)");
|
|
13800
|
+
}
|
|
13801
|
+
for (let i = 0; i < customRanges.length; i++) {
|
|
13802
|
+
const range = customRanges[i];
|
|
13803
|
+
const match = _CHAR_CLASS_RANGE_RE_MCP.exec(range);
|
|
13804
|
+
if (!match) {
|
|
13805
|
+
return { error: `customRanges[${i}] ("${range}") must be exactly two characters joined by a hyphen, e.g. "f-k"` };
|
|
13806
|
+
}
|
|
13807
|
+
const [, start, end] = match;
|
|
13808
|
+
if (start.codePointAt(0) > end.codePointAt(0)) {
|
|
13809
|
+
return { error: `customRanges[${i}] ("${range}") has a start character that comes after its end character` };
|
|
13810
|
+
}
|
|
13811
|
+
parts.push(`${_escapeForCharClassMcp(start)}-${_escapeForCharClassMcp(end)}`);
|
|
13812
|
+
includes.push(`${range}: custom range from '${start}' to '${end}' inclusive`);
|
|
13813
|
+
}
|
|
13814
|
+
if (customChars.length > 0) {
|
|
13815
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13816
|
+
const unique = [];
|
|
13817
|
+
for (const ch of customChars) {
|
|
13818
|
+
if (!seen.has(ch)) {
|
|
13819
|
+
seen.add(ch);
|
|
13820
|
+
unique.push(ch);
|
|
13821
|
+
}
|
|
13822
|
+
}
|
|
13823
|
+
parts.push(unique.map(_escapeForCharClassMcp).join(""));
|
|
13824
|
+
includes.push(`custom characters: ${unique.join(", ")}`);
|
|
13825
|
+
}
|
|
13826
|
+
const pattern = `[${negate ? "^" : ""}${parts.join("")}]`;
|
|
13827
|
+
return { pattern, negate, includes };
|
|
13828
|
+
}
|
|
13829
|
+
server.tool(
|
|
13830
|
+
"build_character_class",
|
|
13831
|
+
"Assemble a [...] regex character class from named building blocks (lowercase, uppercase, digits, whitespace), custom character ranges, and literal custom characters, with optional negation. Pure string assembly, it never runs the resulting pattern against any input.",
|
|
13832
|
+
{
|
|
13833
|
+
lowercase: z.boolean().optional().describe("Include a-z"),
|
|
13834
|
+
uppercase: z.boolean().optional().describe("Include A-Z"),
|
|
13835
|
+
digits: z.boolean().optional().describe("Include 0-9"),
|
|
13836
|
+
whitespace: z.boolean().optional().describe("Include \\s (space, tab, newline, etc.)"),
|
|
13837
|
+
customRanges: z.array(z.string()).optional().describe('Custom ranges like "f-k", each exactly two characters joined by a hyphen'),
|
|
13838
|
+
customChars: z.string().optional().describe("Literal characters to include as-is; duplicates are ignored"),
|
|
13839
|
+
negate: z.boolean().optional().describe("Wrap the class in [^...] to match any character NOT in the set")
|
|
13840
|
+
},
|
|
13841
|
+
async ({ lowercase, uppercase, digits, whitespace, customRanges, customChars, negate }) => {
|
|
13842
|
+
const result = _buildCharacterClassMcp(
|
|
13843
|
+
lowercase ?? false,
|
|
13844
|
+
uppercase ?? false,
|
|
13845
|
+
digits ?? false,
|
|
13846
|
+
whitespace ?? false,
|
|
13847
|
+
customRanges ?? [],
|
|
13848
|
+
customChars ?? "",
|
|
13849
|
+
negate ?? false
|
|
13850
|
+
);
|
|
13851
|
+
if ("error" in result) return errText(result);
|
|
13852
|
+
return text(JSON.stringify(result, null, 2));
|
|
13853
|
+
}
|
|
13854
|
+
);
|
|
13536
13855
|
var transport = new StdioServerTransport();
|
|
13537
13856
|
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.86.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",
|