@utilix-tech/mcp 0.57.0 → 0.59.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 -3
- package/dist/index.js +232 -1
- 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.59.0
|
|
6
|
+
- Add `build_prefer_header`, `parse_prefer_header` tools: build an HTTP Prefer request-header value from preferences with optional values and parameters per RFC 7240, or parse an existing one into the same shape.
|
|
7
|
+
- Add `dedupe_ndjson` tool: remove duplicate records from NDJSON input, either by exact structural equality or by a chosen key field, keeping the first occurrence of each duplicate.
|
|
8
|
+
|
|
9
|
+
## 0.58.0
|
|
10
|
+
- Add `apply_json_merge_patch` tool: apply an RFC 7396 JSON Merge Patch document to a target JSON document, with null-valued patch keys removing the matching target key.
|
|
11
|
+
- Add `generate_csp_nonce` tool: generate a cryptographically random, base64-encoded Content-Security-Policy nonce with ready-to-paste header directive and script/style tag snippets.
|
|
12
|
+
|
|
5
13
|
## 0.57.0
|
|
6
14
|
- Add `build_accept_language`, `parse_accept_language` tools: build an Accept-Language request-header value from locale/quality pairs sorted by descending preference, or parse an existing one into its ranked entries.
|
|
7
15
|
- Add `calculate_blended_loan_rate` tool: compute the single weighted-average interest rate across a list of loan balances, plus combined monthly and annual interest accrual.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 208 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
|
|
|
@@ -75,7 +75,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
75
75
|
|
|
76
76
|
## Tools
|
|
77
77
|
|
|
78
|
-
### JSON (
|
|
78
|
+
### JSON (16 tools)
|
|
79
79
|
| Tool | Description |
|
|
80
80
|
|------|-------------|
|
|
81
81
|
| `format_json` | Pretty-print and validate a JSON string |
|
|
@@ -85,6 +85,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
85
85
|
| `csv_to_json` | Convert CSV text to a JSON array |
|
|
86
86
|
| `jsonpath` | Evaluate a JSONPath expression |
|
|
87
87
|
| `resolve_json_pointer` | Resolve an RFC 6901 JSON Pointer against a JSON document |
|
|
88
|
+
| `apply_json_merge_patch` | Apply an RFC 7396 JSON Merge Patch document to a target JSON document |
|
|
88
89
|
| `validate_json` | Validate JSON against a JSON Schema |
|
|
89
90
|
| `yaml_to_json` | Convert YAML to JSON |
|
|
90
91
|
| `json_to_yaml` | Convert JSON to YAML |
|
|
@@ -120,7 +121,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
120
121
|
| `generate_sri_hash` | Compute a Subresource Integrity hash (SHA-256/384/512) for a script or stylesheet's content, formatted for an HTML `integrity` attribute |
|
|
121
122
|
| `generate_etag` | Compute a content-hash ETag (MD5/SHA-1/SHA-256) for a response header, with an optional weak-validator prefix |
|
|
122
123
|
|
|
123
|
-
### Generators (
|
|
124
|
+
### Generators (7 tools)
|
|
124
125
|
| Tool | Description |
|
|
125
126
|
|------|-------------|
|
|
126
127
|
| `generate_uuid` | Generate UUID v4 or v7 |
|
|
@@ -129,6 +130,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
129
130
|
| `estimate_password_entropy` | Estimate a password's entropy in bits, strength label, and crack-time estimates |
|
|
130
131
|
| `generate_qr` | Generate a QR code as an SVG string |
|
|
131
132
|
| `generate_random_data` | Generate random mock data: names, emails, UUIDs, IPs, etc. |
|
|
133
|
+
| `generate_csp_nonce` | Generate a cryptographically random CSP nonce with ready-to-paste header directive and script/style tag snippets |
|
|
132
134
|
|
|
133
135
|
### Text (16 tools)
|
|
134
136
|
| Tool | Description |
|
|
@@ -233,6 +235,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
233
235
|
| `build_x_content_type_options_header` | Build the fixed X-Content-Type-Options: nosniff header, validate a pasted value, or assess MIME-sniffing risk for a given Content-Type |
|
|
234
236
|
| `build_accept_language` | Build an Accept-Language request-header value from locale/quality pairs, sorted by descending preference |
|
|
235
237
|
| `parse_accept_language` | Parse an existing Accept-Language header value into its ranked locale/quality pairs |
|
|
238
|
+
| `build_prefer_header` | Build an HTTP Prefer request-header value from preferences with optional values/params, per RFC 7240 |
|
|
239
|
+
| `parse_prefer_header` | Parse an existing Prefer header value into its list of preferences, values, and parameters |
|
|
236
240
|
|
|
237
241
|
### Color (4 tools)
|
|
238
242
|
| Tool | Description |
|
|
@@ -270,6 +274,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
270
274
|
| `xml_to_json` | Convert XML to JSON |
|
|
271
275
|
| `parse_csv` | Parse a CSV string and return the data as JSON |
|
|
272
276
|
| `format_ndjson` | Validate and pretty-print newline-delimited JSON (NDJSON) |
|
|
277
|
+
| `dedupe_ndjson` | Remove duplicate records from NDJSON, by exact match or by a key field |
|
|
273
278
|
| `diff_env_example` | Diff a .env file against its .env.example template: flags keys missing from either side, plus empty placeholder values |
|
|
274
279
|
| `parse_markdown_front_matter` | Split a Markdown document into its YAML front matter fields and body content |
|
|
275
280
|
| `generate_markdown_toc` | Scan a Markdown document's ATX headings and build a nested table of contents with GitHub-style anchor slugs |
|
package/dist/index.js
CHANGED
|
@@ -4,7 +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
|
+
import { createHash, randomBytes } from "crypto";
|
|
8
8
|
import * as yaml from "js-yaml";
|
|
9
9
|
import {
|
|
10
10
|
parseJson,
|
|
@@ -2606,6 +2606,53 @@ server.tool(
|
|
|
2606
2606
|
return text(formatted);
|
|
2607
2607
|
}
|
|
2608
2608
|
);
|
|
2609
|
+
function _dedupeNdjsonMcp(input, keyField) {
|
|
2610
|
+
const validated = _validateNdjson(input);
|
|
2611
|
+
if (!validated.valid) {
|
|
2612
|
+
const firstError = validated.lines.find((l) => !l.valid);
|
|
2613
|
+
return { error: firstError ? `Line ${firstError.line}: ${firstError.error}` : "Invalid NDJSON" };
|
|
2614
|
+
}
|
|
2615
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2616
|
+
const keptRaw = [];
|
|
2617
|
+
const removedLines = [];
|
|
2618
|
+
for (const l of validated.lines) {
|
|
2619
|
+
const parsed = JSON.parse(l.raw);
|
|
2620
|
+
let key;
|
|
2621
|
+
if (keyField) {
|
|
2622
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || !(keyField in parsed)) {
|
|
2623
|
+
return { error: `Line ${l.line}: missing key field "${keyField}"` };
|
|
2624
|
+
}
|
|
2625
|
+
key = JSON.stringify(parsed[keyField]);
|
|
2626
|
+
} else {
|
|
2627
|
+
key = JSON.stringify(parsed);
|
|
2628
|
+
}
|
|
2629
|
+
if (seen.has(key)) removedLines.push(l.line);
|
|
2630
|
+
else {
|
|
2631
|
+
seen.add(key);
|
|
2632
|
+
keptRaw.push(l.raw.trim());
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return {
|
|
2636
|
+
totalLines: validated.totalLines,
|
|
2637
|
+
uniqueLines: keptRaw.length,
|
|
2638
|
+
duplicatesRemoved: removedLines.length,
|
|
2639
|
+
output: keptRaw.join("\n"),
|
|
2640
|
+
removedLines
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
server.tool(
|
|
2644
|
+
"dedupe_ndjson",
|
|
2645
|
+
"Remove duplicate records from NDJSON (newline-delimited JSON) input. With no keyField, two lines are duplicates when their parsed JSON values are structurally identical. With a keyField, two lines are duplicates when that field's value matches, keeping the first occurrence of each key.",
|
|
2646
|
+
{
|
|
2647
|
+
input: z.string().describe("NDJSON content: one JSON value per line"),
|
|
2648
|
+
keyField: z.string().optional().describe("Optional field name to dedupe by instead of exact structural equality")
|
|
2649
|
+
},
|
|
2650
|
+
async ({ input, keyField }) => {
|
|
2651
|
+
const result = _dedupeNdjsonMcp(input, keyField);
|
|
2652
|
+
if ("error" in result) return errText(result);
|
|
2653
|
+
return text(JSON.stringify(result, null, 2));
|
|
2654
|
+
}
|
|
2655
|
+
);
|
|
2609
2656
|
function _decodePdfString(raw, isHex) {
|
|
2610
2657
|
const bytes = [];
|
|
2611
2658
|
if (isHex) {
|
|
@@ -4655,6 +4702,45 @@ server.tool(
|
|
|
4655
4702
|
return text(JSON.stringify(result, null, 2));
|
|
4656
4703
|
}
|
|
4657
4704
|
);
|
|
4705
|
+
function _isMergePatchObjectMcp(value) {
|
|
4706
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4707
|
+
}
|
|
4708
|
+
function _mergeJsonPatchMcp(target, patch) {
|
|
4709
|
+
if (!_isMergePatchObjectMcp(patch)) return patch;
|
|
4710
|
+
const result = _isMergePatchObjectMcp(target) ? { ...target } : {};
|
|
4711
|
+
for (const key of Object.keys(patch)) {
|
|
4712
|
+
const value = patch[key];
|
|
4713
|
+
if (value === null) {
|
|
4714
|
+
delete result[key];
|
|
4715
|
+
} else {
|
|
4716
|
+
result[key] = _mergeJsonPatchMcp(result[key], value);
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
return result;
|
|
4720
|
+
}
|
|
4721
|
+
server.tool(
|
|
4722
|
+
"apply_json_merge_patch",
|
|
4723
|
+
"Apply an RFC 7396 JSON Merge Patch document to a target JSON document and return the merged result. A null-valued key in the patch removes that key from the target; a patch that is not itself a JSON object replaces the target wholesale, which is how arrays are replaced rather than merged element by element.",
|
|
4724
|
+
{
|
|
4725
|
+
target: z.string().describe("The target JSON document, as a JSON string"),
|
|
4726
|
+
patch: z.string().describe("The RFC 7396 JSON Merge Patch document, as a JSON string")
|
|
4727
|
+
},
|
|
4728
|
+
async ({ target, patch }) => {
|
|
4729
|
+
let targetParsed;
|
|
4730
|
+
try {
|
|
4731
|
+
targetParsed = JSON.parse(target);
|
|
4732
|
+
} catch {
|
|
4733
|
+
return errText({ error: "Target is not valid JSON" });
|
|
4734
|
+
}
|
|
4735
|
+
let patchParsed;
|
|
4736
|
+
try {
|
|
4737
|
+
patchParsed = JSON.parse(patch);
|
|
4738
|
+
} catch {
|
|
4739
|
+
return errText({ error: "Patch is not valid JSON" });
|
|
4740
|
+
}
|
|
4741
|
+
return text(JSON.stringify({ result: _mergeJsonPatchMcp(targetParsed, patchParsed) }, null, 2));
|
|
4742
|
+
}
|
|
4743
|
+
);
|
|
4658
4744
|
function _convertAprApyMcp(rate, compoundsPerYear, direction) {
|
|
4659
4745
|
if (!Number.isFinite(rate) || rate < 0) return { error: "Rate must be zero or a positive number" };
|
|
4660
4746
|
if (!Number.isInteger(compoundsPerYear) || compoundsPerYear <= 0) return { error: "Compounding frequency must be a positive whole number of periods per year" };
|
|
@@ -9406,6 +9492,132 @@ server.tool(
|
|
|
9406
9492
|
return text(JSON.stringify(result, null, 2));
|
|
9407
9493
|
}
|
|
9408
9494
|
);
|
|
9495
|
+
var _PREFER_TOKEN_RE_MCP = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
9496
|
+
function _isPreferTokenMcp(s) {
|
|
9497
|
+
return _PREFER_TOKEN_RE_MCP.test(s);
|
|
9498
|
+
}
|
|
9499
|
+
function _splitPreferTopLevelMcp(input, delim) {
|
|
9500
|
+
const parts = [];
|
|
9501
|
+
let current = "";
|
|
9502
|
+
let inQuotes = false;
|
|
9503
|
+
for (const ch of input) {
|
|
9504
|
+
if (ch === '"') inQuotes = !inQuotes;
|
|
9505
|
+
if (ch === delim && !inQuotes) {
|
|
9506
|
+
parts.push(current);
|
|
9507
|
+
current = "";
|
|
9508
|
+
} else {
|
|
9509
|
+
current += ch;
|
|
9510
|
+
}
|
|
9511
|
+
}
|
|
9512
|
+
parts.push(current);
|
|
9513
|
+
return parts;
|
|
9514
|
+
}
|
|
9515
|
+
function _parsePreferTokenValueMcp(part) {
|
|
9516
|
+
const trimmed = part.trim();
|
|
9517
|
+
if (!trimmed) return null;
|
|
9518
|
+
const eq = trimmed.indexOf("=");
|
|
9519
|
+
if (eq === -1) return { token: trimmed };
|
|
9520
|
+
const token = trimmed.slice(0, eq).trim();
|
|
9521
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
9522
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
9523
|
+
return { token, value };
|
|
9524
|
+
}
|
|
9525
|
+
function _formatPreferWordMcp(value) {
|
|
9526
|
+
return _isPreferTokenMcp(value) ? value : `"${value.replace(/"/g, '\\"')}"`;
|
|
9527
|
+
}
|
|
9528
|
+
function _formatPreferPreferencesMcp(preferences) {
|
|
9529
|
+
return preferences.map((p) => {
|
|
9530
|
+
let s = p.token;
|
|
9531
|
+
if (p.value !== void 0) s += `=${_formatPreferWordMcp(p.value)}`;
|
|
9532
|
+
for (const param of p.params ?? []) {
|
|
9533
|
+
s += `;${param.name}`;
|
|
9534
|
+
if (param.value !== void 0) s += `=${_formatPreferWordMcp(param.value)}`;
|
|
9535
|
+
}
|
|
9536
|
+
return s;
|
|
9537
|
+
}).join(", ");
|
|
9538
|
+
}
|
|
9539
|
+
function _buildPreferHeaderMcp(preferences) {
|
|
9540
|
+
if (!Array.isArray(preferences) || preferences.length === 0) {
|
|
9541
|
+
return { error: "At least one preference is required" };
|
|
9542
|
+
}
|
|
9543
|
+
const built = [];
|
|
9544
|
+
for (let i = 0; i < preferences.length; i++) {
|
|
9545
|
+
const { token, value, params } = preferences[i];
|
|
9546
|
+
if (typeof token !== "string" || !_isPreferTokenMcp(token.trim())) {
|
|
9547
|
+
return { error: `preference ${i + 1}: token is required and must be a valid HTTP token` };
|
|
9548
|
+
}
|
|
9549
|
+
const cleanParams = [];
|
|
9550
|
+
if (params !== void 0) {
|
|
9551
|
+
if (!Array.isArray(params)) return { error: `preference ${i + 1}: params must be an array` };
|
|
9552
|
+
for (const param of params) {
|
|
9553
|
+
if (typeof param?.name !== "string" || !_isPreferTokenMcp(param.name.trim())) {
|
|
9554
|
+
return { error: `preference ${i + 1}: parameter name is required and must be a valid HTTP token` };
|
|
9555
|
+
}
|
|
9556
|
+
cleanParams.push({ name: param.name.trim(), value: param.value !== void 0 ? String(param.value) : void 0 });
|
|
9557
|
+
}
|
|
9558
|
+
}
|
|
9559
|
+
built.push({
|
|
9560
|
+
token: token.trim(),
|
|
9561
|
+
value: value !== void 0 ? String(value) : void 0,
|
|
9562
|
+
params: cleanParams.length > 0 ? cleanParams : void 0
|
|
9563
|
+
});
|
|
9564
|
+
}
|
|
9565
|
+
const headerValue = _formatPreferPreferencesMcp(built);
|
|
9566
|
+
return { header: `Prefer: ${headerValue}`, headerValue, preferences: built };
|
|
9567
|
+
}
|
|
9568
|
+
function _parsePreferHeaderMcp(raw) {
|
|
9569
|
+
if (!raw || raw.trim() === "") return { error: "Input is empty" };
|
|
9570
|
+
const withoutLabel = raw.trim().replace(/^prefer:\s*/i, "");
|
|
9571
|
+
const parts = _splitPreferTopLevelMcp(withoutLabel, ",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
9572
|
+
if (parts.length === 0) return { error: "Input is empty" };
|
|
9573
|
+
const preferences = [];
|
|
9574
|
+
for (const part of parts) {
|
|
9575
|
+
const segments = _splitPreferTopLevelMcp(part, ";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
9576
|
+
if (segments.length === 0) return { error: `Invalid preference: "${part}"` };
|
|
9577
|
+
const main = _parsePreferTokenValueMcp(segments[0]);
|
|
9578
|
+
if (!main || !_isPreferTokenMcp(main.token)) return { error: `Invalid preference token in "${part}"` };
|
|
9579
|
+
const params = [];
|
|
9580
|
+
for (const seg of segments.slice(1)) {
|
|
9581
|
+
const p = _parsePreferTokenValueMcp(seg);
|
|
9582
|
+
if (!p || !_isPreferTokenMcp(p.token)) return { error: `Invalid parameter in "${part}"` };
|
|
9583
|
+
params.push({ name: p.token, value: p.value });
|
|
9584
|
+
}
|
|
9585
|
+
preferences.push({ token: main.token, value: main.value, params: params.length > 0 ? params : void 0 });
|
|
9586
|
+
}
|
|
9587
|
+
const headerValue = _formatPreferPreferencesMcp(preferences);
|
|
9588
|
+
return { header: `Prefer: ${headerValue}`, headerValue, preferences };
|
|
9589
|
+
}
|
|
9590
|
+
server.tool(
|
|
9591
|
+
"build_prefer_header",
|
|
9592
|
+
"Build a Prefer request-header value from a list of preferences, each with an optional value and optional parameters, per RFC 7240. A value or parameter value that is not a valid HTTP token is quoted automatically.",
|
|
9593
|
+
{
|
|
9594
|
+
preferences: z.array(z.object({
|
|
9595
|
+
token: z.string().describe('The preference token, e.g. "respond-async" or "return"'),
|
|
9596
|
+
value: z.string().optional().describe("Optional value for the preference"),
|
|
9597
|
+
params: z.array(z.object({
|
|
9598
|
+
name: z.string(),
|
|
9599
|
+
value: z.string().optional()
|
|
9600
|
+
})).optional().describe("Optional parameters following the preference")
|
|
9601
|
+
})).min(1).describe("Preferences to build the header from")
|
|
9602
|
+
},
|
|
9603
|
+
async ({ preferences }) => {
|
|
9604
|
+
const result = _buildPreferHeaderMcp(preferences);
|
|
9605
|
+
if ("error" in result) return errText(result);
|
|
9606
|
+
return text(JSON.stringify(result, null, 2));
|
|
9607
|
+
}
|
|
9608
|
+
);
|
|
9609
|
+
server.tool(
|
|
9610
|
+
"parse_prefer_header",
|
|
9611
|
+
'Parse an existing Prefer header value (with or without a leading "Prefer:" label) into its list of preferences, each with an optional value and optional parameters, per RFC 7240.',
|
|
9612
|
+
{
|
|
9613
|
+
header: z.string().min(1).describe("The Prefer header value to parse")
|
|
9614
|
+
},
|
|
9615
|
+
async ({ header }) => {
|
|
9616
|
+
const result = _parsePreferHeaderMcp(header);
|
|
9617
|
+
if ("error" in result) return errText(result);
|
|
9618
|
+
return text(JSON.stringify(result, null, 2));
|
|
9619
|
+
}
|
|
9620
|
+
);
|
|
9409
9621
|
function _calculateBlendedLoanRateMcp(loans) {
|
|
9410
9622
|
if (!Array.isArray(loans) || loans.length === 0) {
|
|
9411
9623
|
return { error: "loans must be a non-empty array" };
|
|
@@ -9461,5 +9673,24 @@ server.tool(
|
|
|
9461
9673
|
return text(JSON.stringify(result, null, 2));
|
|
9462
9674
|
}
|
|
9463
9675
|
);
|
|
9676
|
+
server.tool(
|
|
9677
|
+
"generate_csp_nonce",
|
|
9678
|
+
'Generate a cryptographically random, base64-encoded nonce for a Content-Security-Policy script-src or style-src directive, along with ready-to-paste header directive and <script>/<style nonce="..."> tag snippets. Generates a fresh, unique value on every call.',
|
|
9679
|
+
{
|
|
9680
|
+
byteLength: z.number().int().min(8).max(64).optional().describe("Number of random bytes to encode (default 16 = 128 bits, the CSP spec's recommended minimum)")
|
|
9681
|
+
},
|
|
9682
|
+
async ({ byteLength }) => {
|
|
9683
|
+
const length = byteLength ?? 16;
|
|
9684
|
+
const nonce = randomBytes(length).toString("base64");
|
|
9685
|
+
return text(JSON.stringify({
|
|
9686
|
+
nonce,
|
|
9687
|
+
byteLength: length,
|
|
9688
|
+
scriptSrcDirective: `script-src 'nonce-${nonce}'`,
|
|
9689
|
+
styleSrcDirective: `style-src 'nonce-${nonce}'`,
|
|
9690
|
+
scriptTag: `<script nonce="${nonce}">`,
|
|
9691
|
+
styleTag: `<style nonce="${nonce}">`
|
|
9692
|
+
}, null, 2));
|
|
9693
|
+
}
|
|
9694
|
+
);
|
|
9464
9695
|
var transport = new StdioServerTransport();
|
|
9465
9696
|
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.59.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",
|