@utilix-tech/mcp 0.74.0 → 0.76.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 +7 -3
- package/dist/index.js +223 -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.76.0
|
|
6
|
+
- Add `detect_wordy_phrases` tool: scan text against a curated table of wordy/redundant phrases ("due to the fact that", "in order to") and flag each occurrence with a suggested concise replacement, an occurrence count, and character offsets.
|
|
7
|
+
- Add `check_oxford_comma_consistency` tool: scan a document for comma-before-and/or usage in three-or-more-item lists and flag documents that mix Oxford comma styles.
|
|
8
|
+
|
|
9
|
+
## 0.75.0
|
|
10
|
+
- 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.
|
|
11
|
+
- 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.
|
|
12
|
+
|
|
5
13
|
## 0.74.0
|
|
6
14
|
- 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
15
|
- 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
|
|
3
|
+
MCP server exposing 247 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
|
|
|
@@ -147,6 +147,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
147
147
|
| `detect_passive_voice` | Flag sentences written in passive voice, e.g. "was written", "were approved by the team" |
|
|
148
148
|
| `score_readability` | Score text with Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog Index |
|
|
149
149
|
| `detect_filler_words` | Scan text for filler words, hedge phrases, and clichés with per-phrase counts and a density score |
|
|
150
|
+
| `detect_wordy_phrases` | Scan text against a curated table of wordy/redundant phrases and suggest a concise replacement for each occurrence |
|
|
151
|
+
| `check_oxford_comma_consistency` | Scan a document for comma-before-and/or usage in three-or-more-item lists and flag documents that mix Oxford comma styles |
|
|
150
152
|
| `calculate_string_distance` | Compute the Levenshtein (or Damerau) edit distance and normalized similarity between two strings |
|
|
151
153
|
| `analyze_sentence_lengths` | Split text into sentences and report a histogram of sentence lengths, plus mean, median, standard deviation, and a consistency label |
|
|
152
154
|
| `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 |
|
|
@@ -162,7 +164,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
162
164
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
163
165
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
164
166
|
|
|
165
|
-
### Units (
|
|
167
|
+
### Units (45 tools)
|
|
166
168
|
| Tool | Description |
|
|
167
169
|
|------|-------------|
|
|
168
170
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -209,8 +211,9 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
209
211
|
| `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
212
|
| `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
213
|
| `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 |
|
|
214
|
+
| `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
215
|
|
|
213
|
-
### API / Network (
|
|
216
|
+
### API / Network (50 tools)
|
|
214
217
|
| Tool | Description |
|
|
215
218
|
|------|-------------|
|
|
216
219
|
| `decode_jwt` | Decode a JWT: header, payload, and expiry |
|
|
@@ -264,6 +267,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
264
267
|
| `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
268
|
| `parse_robots_meta_tag` | Parse an existing robots meta tag (or bare content value) into the same shape |
|
|
266
269
|
| `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 |
|
|
270
|
+
| `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
271
|
|
|
268
272
|
### Color (4 tools)
|
|
269
273
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -4781,6 +4781,114 @@ server.tool(
|
|
|
4781
4781
|
return text(JSON.stringify(_detectFillerWordsMcp(input), null, 2));
|
|
4782
4782
|
}
|
|
4783
4783
|
);
|
|
4784
|
+
var _WORDY_PHRASES_MCP = [
|
|
4785
|
+
["due to the fact that", "because"],
|
|
4786
|
+
["in spite of the fact that", "although"],
|
|
4787
|
+
["despite the fact that", "although"],
|
|
4788
|
+
["in order to", "to"],
|
|
4789
|
+
["for the purpose of", "for"],
|
|
4790
|
+
["in the event that", "if"],
|
|
4791
|
+
["in the near future", "soon"],
|
|
4792
|
+
["at this point in time", "now"],
|
|
4793
|
+
["at the present time", "now"],
|
|
4794
|
+
["on a daily basis", "daily"],
|
|
4795
|
+
["on a regular basis", "regularly"],
|
|
4796
|
+
["in a timely manner", "promptly"],
|
|
4797
|
+
["with regard to", "regarding"],
|
|
4798
|
+
["with respect to", "regarding"],
|
|
4799
|
+
["in regards to", "regarding"],
|
|
4800
|
+
["with the exception of", "except"],
|
|
4801
|
+
["in close proximity to", "near"],
|
|
4802
|
+
["take into consideration", "consider"],
|
|
4803
|
+
["give consideration to", "consider"],
|
|
4804
|
+
["make a decision", "decide"],
|
|
4805
|
+
["is able to", "can"],
|
|
4806
|
+
["are able to", "can"],
|
|
4807
|
+
["a large number of", "many"],
|
|
4808
|
+
["a majority of", "most"],
|
|
4809
|
+
["each and every", "each"],
|
|
4810
|
+
["first and foremost", "first"],
|
|
4811
|
+
["last but not least", "finally"],
|
|
4812
|
+
["null and void", "void"],
|
|
4813
|
+
["whether or not", "whether"],
|
|
4814
|
+
["the reason why", "the reason"],
|
|
4815
|
+
["end result", "result"],
|
|
4816
|
+
["future plans", "plans"],
|
|
4817
|
+
["past history", "history"],
|
|
4818
|
+
["basic fundamentals", "fundamentals"],
|
|
4819
|
+
["absolutely essential", "essential"],
|
|
4820
|
+
["it is important to note that", "(often removable)"],
|
|
4821
|
+
["as a matter of fact", "in fact"],
|
|
4822
|
+
["in the process of", "(often removable)"]
|
|
4823
|
+
];
|
|
4824
|
+
function _wordyRegexMcp(phrase) {
|
|
4825
|
+
const tokens = phrase.split(/\s+/).map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
4826
|
+
return new RegExp("\\b" + tokens.join("\\s+") + "\\b", "gi");
|
|
4827
|
+
}
|
|
4828
|
+
function _detectWordyPhrasesMcp(input) {
|
|
4829
|
+
const totalWords = (input.match(/[A-Za-z0-9']+/g) ?? []).length;
|
|
4830
|
+
const matches = [];
|
|
4831
|
+
for (const [phrase, suggestion] of _WORDY_PHRASES_MCP) {
|
|
4832
|
+
const re = _wordyRegexMcp(phrase);
|
|
4833
|
+
const indices = [];
|
|
4834
|
+
let m;
|
|
4835
|
+
while ((m = re.exec(input)) !== null) {
|
|
4836
|
+
indices.push(m.index);
|
|
4837
|
+
if (m.index === re.lastIndex) re.lastIndex++;
|
|
4838
|
+
}
|
|
4839
|
+
if (indices.length > 0) matches.push({ phrase, suggestion, count: indices.length, indices });
|
|
4840
|
+
}
|
|
4841
|
+
matches.sort((a, b) => b.count - a.count || a.phrase.localeCompare(b.phrase));
|
|
4842
|
+
const totalMatches = matches.reduce((s, m) => s + m.count, 0);
|
|
4843
|
+
const density = totalWords > 0 ? Math.round(totalMatches / totalWords * 1e3) / 10 : 0;
|
|
4844
|
+
return { totalWords, totalMatches, density, matches };
|
|
4845
|
+
}
|
|
4846
|
+
server.tool(
|
|
4847
|
+
"detect_wordy_phrases",
|
|
4848
|
+
'Scan text against a curated table of wordy/redundant phrases ("due to the fact that", "in order to", "at this point in time") and flag each occurrence with a suggested concise replacement, an occurrence count, and character offsets. Dictionary-based heuristic: a flagged phrase is a candidate to tighten, not always wrong in context.',
|
|
4849
|
+
{ text: z.string().describe("The text to scan for wordy/redundant phrases") },
|
|
4850
|
+
async ({ text: input }) => {
|
|
4851
|
+
return text(JSON.stringify(_detectWordyPhrasesMcp(input), null, 2));
|
|
4852
|
+
}
|
|
4853
|
+
);
|
|
4854
|
+
var _OXFORD_CONJ_RE_MCP = /(,)?\s+\b(?:and|or)\b\s+/gi;
|
|
4855
|
+
function _checkOxfordCommaConsistencyMcp(input) {
|
|
4856
|
+
const instances = [];
|
|
4857
|
+
_OXFORD_CONJ_RE_MCP.lastIndex = 0;
|
|
4858
|
+
let m;
|
|
4859
|
+
while ((m = _OXFORD_CONJ_RE_MCP.exec(input)) !== null) {
|
|
4860
|
+
const matchStart = m.index;
|
|
4861
|
+
const hasCommaBefore = !!m[1];
|
|
4862
|
+
let clauseStart = -1;
|
|
4863
|
+
for (const ch of [".", "!", "?", "\n"]) {
|
|
4864
|
+
const idx = input.lastIndexOf(ch, matchStart - 1);
|
|
4865
|
+
if (idx > clauseStart) clauseStart = idx;
|
|
4866
|
+
}
|
|
4867
|
+
clauseStart += 1;
|
|
4868
|
+
const clause = input.slice(clauseStart, matchStart);
|
|
4869
|
+
const commaCount = (clause.match(/,/g) ?? []).length;
|
|
4870
|
+
if (commaCount < 1) continue;
|
|
4871
|
+
const snippetStart = Math.max(clauseStart, matchStart - 60);
|
|
4872
|
+
const afterConjEnd = _OXFORD_CONJ_RE_MCP.lastIndex;
|
|
4873
|
+
const tail = input.slice(afterConjEnd);
|
|
4874
|
+
const boundaryOffset = tail.search(/[.!?\n,]/);
|
|
4875
|
+
const snippetEnd = boundaryOffset === -1 ? Math.min(input.length, afterConjEnd + 40) : afterConjEnd + boundaryOffset;
|
|
4876
|
+
const snippet = input.slice(snippetStart, snippetEnd).trim();
|
|
4877
|
+
instances.push({ snippet, hasOxfordComma: hasCommaBefore, index: matchStart });
|
|
4878
|
+
}
|
|
4879
|
+
const withOxfordComma = instances.filter((i) => i.hasOxfordComma).length;
|
|
4880
|
+
const withoutOxfordComma = instances.length - withOxfordComma;
|
|
4881
|
+
const consistent = withOxfordComma === 0 || withoutOxfordComma === 0;
|
|
4882
|
+
return { totalLists: instances.length, withOxfordComma, withoutOxfordComma, consistent, instances };
|
|
4883
|
+
}
|
|
4884
|
+
server.tool(
|
|
4885
|
+
"check_oxford_comma_consistency",
|
|
4886
|
+
"Scan a document for comma-before-and/or usage in three-or-more-item lists and flag inconsistency: some lists using the Oxford (serial) comma while others in the same document don't. A heuristic over punctuation and sentence boundaries, not a full grammar parser.",
|
|
4887
|
+
{ text: z.string().describe("The document to scan for Oxford comma consistency") },
|
|
4888
|
+
async ({ text: input }) => {
|
|
4889
|
+
return text(JSON.stringify(_checkOxfordCommaConsistencyMcp(input), null, 2));
|
|
4890
|
+
}
|
|
4891
|
+
);
|
|
4784
4892
|
function _calculatePercentageChangeMcp(oldValue, newValue) {
|
|
4785
4893
|
if (!Number.isFinite(oldValue)) return { error: "Original value must be a finite number" };
|
|
4786
4894
|
if (!Number.isFinite(newValue)) return { error: "New value must be a finite number" };
|
|
@@ -11845,5 +11953,120 @@ server.tool(
|
|
|
11845
11953
|
return text(JSON.stringify(result, null, 2));
|
|
11846
11954
|
}
|
|
11847
11955
|
);
|
|
11956
|
+
var _WEBHOOK_HASH_ALGORITHMS_MCP = ["sha1", "sha256", "sha512"];
|
|
11957
|
+
function _webhookConstantTimeEqualMcp(a, b) {
|
|
11958
|
+
if (a.length !== b.length) return false;
|
|
11959
|
+
let diff = 0;
|
|
11960
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
11961
|
+
return diff === 0;
|
|
11962
|
+
}
|
|
11963
|
+
function _verifyWebhookSignatureMcp(payload, secret, signature, algorithm) {
|
|
11964
|
+
if (!payload) return { error: "Payload is required" };
|
|
11965
|
+
if (!secret) return { error: "Secret is required" };
|
|
11966
|
+
if (!signature || !signature.trim()) return { error: "Signature is required" };
|
|
11967
|
+
if (!_WEBHOOK_HASH_ALGORITHMS_MCP.includes(algorithm)) {
|
|
11968
|
+
return { error: "Algorithm must be one of sha1, sha256, sha512" };
|
|
11969
|
+
}
|
|
11970
|
+
const trimmedSignature = signature.trim();
|
|
11971
|
+
const prefixMatch = trimmedSignature.match(/^(sha1|sha256|sha512)=(.+)$/i);
|
|
11972
|
+
const providedSignature = prefixMatch ? prefixMatch[2].trim() : trimmedSignature;
|
|
11973
|
+
const expectedHex = createHmac(algorithm, secret).update(payload, "utf8").digest("hex");
|
|
11974
|
+
const expectedBase64 = createHmac(algorithm, secret).update(payload, "utf8").digest("base64");
|
|
11975
|
+
let matchedEncoding = null;
|
|
11976
|
+
if (_webhookConstantTimeEqualMcp(providedSignature.toLowerCase(), expectedHex.toLowerCase())) {
|
|
11977
|
+
matchedEncoding = "hex";
|
|
11978
|
+
} else if (_webhookConstantTimeEqualMcp(providedSignature, expectedBase64)) {
|
|
11979
|
+
matchedEncoding = "base64";
|
|
11980
|
+
}
|
|
11981
|
+
return {
|
|
11982
|
+
valid: matchedEncoding !== null,
|
|
11983
|
+
algorithm,
|
|
11984
|
+
expectedHex,
|
|
11985
|
+
expectedBase64,
|
|
11986
|
+
providedSignature: trimmedSignature,
|
|
11987
|
+
matchedEncoding
|
|
11988
|
+
};
|
|
11989
|
+
}
|
|
11990
|
+
server.tool(
|
|
11991
|
+
"verify_webhook_signature",
|
|
11992
|
+
'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.',
|
|
11993
|
+
{
|
|
11994
|
+
payload: z.string().describe("The raw webhook request body, exactly as received (do not re-serialize JSON)"),
|
|
11995
|
+
secret: z.string().describe("The shared webhook signing secret"),
|
|
11996
|
+
signature: z.string().describe('The signature to verify, with or without a "sha256=" style prefix, hex or base64 encoded'),
|
|
11997
|
+
algorithm: z.enum(["sha1", "sha256", "sha512"]).optional().describe("The HMAC hash algorithm the signature was computed with (default sha256)")
|
|
11998
|
+
},
|
|
11999
|
+
async ({ payload, secret, signature, algorithm }) => {
|
|
12000
|
+
const result = _verifyWebhookSignatureMcp(payload, secret, signature, algorithm ?? "sha256");
|
|
12001
|
+
if ("error" in result) return errText(result);
|
|
12002
|
+
return text(JSON.stringify(result, null, 2));
|
|
12003
|
+
}
|
|
12004
|
+
);
|
|
12005
|
+
var _FILE_SIZE_UNIT_BYTES_MCP = { B: 1, KB: 1e3, MB: 1e6, GB: 1e9, TB: 1e12 };
|
|
12006
|
+
var _SPEED_UNIT_BITS_PER_SECOND_MCP = {
|
|
12007
|
+
Kbps: 1e3,
|
|
12008
|
+
Mbps: 1e6,
|
|
12009
|
+
Gbps: 1e9,
|
|
12010
|
+
KBps: 1e3 * 8,
|
|
12011
|
+
MBps: 1e6 * 8,
|
|
12012
|
+
GBps: 1e9 * 8
|
|
12013
|
+
};
|
|
12014
|
+
function _formatDurationSecondsMcp(totalSeconds) {
|
|
12015
|
+
if (totalSeconds < 1) return `${(totalSeconds * 1e3).toFixed(0)}ms`;
|
|
12016
|
+
if (totalSeconds < 60) {
|
|
12017
|
+
const decimals = totalSeconds < 10 && totalSeconds % 1 !== 0 ? 1 : 0;
|
|
12018
|
+
return `${totalSeconds.toFixed(decimals)}s`;
|
|
12019
|
+
}
|
|
12020
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
12021
|
+
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
12022
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
12023
|
+
const seconds = Math.round(totalSeconds % 60);
|
|
12024
|
+
const parts = [];
|
|
12025
|
+
if (days > 0) parts.push(`${days}d`);
|
|
12026
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
12027
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
12028
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
|
|
12029
|
+
return parts.join(" ");
|
|
12030
|
+
}
|
|
12031
|
+
function _calculateTransferTimeMcp(fileSize, fileSizeUnit, speed, speedUnit) {
|
|
12032
|
+
if (!Number.isFinite(fileSize) || fileSize <= 0) return { error: "File size must be a positive number" };
|
|
12033
|
+
if (!Number.isFinite(speed) || speed <= 0) return { error: "Connection speed must be a positive number" };
|
|
12034
|
+
if (!(fileSizeUnit in _FILE_SIZE_UNIT_BYTES_MCP)) return { error: `Unsupported file size unit: ${fileSizeUnit}` };
|
|
12035
|
+
if (!(speedUnit in _SPEED_UNIT_BITS_PER_SECOND_MCP)) return { error: `Unsupported speed unit: ${speedUnit}` };
|
|
12036
|
+
const fileSizeBytes = fileSize * _FILE_SIZE_UNIT_BYTES_MCP[fileSizeUnit];
|
|
12037
|
+
const speedBitsPerSecond = speed * _SPEED_UNIT_BITS_PER_SECOND_MCP[speedUnit];
|
|
12038
|
+
const transferTimeSeconds = fileSizeBytes * 8 / speedBitsPerSecond;
|
|
12039
|
+
return { fileSizeBytes, speedBitsPerSecond, transferTimeSeconds, formattedTime: _formatDurationSecondsMcp(transferTimeSeconds) };
|
|
12040
|
+
}
|
|
12041
|
+
function _calculateRequiredBandwidthMcp(fileSize, fileSizeUnit, targetSeconds) {
|
|
12042
|
+
if (!Number.isFinite(fileSize) || fileSize <= 0) return { error: "File size must be a positive number" };
|
|
12043
|
+
if (!Number.isFinite(targetSeconds) || targetSeconds <= 0) return { error: "Target time must be a positive number of seconds" };
|
|
12044
|
+
if (!(fileSizeUnit in _FILE_SIZE_UNIT_BYTES_MCP)) return { error: `Unsupported file size unit: ${fileSizeUnit}` };
|
|
12045
|
+
const fileSizeBytes = fileSize * _FILE_SIZE_UNIT_BYTES_MCP[fileSizeUnit];
|
|
12046
|
+
const requiredBitsPerSecond = fileSizeBytes * 8 / targetSeconds;
|
|
12047
|
+
return {
|
|
12048
|
+
fileSizeBytes,
|
|
12049
|
+
targetSeconds,
|
|
12050
|
+
requiredBitsPerSecond,
|
|
12051
|
+
requiredMbps: requiredBitsPerSecond / 1e6,
|
|
12052
|
+
requiredMBps: requiredBitsPerSecond / 8 / 1e6
|
|
12053
|
+
};
|
|
12054
|
+
}
|
|
12055
|
+
server.tool(
|
|
12056
|
+
"estimate_data_transfer_time",
|
|
12057
|
+
"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.",
|
|
12058
|
+
{
|
|
12059
|
+
fileSize: z.number().positive().describe("The file size, in fileSizeUnit"),
|
|
12060
|
+
fileSizeUnit: z.enum(["B", "KB", "MB", "GB", "TB"]).describe("The unit fileSize is expressed in"),
|
|
12061
|
+
speed: z.number().positive().optional().describe("The connection speed, in speedUnit (omit and pass targetSeconds for the reverse calculation)"),
|
|
12062
|
+
speedUnit: z.enum(["Kbps", "Mbps", "Gbps", "KBps", "MBps", "GBps"]).optional().describe("The unit speed is expressed in"),
|
|
12063
|
+
targetSeconds: z.number().positive().optional().describe("Target transfer time in seconds; when set, computes the required bandwidth instead of the transfer time")
|
|
12064
|
+
},
|
|
12065
|
+
async ({ fileSize, fileSizeUnit, speed, speedUnit, targetSeconds }) => {
|
|
12066
|
+
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" };
|
|
12067
|
+
if ("error" in result) return errText(result);
|
|
12068
|
+
return text(JSON.stringify(result, null, 2));
|
|
12069
|
+
}
|
|
12070
|
+
);
|
|
11848
12071
|
var transport = new StdioServerTransport();
|
|
11849
12072
|
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.76.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",
|