@utilix-tech/mcp 0.17.0 → 0.19.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,12 @@
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.19.0
6
+ - Added `calculate_time_value` (present/future value) and `vector_similarity` tools.
7
+
8
+ ## 0.18.0
9
+ - Added `parse_user_agent` and `parse_github_url` tools.
10
+
5
11
  ## 0.17.0
6
12
  - Added Simple Interest Calculator (`calculate_simple_interest`) and Base58 / Base62 Encoder (`base58_encode`, `base58_decode`, `base62_encode`, `base62_decode`).
7
13
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 123 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 127 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
 
@@ -149,7 +149,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
149
149
  | `date_diff` | Calculate the difference between two dates |
150
150
  | `convert_timezone` | Convert a date/time between timezones |
151
151
 
152
- ### Units (11 tools)
152
+ ### Units (12 tools)
153
153
  | Tool | Description |
154
154
  |------|-------------|
155
155
  | `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
@@ -163,8 +163,9 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
163
163
  | `validate_isbn` | Validate an ISBN-10 or ISBN-13 checksum and convert it to the other format where possible |
164
164
  | `calculate_tip_split` | Compute a tip on a bill amount and split the bill, tip, and total evenly across N people |
165
165
  | `calculate_simple_interest` | Compute simple (non-compounding) interest as principal x rate x time, plus the total payoff |
166
+ | `calculate_time_value` | Convert between present and future value (time value of money) in either direction |
166
167
 
167
- ### API / Network (8 tools)
168
+ ### API / Network (9 tools)
168
169
  | Tool | Description |
169
170
  |------|-------------|
170
171
  | `decode_jwt` | Decode a JWT: header, payload, and expiry |
@@ -175,6 +176,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
175
176
  | `parse_har` | Parse a DevTools .har network export into requests and summary stats |
176
177
  | `validate_sitemap` | Validate a sitemap.xml or sitemap index against the sitemaps.org protocol |
177
178
  | `validate_robots_txt` | Validate a robots.txt file: User-agent groups, rules, and directive syntax |
179
+ | `parse_user_agent` | Parse a browser User-Agent string into browser, engine, OS, and device type |
178
180
 
179
181
  ### Color (4 tools)
180
182
  | Tool | Description |
@@ -184,7 +186,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
184
186
  | `color_palette` | Generate a color palette from a base color |
185
187
  | `color_shades` | Generate shades or tints from a base color |
186
188
 
187
- ### Code (8 tools)
189
+ ### Code (9 tools)
188
190
  | Tool | Description |
189
191
  |------|-------------|
190
192
  | `format_sql` | Format a SQL query with proper indentation |
@@ -195,6 +197,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
195
197
  | `parse_env` | Parse a .env file and return all key-value pairs |
196
198
  | `parse_docker_image` | Parse a Docker image reference into its components |
197
199
  | `lookup_mime` | Look up the MIME type for a file extension, or the extension(s) for a MIME type |
200
+ | `parse_github_url` | Parse a github.com URL (repo, pull request, issue, commit, file, or release) into structured components |
198
201
 
199
202
  ### Data (6 tools)
200
203
  | Tool | Description |
@@ -219,7 +222,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
219
222
  | `unicode_inspect` | Inspect Unicode code points of a string |
220
223
  | `optimize_svg` | Optimize and minify an SVG string |
221
224
 
222
- ### AI / Agent (25 tools)
225
+ ### AI / Agent (26 tools)
223
226
  | Tool | Description |
224
227
  |------|-------------|
225
228
  | `estimate_tokens` | Estimate token count and cost for an LLM prompt |
@@ -247,6 +250,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
247
250
  | `compress_markdown` | Compress Markdown for LLM context budgets |
248
251
  | `compress_json` | Compress JSON for LLM context budgets |
249
252
  | `diff_json_detailed` | Diff two parsed JSON values, returning every path with its op and unchanged paths |
253
+ | `vector_similarity` | Cosine similarity, dot product, and Euclidean / Manhattan distance between two numeric vectors |
250
254
 
251
255
  ### Image (3 tools)
252
256
  | Tool | Description |
package/dist/index.js CHANGED
@@ -74,7 +74,7 @@ import { formatSql, formatHtml, testRegex, minifyJs, formatGql, parseDockerImage
74
74
  import { validateYaml, tomlToJson, xmlToJson, parseCsv } from "@utilix-tech/sdk/data";
75
75
  import { generateGradient, minifyCss } from "@utilix-tech/sdk/css";
76
76
  import { optimizeSvg, charToCodePoint, numberToWords } from "@utilix-tech/sdk/misc";
77
- import { estimateTokens, estimateCost, detectPii, redactPii, detectSecrets, detectPromptInjection, repairJson, trimToTokens, chunkText, extractUrls, sanitizeHtml, flattenJson, unflattenJson, mergeJson, extractJson, deduplicateLines, extractKeywords, validateJsonSchema, diffJson as diffJsonDetailed, extractTables, extractEntities, compressHtml, compressMarkdown, compressJson, rerankChunks, scoreRelevance, expandQuery, summarizeForLlm } from "@utilix-tech/sdk/ai_agent";
77
+ import { estimateTokens, estimateCost, detectPii, redactPii, detectSecrets, detectPromptInjection, repairJson, trimToTokens, chunkText, extractUrls, sanitizeHtml, flattenJson, unflattenJson, mergeJson, extractJson, deduplicateLines, extractKeywords, validateJsonSchema, diffJson as diffJsonDetailed, extractTables, extractEntities, compressHtml, compressMarkdown, compressJson, rerankChunks, scoreRelevance, expandQuery, summarizeForLlm, vectorSimilarity } from "@utilix-tech/sdk/ai_agent";
78
78
  var CRON_DAY_MAP = {
79
79
  SUN: "0",
80
80
  MON: "1",
@@ -1140,6 +1140,11 @@ server.tool("score_relevance", "Score a passage relevance to a query (0-1 score
1140
1140
  server.tool("expand_query", "Expand a search query with synonyms and stemmed variants for better retrieval.", { query: z.string(), max_synonyms: z.number().int().min(1).max(5).default(2) }, async ({ query, max_synonyms }) => {
1141
1141
  return { content: [{ type: "text", text: JSON.stringify(expandQuery(query, max_synonyms), null, 2) }] };
1142
1142
  });
1143
+ server.tool("vector_similarity", "Compute cosine similarity, dot product, Euclidean distance, and Manhattan distance between two equal-length numeric vectors (e.g. embeddings from an external model).", { a: z.array(z.number()), b: z.array(z.number()) }, async ({ a, b }) => {
1144
+ const result = vectorSimilarity(a, b);
1145
+ if ("error" in result) return errText(result);
1146
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1147
+ });
1143
1148
  server.tool("summarize_for_llm", "Extractive summarization: pick key sentences to fit a token budget.", { text: z.string(), max_tokens: z.number().int().min(10).default(200), strategy: z.enum(["extractive", "first", "last"]).default("extractive") }, async ({ text: text2, max_tokens, strategy }) => {
1144
1149
  return { content: [{ type: "text", text: JSON.stringify(summarizeForLlm(text2, max_tokens, strategy), null, 2) }] };
1145
1150
  });
@@ -2295,6 +2300,55 @@ server.tool(
2295
2300
  return text(JSON.stringify(result, null, 2));
2296
2301
  }
2297
2302
  );
2303
+ function _calculateTimeValueMcp(amount, annualRatePercent, years, direction, compoundsPerYear = 1) {
2304
+ if (!Number.isFinite(amount) || amount < 0) return { error: "Amount must be zero or a positive number" };
2305
+ if (!Number.isFinite(annualRatePercent) || annualRatePercent < 0) return { error: "Annual interest rate must be zero or a positive number" };
2306
+ if (!Number.isFinite(years) || years <= 0) return { error: "Years must be a positive number" };
2307
+ if (!Number.isInteger(compoundsPerYear) || compoundsPerYear <= 0) return { error: "Compounding frequency must be a positive whole number of periods per year" };
2308
+ if (direction !== "future" && direction !== "present") return { error: 'Direction must be "future" or "present"' };
2309
+ const round2 = (n) => Math.round(n * 100) / 100;
2310
+ const round6 = (n) => Math.round(n * 1e6) / 1e6;
2311
+ const ratePerPeriod = annualRatePercent / 100 / compoundsPerYear;
2312
+ const totalPeriods = years * compoundsPerYear;
2313
+ const growthFactor = Math.pow(1 + ratePerPeriod, totalPeriods);
2314
+ let presentValue;
2315
+ let futureValue;
2316
+ if (direction === "future") {
2317
+ presentValue = amount;
2318
+ futureValue = amount * growthFactor;
2319
+ } else {
2320
+ futureValue = amount;
2321
+ presentValue = amount / growthFactor;
2322
+ }
2323
+ return {
2324
+ direction,
2325
+ annualRatePercent,
2326
+ years,
2327
+ compoundsPerYear,
2328
+ totalPeriods,
2329
+ ratePerPeriodPercent: round6(ratePerPeriod * 100),
2330
+ growthFactor: round6(growthFactor),
2331
+ presentValue: round2(presentValue),
2332
+ futureValue: round2(futureValue),
2333
+ interest: round2(futureValue - presentValue)
2334
+ };
2335
+ }
2336
+ server.tool(
2337
+ "calculate_time_value",
2338
+ 'Convert between present and future value (time value of money). With direction "future" the amount is a present value grown to its future value at the given annual rate; with direction "present" it is a future value discounted back to present value. Compounds compoundsPerYear times per year (1 = annually).',
2339
+ {
2340
+ amount: z.number().describe('The known value: a present value when direction is "future", or a future value when direction is "present"'),
2341
+ annualRatePercent: z.number().describe("Annual interest / discount rate as a percent, e.g. 5"),
2342
+ years: z.number().describe("Number of years, may be fractional"),
2343
+ direction: z.enum(["future", "present"]).describe("Whether to grow the amount to a future value or discount it to a present value"),
2344
+ compoundsPerYear: z.number().int().default(1).describe("Compounding periods per year (1 = annually)")
2345
+ },
2346
+ async ({ amount, annualRatePercent, years, direction, compoundsPerYear }) => {
2347
+ const result = _calculateTimeValueMcp(amount, annualRatePercent, years, direction, compoundsPerYear);
2348
+ if ("error" in result) return errText(result);
2349
+ return text(JSON.stringify(result, null, 2));
2350
+ }
2351
+ );
2298
2352
  var _BASE58_ALPHABET_MCP = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2299
2353
  var _BASE62_ALPHABET_MCP = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
2300
2354
  function _encodeBytesToBaseMcp(bytes, alphabet) {
@@ -3228,5 +3282,184 @@ server.tool("diff_env_example", "Diff a .env file against its .env.example templ
3228
3282
  if ("error" in result) return errText(result);
3229
3283
  return text(JSON.stringify(result, null, 2));
3230
3284
  });
3285
+ var _UA_BOT_PATTERNS_MCP = [
3286
+ [/googlebot/i, "Googlebot"],
3287
+ [/bingbot/i, "Bingbot"],
3288
+ [/slurp/i, "Slurp"],
3289
+ [/duckduckbot/i, "DuckDuckBot"],
3290
+ [/baiduspider/i, "Baiduspider"],
3291
+ [/yandexbot/i, "YandexBot"],
3292
+ [/facebookexternalhit/i, "Facebot"],
3293
+ [/twitterbot/i, "Twitterbot"],
3294
+ [/applebot/i, "Applebot"],
3295
+ [/discordbot/i, "Discordbot"],
3296
+ [/slackbot/i, "Slackbot"],
3297
+ [/telegrambot/i, "TelegramBot"],
3298
+ [/whatsapp/i, "WhatsApp"],
3299
+ [/ahrefsbot/i, "AhrefsBot"],
3300
+ [/semrushbot/i, "SemrushBot"],
3301
+ [/crawler|spider|\bbot\b/i, "Bot"]
3302
+ ];
3303
+ function _detectUaBrowserMcp(s) {
3304
+ let m;
3305
+ if (m = s.match(/Edg(?:A|iOS)?\/([\d.]+)/)) return { name: "Edge", version: m[1] };
3306
+ if (m = s.match(/Edge\/([\d.]+)/)) return { name: "Edge (Legacy)", version: m[1] };
3307
+ if (m = s.match(/OPR\/([\d.]+)/)) return { name: "Opera", version: m[1] };
3308
+ if (m = s.match(/Opera\/([\d.]+)/)) return { name: "Opera", version: m[1] };
3309
+ if (m = s.match(/SamsungBrowser\/([\d.]+)/)) return { name: "Samsung Internet", version: m[1] };
3310
+ if (m = s.match(/FxiOS\/([\d.]+)/)) return { name: "Firefox", version: m[1] };
3311
+ if (m = s.match(/Firefox\/([\d.]+)/)) return { name: "Firefox", version: m[1] };
3312
+ if (m = s.match(/CriOS\/([\d.]+)/)) return { name: "Chrome", version: m[1] };
3313
+ if (m = s.match(/Chrome\/([\d.]+)/)) return { name: "Chrome", version: m[1] };
3314
+ if (m = s.match(/Version\/([\d.]+).*Safari\//)) return { name: "Safari", version: m[1] };
3315
+ if (/Safari\//.test(s)) return { name: "Safari", version: "" };
3316
+ if (m = s.match(/MSIE ([\d.]+)/)) return { name: "Internet Explorer", version: m[1] };
3317
+ if (m = s.match(/Trident\/.*rv:([\d.]+)/)) return { name: "Internet Explorer", version: m[1] };
3318
+ return { name: "Unknown", version: "" };
3319
+ }
3320
+ function _detectUaEngineMcp(s, browserName) {
3321
+ let m;
3322
+ if (/Chrome\/|CriOS\/|Edg\/|OPR\/|SamsungBrowser\//.test(s)) {
3323
+ m = s.match(/Chrome\/([\d.]+)/) || s.match(/CriOS\/([\d.]+)/);
3324
+ return { name: "Blink", version: m ? m[1] : "" };
3325
+ }
3326
+ if (/Firefox\/|FxiOS\//.test(s)) {
3327
+ m = s.match(/rv:([\d.]+)/);
3328
+ return { name: "Gecko", version: m ? m[1] : "" };
3329
+ }
3330
+ if (m = s.match(/AppleWebKit\/([\d.]+)/)) return { name: "WebKit", version: m[1] };
3331
+ if (m = s.match(/Trident\/([\d.]+)/)) return { name: "Trident", version: m[1] };
3332
+ if (browserName === "Edge (Legacy)") return { name: "EdgeHTML", version: "" };
3333
+ return { name: "Unknown", version: "" };
3334
+ }
3335
+ var _UA_WINDOWS_VERSIONS_MCP = {
3336
+ "10.0": "10",
3337
+ "6.3": "8.1",
3338
+ "6.2": "8",
3339
+ "6.1": "7",
3340
+ "6.0": "Vista",
3341
+ "5.1": "XP"
3342
+ };
3343
+ function _detectUaOsMcp(s) {
3344
+ if (/Windows NT/.test(s)) return "Windows";
3345
+ if (/CrOS/.test(s)) return "ChromeOS";
3346
+ if (/Android/.test(s)) return "Android";
3347
+ if (/iPhone|iPad|iPod/.test(s)) return "iOS";
3348
+ if (/Mac OS X/.test(s)) return "macOS";
3349
+ if (/Linux/.test(s)) return "Linux";
3350
+ return "Unknown";
3351
+ }
3352
+ function _detectUaOsVersionMcp(s) {
3353
+ let m;
3354
+ if (m = s.match(/Windows NT ([\d.]+)/)) return _UA_WINDOWS_VERSIONS_MCP[m[1]] ?? m[1];
3355
+ if (/CrOS/.test(s) && (m = s.match(/CrOS \S+ ([\d.]+)/))) return m[1];
3356
+ if (m = s.match(/Android ([\d.]+)/)) return m[1];
3357
+ if (m = s.match(/(?:iPhone|CPU) OS ([\d_]+)/)) return m[1].replace(/_/g, ".");
3358
+ if (m = s.match(/Mac OS X ([\d_]+)/)) return m[1].replace(/_/g, ".");
3359
+ return "";
3360
+ }
3361
+ function _detectUaDeviceTypeMcp(s) {
3362
+ if (/iPad/.test(s)) return "tablet";
3363
+ if (/Android/.test(s) && !/Mobile/.test(s)) return "tablet";
3364
+ if (/Mobile|iPhone|iPod|Android/.test(s)) return "mobile";
3365
+ return "desktop";
3366
+ }
3367
+ function _parseUserAgentMcp(ua) {
3368
+ if (!ua || typeof ua !== "string" || ua.trim() === "") return { error: "Empty user-agent string" };
3369
+ const s = ua.trim();
3370
+ for (const [re, name] of _UA_BOT_PATTERNS_MCP) {
3371
+ if (re.test(s)) {
3372
+ const versionMatch = s.match(new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/([\\d.]+)`, "i"));
3373
+ return {
3374
+ browser: { name, version: versionMatch ? versionMatch[1] : "" },
3375
+ engine: { name: "Unknown", version: "" },
3376
+ os: { name: _detectUaOsMcp(s), version: _detectUaOsVersionMcp(s) },
3377
+ device: { type: "bot" },
3378
+ raw: s
3379
+ };
3380
+ }
3381
+ }
3382
+ const browser = _detectUaBrowserMcp(s);
3383
+ const engine = _detectUaEngineMcp(s, browser.name);
3384
+ const os = { name: _detectUaOsMcp(s), version: _detectUaOsVersionMcp(s) };
3385
+ const device = { type: _detectUaDeviceTypeMcp(s) };
3386
+ return { browser, engine, os, device, raw: s };
3387
+ }
3388
+ server.tool(
3389
+ "parse_user_agent",
3390
+ "Parse a browser User-Agent string into browser, engine, OS, and device type via pattern matching against well-known UA token formats. Covers major desktop/mobile browsers and common search-engine/social crawlers.",
3391
+ { ua: z.string().describe("The User-Agent header value to parse") },
3392
+ async ({ ua }) => {
3393
+ const result = _parseUserAgentMcp(ua);
3394
+ if ("error" in result) return errText(result);
3395
+ return text(JSON.stringify(result, null, 2));
3396
+ }
3397
+ );
3398
+ function _parseGithubLineRangeMcp(hash) {
3399
+ const m = hash.match(/^#?L(\d+)(?:-L?(\d+))?$/i);
3400
+ if (!m) return {};
3401
+ const startLine = parseInt(m[1], 10);
3402
+ const endLine = m[2] ? parseInt(m[2], 10) : void 0;
3403
+ return endLine !== void 0 ? { startLine, endLine } : { startLine };
3404
+ }
3405
+ function _parseGithubUrlMcp(input) {
3406
+ if (!input || typeof input !== "string" || input.trim() === "") return { error: "Empty URL" };
3407
+ const trimmed = input.trim();
3408
+ const normalized = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
3409
+ let url;
3410
+ try {
3411
+ url = new URL(normalized);
3412
+ } catch {
3413
+ return { error: "Invalid URL" };
3414
+ }
3415
+ if (!/^(www\.)?github\.com$/i.test(url.hostname)) {
3416
+ return { error: `Not a github.com URL (got host "${url.hostname}")` };
3417
+ }
3418
+ const segments = url.pathname.split("/").filter(Boolean);
3419
+ if (segments.length < 2) return { error: "URL must include an owner and a repository" };
3420
+ const [owner, repo, ...rest] = segments;
3421
+ if (rest.length === 0) return { owner, repo, type: "repo" };
3422
+ const kind = rest[0];
3423
+ if (kind === "pull" || kind === "issues") {
3424
+ const number = parseInt(rest[1], 10);
3425
+ if (!rest[1] || Number.isNaN(number)) {
3426
+ return { error: `Missing ${kind === "pull" ? "pull request" : "issue"} number` };
3427
+ }
3428
+ return { owner, repo, type: kind === "pull" ? "pull" : "issue", number };
3429
+ }
3430
+ if (kind === "commit") {
3431
+ if (!rest[1]) return { error: "Missing commit SHA" };
3432
+ return { owner, repo, type: "commit", ref: rest[1] };
3433
+ }
3434
+ if (kind === "blob") {
3435
+ const ref = rest[1];
3436
+ const path = rest.slice(2).join("/");
3437
+ if (!ref || !path) return { error: "File URL must include a ref and a file path (/blob/<ref>/<path>)" };
3438
+ return { owner, repo, type: "file", ref, path, ..._parseGithubLineRangeMcp(url.hash) };
3439
+ }
3440
+ if (kind === "tree") {
3441
+ const ref = rest[1];
3442
+ if (!ref) return { error: "Missing branch or tag ref (/tree/<ref>)" };
3443
+ const path = rest.slice(2).join("/");
3444
+ return { owner, repo, type: "tree", ref, ...path ? { path } : {} };
3445
+ }
3446
+ if (kind === "releases") {
3447
+ if (rest[1] !== "tag" || !rest[2]) {
3448
+ return { error: "Unsupported releases URL (expected /releases/tag/<name>)" };
3449
+ }
3450
+ return { owner, repo, type: "release", ref: rest[2] };
3451
+ }
3452
+ return { error: `Unsupported GitHub URL path: /${rest.join("/")}` };
3453
+ }
3454
+ server.tool(
3455
+ "parse_github_url",
3456
+ "Parse a github.com URL (repo, pull request, issue, commit, file, directory, or release tag) into structured components, including an optional #L10-L20 line-range anchor for file URLs. SSH remotes and other Git hosts are out of scope.",
3457
+ { url: z.string().describe("The github.com URL to parse, e.g. https://github.com/owner/repo/pull/123") },
3458
+ async ({ url }) => {
3459
+ const result = _parseGithubUrlMcp(url);
3460
+ if ("error" in result) return errText(result);
3461
+ return text(JSON.stringify(result, null, 2));
3462
+ }
3463
+ );
3231
3464
  var transport = new StdioServerTransport();
3232
3465
  await server.connect(transport);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.17.0",
4
- "description": "MCP server exposing 123 Utilix developer tools to Claude, Cursor, VS Code Copilot, and any MCP-compatible AI assistant.",
3
+ "version": "0.19.0",
4
+ "description": "MCP server exposing 125 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",