@utilix-tech/mcp 0.25.0 → 0.27.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 +214 -0
- package/package.json +2 -2
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.27.0
|
|
6
|
+
- Add `compute_jwk_thumbprint` tool: RFC 7638 canonical thumbprint of a single JWK, hashed with SHA-256/384/512.
|
|
7
|
+
- Add `calculate_video_bitrate` tool: resulting file size from a video/audio bitrate and duration, or the required bitrate to hit a target file size.
|
|
8
|
+
|
|
9
|
+
## 0.26.0
|
|
10
|
+
- Add `calculate_break_even` tool: break-even unit count and revenue from fixed costs, variable cost per unit, and price per unit, plus profit and margin of safety at an optional sales volume.
|
|
11
|
+
- Add `calculate_string_distance` tool: Levenshtein edit distance and normalized similarity between two strings, with an optional Damerau (transposition) mode.
|
|
12
|
+
|
|
5
13
|
## 0.25.0
|
|
6
14
|
- Add `calculate_discount` tool: solve a sale price, amount saved, or discount percent from any one of them, or stack successive discounts where 20% then 10% is an effective 28% off.
|
|
7
15
|
- Add `convert_subtitles` tool: convert between SubRip (.srt) and WebVTT (.vtt) with an optional time shift to resync every cue.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 141 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
|
|
|
@@ -126,7 +126,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
126
126
|
| `generate_qr` | Generate a QR code as an SVG string |
|
|
127
127
|
| `generate_random_data` | Generate random mock data: names, emails, UUIDs, IPs, etc. |
|
|
128
128
|
|
|
129
|
-
### Text (
|
|
129
|
+
### Text (13 tools)
|
|
130
130
|
| Tool | Description |
|
|
131
131
|
|------|-------------|
|
|
132
132
|
| `convert_case` | Convert text case: camelCase, snake_case, PascalCase, etc. |
|
|
@@ -141,6 +141,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
141
141
|
| `detect_passive_voice` | Flag sentences written in passive voice, e.g. "was written", "were approved by the team" |
|
|
142
142
|
| `score_readability` | Score text with Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog Index |
|
|
143
143
|
| `detect_filler_words` | Scan text for filler words, hedge phrases, and clichés with per-phrase counts and a density score |
|
|
144
|
+
| `calculate_string_distance` | Compute the Levenshtein (or Damerau) edit distance and normalized similarity between two strings |
|
|
144
145
|
|
|
145
146
|
### Time (6 tools)
|
|
146
147
|
| Tool | Description |
|
|
@@ -152,7 +153,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
152
153
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
153
154
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
154
155
|
|
|
155
|
-
### Units (
|
|
156
|
+
### Units (19 tools)
|
|
156
157
|
| Tool | Description |
|
|
157
158
|
|------|-------------|
|
|
158
159
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -172,12 +173,15 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
172
173
|
| `calculate_margin_markup` | Given any two of cost, price, margin percent, and markup percent, solve for the other two plus gross profit |
|
|
173
174
|
| `calculate_discount` | Solve a sale price, amount saved, or discount percent from any one of them, or stack successive discounts in order |
|
|
174
175
|
| `convert_subtitles` | Convert subtitles between SubRip (.srt) and WebVTT (.vtt), with an optional time shift to resync every cue |
|
|
176
|
+
| `calculate_break_even` | Compute the break-even unit count and revenue from fixed costs, variable cost per unit, and price per unit |
|
|
177
|
+
| `calculate_video_bitrate` | Calculate a video's file size from a bitrate and duration, or the bitrate needed to hit a target file size |
|
|
175
178
|
|
|
176
|
-
### API / Network (
|
|
179
|
+
### API / Network (11 tools)
|
|
177
180
|
| Tool | Description |
|
|
178
181
|
|------|-------------|
|
|
179
182
|
| `decode_jwt` | Decode a JWT: header, payload, and expiry |
|
|
180
183
|
| `parse_jwks` | Parse a JWKS (or a single bare JWK): type, use, algorithm, key ID, and size per key |
|
|
184
|
+
| `compute_jwk_thumbprint` | Compute the RFC 7638 canonical thumbprint of a single JWK (SHA-256/384/512) |
|
|
181
185
|
| `build_curl` | Build a cURL command from method, URL, headers, and body |
|
|
182
186
|
| `curl_to_code` | Convert a cURL command to fetch, axios, Python, Go, PHP, or Ruby |
|
|
183
187
|
| `cors_headers` | Generate CORS response headers for a configuration |
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { createHash } from "crypto";
|
|
7
8
|
import {
|
|
8
9
|
parseJson,
|
|
9
10
|
minifyJson,
|
|
@@ -2826,6 +2827,61 @@ server.tool("parse_jwks", "Parse a JSON Web Key Set (or a single bare JWK) and s
|
|
|
2826
2827
|
if ("error" in result) return errText(result);
|
|
2827
2828
|
return text(JSON.stringify(result, null, 2));
|
|
2828
2829
|
});
|
|
2830
|
+
var _JWK_THUMBPRINT_MEMBERS = {
|
|
2831
|
+
oct: ["k", "kty"],
|
|
2832
|
+
RSA: ["e", "kty", "n"],
|
|
2833
|
+
EC: ["crv", "kty", "x", "y"],
|
|
2834
|
+
OKP: ["crv", "kty", "x"]
|
|
2835
|
+
};
|
|
2836
|
+
var _JWK_THUMBPRINT_NODE_HASH = {
|
|
2837
|
+
"SHA-256": "sha256",
|
|
2838
|
+
"SHA-384": "sha384",
|
|
2839
|
+
"SHA-512": "sha512"
|
|
2840
|
+
};
|
|
2841
|
+
function _computeJwkThumbprint(input, hashAlg) {
|
|
2842
|
+
const trimmed = input.trim();
|
|
2843
|
+
if (!trimmed) return { error: "No input provided" };
|
|
2844
|
+
const nodeHash = _JWK_THUMBPRINT_NODE_HASH[hashAlg];
|
|
2845
|
+
if (!nodeHash) return { error: 'hashAlg must be one of "SHA-256", "SHA-384", "SHA-512"' };
|
|
2846
|
+
let jwk;
|
|
2847
|
+
try {
|
|
2848
|
+
jwk = JSON.parse(trimmed);
|
|
2849
|
+
} catch (e) {
|
|
2850
|
+
return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
2851
|
+
}
|
|
2852
|
+
if (jwk === null || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
2853
|
+
return { error: "Input must be a single JWK object, not a JWKS set or array" };
|
|
2854
|
+
}
|
|
2855
|
+
const obj = jwk;
|
|
2856
|
+
const kty = obj.kty;
|
|
2857
|
+
if (typeof kty !== "string") return { error: 'JWK must have a "kty" (key type) member' };
|
|
2858
|
+
const members = _JWK_THUMBPRINT_MEMBERS[kty];
|
|
2859
|
+
if (!members) return { error: `Unsupported key type "${kty}": supported types are RSA, EC, oct, OKP` };
|
|
2860
|
+
const canonical = {};
|
|
2861
|
+
for (const m of members) {
|
|
2862
|
+
const v = obj[m];
|
|
2863
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
2864
|
+
return { error: `JWK is missing required member "${m}" for key type "${kty}"` };
|
|
2865
|
+
}
|
|
2866
|
+
canonical[m] = v;
|
|
2867
|
+
}
|
|
2868
|
+
const canonicalJson = "{" + members.map((m) => `${JSON.stringify(m)}:${JSON.stringify(canonical[m])}`).join(",") + "}";
|
|
2869
|
+
const thumbprint = createHash(nodeHash).update(canonicalJson, "utf8").digest("base64url");
|
|
2870
|
+
return { kty, hashAlg, members, canonicalJson, thumbprint };
|
|
2871
|
+
}
|
|
2872
|
+
server.tool(
|
|
2873
|
+
"compute_jwk_thumbprint",
|
|
2874
|
+
"Compute the RFC 7638 thumbprint of a single JWK: canonicalize its required members into lexicographically-ordered, whitespace-free JSON and hash it. A fingerprint for identifying/matching a key, not a signature or verification. Accepts a bare JWK only, not a JWKS set.",
|
|
2875
|
+
{
|
|
2876
|
+
input: z.string().describe("A single JWK object (not a JWKS set)"),
|
|
2877
|
+
hashAlg: z.enum(["SHA-256", "SHA-384", "SHA-512"]).default("SHA-256")
|
|
2878
|
+
},
|
|
2879
|
+
async ({ input, hashAlg }) => {
|
|
2880
|
+
const result = _computeJwkThumbprint(input, hashAlg);
|
|
2881
|
+
if ("error" in result) return errText(result);
|
|
2882
|
+
return text(JSON.stringify(result, null, 2));
|
|
2883
|
+
}
|
|
2884
|
+
);
|
|
2829
2885
|
var _WAV_AUDIO_FORMAT_LABELS = { 1: "PCM", 3: "IEEE float", 6: "A-law", 7: "mu-law", 65534: "Extensible" };
|
|
2830
2886
|
function _readWavAscii4(bytes, offset) {
|
|
2831
2887
|
return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
@@ -4342,5 +4398,163 @@ server.tool(
|
|
|
4342
4398
|
return text(JSON.stringify(result, null, 2));
|
|
4343
4399
|
}
|
|
4344
4400
|
);
|
|
4401
|
+
function _calculateBreakEvenMcp(fixedCosts, variableCostPerUnit, pricePerUnit, salesVolume) {
|
|
4402
|
+
if (!Number.isFinite(fixedCosts)) return { error: "fixedCosts must be a finite number" };
|
|
4403
|
+
if (!Number.isFinite(variableCostPerUnit)) return { error: "variableCostPerUnit must be a finite number" };
|
|
4404
|
+
if (!Number.isFinite(pricePerUnit)) return { error: "pricePerUnit must be a finite number" };
|
|
4405
|
+
if (fixedCosts < 0) return { error: "fixedCosts cannot be negative" };
|
|
4406
|
+
if (variableCostPerUnit < 0) return { error: "variableCostPerUnit cannot be negative" };
|
|
4407
|
+
if (pricePerUnit <= 0) return { error: "pricePerUnit must be greater than zero" };
|
|
4408
|
+
if (pricePerUnit <= variableCostPerUnit) {
|
|
4409
|
+
return { error: "pricePerUnit must be greater than variableCostPerUnit, otherwise every unit sold loses money and break-even is never reached" };
|
|
4410
|
+
}
|
|
4411
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
4412
|
+
const contributionMarginPerUnit = pricePerUnit - variableCostPerUnit;
|
|
4413
|
+
const contributionMarginRatio = contributionMarginPerUnit / pricePerUnit * 100;
|
|
4414
|
+
const breakEvenUnits = fixedCosts / contributionMarginPerUnit;
|
|
4415
|
+
const breakEvenUnitsRounded = Math.ceil(breakEvenUnits);
|
|
4416
|
+
const breakEvenRevenue = breakEvenUnits * pricePerUnit;
|
|
4417
|
+
const result = {
|
|
4418
|
+
fixedCosts: round4(fixedCosts),
|
|
4419
|
+
variableCostPerUnit: round4(variableCostPerUnit),
|
|
4420
|
+
pricePerUnit: round4(pricePerUnit),
|
|
4421
|
+
contributionMarginPerUnit: round4(contributionMarginPerUnit),
|
|
4422
|
+
contributionMarginRatio: round4(contributionMarginRatio),
|
|
4423
|
+
breakEvenUnits: round4(breakEvenUnits),
|
|
4424
|
+
breakEvenUnitsRounded,
|
|
4425
|
+
breakEvenRevenue: round4(breakEvenRevenue)
|
|
4426
|
+
};
|
|
4427
|
+
if (typeof salesVolume === "number" && Number.isFinite(salesVolume)) {
|
|
4428
|
+
if (salesVolume < 0) return { error: "salesVolume cannot be negative" };
|
|
4429
|
+
const marginOfSafetyUnits = salesVolume - breakEvenUnits;
|
|
4430
|
+
result.salesVolume = round4(salesVolume);
|
|
4431
|
+
result.revenueAtSalesVolume = round4(salesVolume * pricePerUnit);
|
|
4432
|
+
result.profitAtSalesVolume = round4(salesVolume * contributionMarginPerUnit - fixedCosts);
|
|
4433
|
+
result.marginOfSafetyUnits = round4(marginOfSafetyUnits);
|
|
4434
|
+
result.marginOfSafetyPercent = salesVolume > 0 ? round4(marginOfSafetyUnits / salesVolume * 100) : 0;
|
|
4435
|
+
}
|
|
4436
|
+
return result;
|
|
4437
|
+
}
|
|
4438
|
+
server.tool(
|
|
4439
|
+
"calculate_break_even",
|
|
4440
|
+
"From fixed costs, the variable cost per unit, and the selling price per unit, compute the break-even point: the unit count and revenue at which profit is exactly zero. The price must exceed the variable cost, since otherwise every unit sold loses money and break-even is never reached no matter the volume. Optionally pass a sales volume to also get the profit (or loss) at that volume and the margin of safety above break-even. Pure arithmetic, no live cost/pricing feed.",
|
|
4441
|
+
{
|
|
4442
|
+
fixedCosts: z.number().describe("Total fixed costs for the period (cannot be negative)"),
|
|
4443
|
+
variableCostPerUnit: z.number().describe("Variable cost to produce one unit (cannot be negative)"),
|
|
4444
|
+
pricePerUnit: z.number().describe("Selling price per unit (must be greater than variableCostPerUnit)"),
|
|
4445
|
+
salesVolume: z.number().optional().describe("Optional actual/planned unit sales, to compute profit and margin of safety at that volume")
|
|
4446
|
+
},
|
|
4447
|
+
async ({ fixedCosts, variableCostPerUnit, pricePerUnit, salesVolume }) => {
|
|
4448
|
+
const result = _calculateBreakEvenMcp(fixedCosts, variableCostPerUnit, pricePerUnit, salesVolume);
|
|
4449
|
+
if ("error" in result) return errText(result);
|
|
4450
|
+
return text(JSON.stringify(result, null, 2));
|
|
4451
|
+
}
|
|
4452
|
+
);
|
|
4453
|
+
function _calculateStringDistanceMcp(a, b, mode, caseSensitive) {
|
|
4454
|
+
const MAX_LEN = 5e3;
|
|
4455
|
+
if (a.length > MAX_LEN || b.length > MAX_LEN) return { error: `Strings must be at most ${MAX_LEN} characters` };
|
|
4456
|
+
const s1 = caseSensitive ? a : a.toLowerCase();
|
|
4457
|
+
const s2 = caseSensitive ? b : b.toLowerCase();
|
|
4458
|
+
const m = s1.length;
|
|
4459
|
+
const n = s2.length;
|
|
4460
|
+
const maxLength = Math.max(m, n);
|
|
4461
|
+
if (m === 0 || n === 0) {
|
|
4462
|
+
return { a, b, distance: maxLength, similarity: maxLength === 0 ? 1 : 0, maxLength, mode };
|
|
4463
|
+
}
|
|
4464
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
4465
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
4466
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
4467
|
+
for (let i = 1; i <= m; i++) {
|
|
4468
|
+
for (let j = 1; j <= n; j++) {
|
|
4469
|
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
4470
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
4471
|
+
if (mode === "damerau" && i > 1 && j > 1 && s1[i - 1] === s2[j - 2] && s1[i - 2] === s2[j - 1]) {
|
|
4472
|
+
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
const distance = dp[m][n];
|
|
4477
|
+
const similarity = maxLength === 0 ? 1 : Math.round((1 - distance / maxLength) * 1e4) / 1e4;
|
|
4478
|
+
return { a, b, distance, similarity, maxLength, mode };
|
|
4479
|
+
}
|
|
4480
|
+
server.tool(
|
|
4481
|
+
"calculate_string_distance",
|
|
4482
|
+
'Compute the Levenshtein edit distance between two strings: the minimum number of single-character insertions, deletions, and substitutions needed to turn a into b, plus a normalized 0-1 similarity ratio. Pass mode "damerau" to also count an adjacent-character transposition as a single edit (optimal-string-alignment variant, not unrestricted Damerau-Levenshtein).',
|
|
4483
|
+
{
|
|
4484
|
+
a: z.string().describe("The first string (max 5000 characters)"),
|
|
4485
|
+
b: z.string().describe("The second string (max 5000 characters)"),
|
|
4486
|
+
mode: z.enum(["levenshtein", "damerau"]).default("levenshtein").describe("levenshtein counts a transposition as 2 edits; damerau counts it as 1"),
|
|
4487
|
+
caseSensitive: z.boolean().default(true)
|
|
4488
|
+
},
|
|
4489
|
+
async ({ a, b, mode, caseSensitive }) => {
|
|
4490
|
+
const result = _calculateStringDistanceMcp(a, b, mode, caseSensitive);
|
|
4491
|
+
if ("error" in result) return errText(result);
|
|
4492
|
+
return text(JSON.stringify(result, null, 2));
|
|
4493
|
+
}
|
|
4494
|
+
);
|
|
4495
|
+
function _calculateVideoBitrateMcp(mode, durationSeconds, videoBitrateKbps, audioBitrateKbps, targetSizeMB) {
|
|
4496
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
|
|
4497
|
+
return { error: "durationSeconds must be a positive number" };
|
|
4498
|
+
}
|
|
4499
|
+
if (!Number.isFinite(audioBitrateKbps) || audioBitrateKbps < 0) {
|
|
4500
|
+
return { error: "audioBitrateKbps must be zero or a positive number" };
|
|
4501
|
+
}
|
|
4502
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
4503
|
+
if (mode === "size-from-bitrate") {
|
|
4504
|
+
if (typeof videoBitrateKbps !== "number" || !Number.isFinite(videoBitrateKbps) || videoBitrateKbps <= 0) {
|
|
4505
|
+
return { error: 'videoBitrateKbps must be a positive number for mode "size-from-bitrate"' };
|
|
4506
|
+
}
|
|
4507
|
+
const totalBitrateKbps = videoBitrateKbps + audioBitrateKbps;
|
|
4508
|
+
const fileSizeBytes = totalBitrateKbps * 1e3 * durationSeconds / 8;
|
|
4509
|
+
return {
|
|
4510
|
+
mode,
|
|
4511
|
+
durationSeconds,
|
|
4512
|
+
videoBitrateKbps: round2(videoBitrateKbps),
|
|
4513
|
+
audioBitrateKbps: round2(audioBitrateKbps),
|
|
4514
|
+
totalBitrateKbps: round2(totalBitrateKbps),
|
|
4515
|
+
fileSizeBytes: Math.round(fileSizeBytes),
|
|
4516
|
+
fileSizeMB: round2(fileSizeBytes / 1e6),
|
|
4517
|
+
fileSizeMiB: round2(fileSizeBytes / 1048576)
|
|
4518
|
+
};
|
|
4519
|
+
}
|
|
4520
|
+
if (mode === "bitrate-from-size") {
|
|
4521
|
+
if (typeof targetSizeMB !== "number" || !Number.isFinite(targetSizeMB) || targetSizeMB <= 0) {
|
|
4522
|
+
return { error: 'targetSizeMB must be a positive number for mode "bitrate-from-size"' };
|
|
4523
|
+
}
|
|
4524
|
+
const fileSizeBytes = targetSizeMB * 1e6;
|
|
4525
|
+
const totalBitrateKbps = fileSizeBytes * 8 / (1e3 * durationSeconds);
|
|
4526
|
+
const videoKbps = totalBitrateKbps - audioBitrateKbps;
|
|
4527
|
+
if (videoKbps <= 0) {
|
|
4528
|
+
return { error: "audioBitrateKbps exceeds the total bitrate available at this target size and duration; lower the audio bitrate or raise the target size" };
|
|
4529
|
+
}
|
|
4530
|
+
return {
|
|
4531
|
+
mode,
|
|
4532
|
+
durationSeconds,
|
|
4533
|
+
videoBitrateKbps: round2(videoKbps),
|
|
4534
|
+
audioBitrateKbps: round2(audioBitrateKbps),
|
|
4535
|
+
totalBitrateKbps: round2(totalBitrateKbps),
|
|
4536
|
+
fileSizeBytes: Math.round(fileSizeBytes),
|
|
4537
|
+
fileSizeMB: round2(targetSizeMB),
|
|
4538
|
+
fileSizeMiB: round2(fileSizeBytes / 1048576)
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
return { error: 'mode must be "size-from-bitrate" or "bitrate-from-size"' };
|
|
4542
|
+
}
|
|
4543
|
+
server.tool(
|
|
4544
|
+
"calculate_video_bitrate",
|
|
4545
|
+
"Calculate the video file size from a target bitrate and duration, or the required bitrate to hit a target file size, with an optional separate audio bitrate. Pure arithmetic (file size bytes = total bitrate bits/sec * duration seconds / 8); no container parsing or encoding is performed.",
|
|
4546
|
+
{
|
|
4547
|
+
mode: z.enum(["size-from-bitrate", "bitrate-from-size"]).describe("size-from-bitrate requires videoBitrateKbps; bitrate-from-size requires targetSizeMB"),
|
|
4548
|
+
durationSeconds: z.number().describe("Video duration in seconds (must be positive)"),
|
|
4549
|
+
videoBitrateKbps: z.number().optional().describe('Required for mode "size-from-bitrate"'),
|
|
4550
|
+
audioBitrateKbps: z.number().default(0).describe("Optional for either mode"),
|
|
4551
|
+
targetSizeMB: z.number().optional().describe('Required for mode "bitrate-from-size"; decimal MB (1 MB = 1,000,000 bytes)')
|
|
4552
|
+
},
|
|
4553
|
+
async ({ mode, durationSeconds, videoBitrateKbps, audioBitrateKbps, targetSizeMB }) => {
|
|
4554
|
+
const result = _calculateVideoBitrateMcp(mode, durationSeconds, videoBitrateKbps, audioBitrateKbps, targetSizeMB);
|
|
4555
|
+
if ("error" in result) return errText(result);
|
|
4556
|
+
return text(JSON.stringify(result, null, 2));
|
|
4557
|
+
}
|
|
4558
|
+
);
|
|
4345
4559
|
var transport = new StdioServerTransport();
|
|
4346
4560
|
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.27.0",
|
|
4
|
+
"description": "MCP server exposing 139 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",
|