@utilix-tech/mcp 0.4.1 → 0.6.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/README.md +11 -3
- package/dist/index.js +288 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 101 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
|
|
|
@@ -150,13 +150,15 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
150
150
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
151
151
|
| `px_to_units` | Convert pixels to rem, em, pt, and vw |
|
|
152
152
|
|
|
153
|
-
### API / Network (
|
|
153
|
+
### API / Network (6 tools)
|
|
154
154
|
| Tool | Description |
|
|
155
155
|
|------|-------------|
|
|
156
156
|
| `decode_jwt` | Decode a JWT: header, payload, and expiry |
|
|
157
|
+
| `parse_jwks` | Parse a JWKS (or a single bare JWK): type, use, algorithm, key ID, and size per key |
|
|
157
158
|
| `build_curl` | Build a cURL command from method, URL, headers, and body |
|
|
158
159
|
| `curl_to_code` | Convert a cURL command to fetch, axios, Python, Go, PHP, or Ruby |
|
|
159
160
|
| `cors_headers` | Generate CORS response headers for a configuration |
|
|
161
|
+
| `parse_har` | Parse a DevTools .har network export into requests and summary stats |
|
|
160
162
|
|
|
161
163
|
### Color (4 tools)
|
|
162
164
|
| Tool | Description |
|
|
@@ -177,13 +179,14 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
177
179
|
| `parse_env` | Parse a .env file and return all key-value pairs |
|
|
178
180
|
| `parse_docker_image` | Parse a Docker image reference into its components |
|
|
179
181
|
|
|
180
|
-
### Data (
|
|
182
|
+
### Data (5 tools)
|
|
181
183
|
| Tool | Description |
|
|
182
184
|
|------|-------------|
|
|
183
185
|
| `validate_yaml` | Validate YAML syntax |
|
|
184
186
|
| `toml_to_json` | Convert TOML to JSON |
|
|
185
187
|
| `xml_to_json` | Convert XML to JSON |
|
|
186
188
|
| `parse_csv` | Parse a CSV string and return the data as JSON |
|
|
189
|
+
| `format_ndjson` | Validate and pretty-print newline-delimited JSON (NDJSON) |
|
|
187
190
|
|
|
188
191
|
### CSS (2 tools)
|
|
189
192
|
| Tool | Description |
|
|
@@ -232,6 +235,11 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
232
235
|
|------|-------------|
|
|
233
236
|
| `read_image_info` | Read format, dimensions, bit depth, and alpha channel from image bytes: no decoding required. Supports PNG, JPEG, GIF, WebP, BMP |
|
|
234
237
|
|
|
238
|
+
### PDF (1 tool)
|
|
239
|
+
| Tool | Description |
|
|
240
|
+
|------|-------------|
|
|
241
|
+
| `read_pdf_metadata` | Read title, author, dates, page count, and encryption flag from a PDF's trailer/Info dictionary: no PDF rendering library required |
|
|
242
|
+
|
|
235
243
|
## Requirements
|
|
236
244
|
|
|
237
245
|
- Node.js 18+
|
package/dist/index.js
CHANGED
|
@@ -1398,5 +1398,293 @@ server.tool("read_image_info", "Read image format, dimensions, and color info di
|
|
|
1398
1398
|
const bytes = new Uint8Array(Buffer.from(image_base64.replace(/^data:.*;base64,/, ""), "base64"));
|
|
1399
1399
|
return { content: [{ type: "text", text: JSON.stringify(_readImageInfo(bytes), null, 2) }] };
|
|
1400
1400
|
});
|
|
1401
|
+
function _parseHar(input) {
|
|
1402
|
+
if (!input || !input.trim()) return { error: "Input is empty" };
|
|
1403
|
+
let data;
|
|
1404
|
+
try {
|
|
1405
|
+
data = JSON.parse(input);
|
|
1406
|
+
} catch (e) {
|
|
1407
|
+
return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
1408
|
+
}
|
|
1409
|
+
if (typeof data !== "object" || data === null || !("log" in data)) {
|
|
1410
|
+
return { error: 'Not a valid HAR file: missing "log" property' };
|
|
1411
|
+
}
|
|
1412
|
+
const log = data.log;
|
|
1413
|
+
if (typeof log !== "object" || log === null || !Array.isArray(log.entries)) {
|
|
1414
|
+
return { error: 'Not a valid HAR file: "log.entries" must be an array' };
|
|
1415
|
+
}
|
|
1416
|
+
const logObj = log;
|
|
1417
|
+
const entries = [];
|
|
1418
|
+
const methods = {};
|
|
1419
|
+
const statusCodes = {};
|
|
1420
|
+
let totalSize = 0;
|
|
1421
|
+
let totalTime = 0;
|
|
1422
|
+
let failedRequests = 0;
|
|
1423
|
+
for (const raw of logObj.entries) {
|
|
1424
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
1425
|
+
const e = raw;
|
|
1426
|
+
const request = e.request ?? {};
|
|
1427
|
+
const response = e.response ?? {};
|
|
1428
|
+
const content = response.content ?? {};
|
|
1429
|
+
const method = typeof request.method === "string" ? request.method : "GET";
|
|
1430
|
+
const url = typeof request.url === "string" ? request.url : "";
|
|
1431
|
+
const status = typeof response.status === "number" ? response.status : 0;
|
|
1432
|
+
const statusText = typeof response.statusText === "string" ? response.statusText : "";
|
|
1433
|
+
const httpVersion = typeof response.httpVersion === "string" ? response.httpVersion : "";
|
|
1434
|
+
const mimeType = typeof content.mimeType === "string" ? content.mimeType : "";
|
|
1435
|
+
const size = typeof content.size === "number" && content.size > 0 ? content.size : 0;
|
|
1436
|
+
const time = typeof e.time === "number" ? e.time : 0;
|
|
1437
|
+
const startedDateTime = typeof e.startedDateTime === "string" ? e.startedDateTime : "";
|
|
1438
|
+
entries.push({ method, url, status, statusText, httpVersion, mimeType, size, time, startedDateTime });
|
|
1439
|
+
methods[method] = (methods[method] ?? 0) + 1;
|
|
1440
|
+
if (status > 0) statusCodes[String(status)] = (statusCodes[String(status)] ?? 0) + 1;
|
|
1441
|
+
totalSize += size;
|
|
1442
|
+
totalTime += time;
|
|
1443
|
+
if (status === 0 || status >= 400) failedRequests++;
|
|
1444
|
+
}
|
|
1445
|
+
const totalRequests = entries.length;
|
|
1446
|
+
const avgTime = totalRequests > 0 ? Math.round(totalTime / totalRequests * 100) / 100 : 0;
|
|
1447
|
+
const creator = logObj.creator ?? {};
|
|
1448
|
+
return {
|
|
1449
|
+
version: typeof logObj.version === "string" ? logObj.version : "",
|
|
1450
|
+
creator: { name: typeof creator.name === "string" ? creator.name : "", version: typeof creator.version === "string" ? creator.version : "" },
|
|
1451
|
+
entries,
|
|
1452
|
+
summary: { totalRequests, totalSize, totalTime, avgTime, failedRequests, methods, statusCodes }
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function _validateNdjson(input) {
|
|
1456
|
+
const rawLines = input.split("\n");
|
|
1457
|
+
const lines = [];
|
|
1458
|
+
let validCount = 0;
|
|
1459
|
+
rawLines.forEach((raw, idx) => {
|
|
1460
|
+
if (raw.trim() === "") return;
|
|
1461
|
+
const lineNum = idx + 1;
|
|
1462
|
+
try {
|
|
1463
|
+
JSON.parse(raw);
|
|
1464
|
+
lines.push({ line: lineNum, raw, valid: true });
|
|
1465
|
+
validCount++;
|
|
1466
|
+
} catch (e) {
|
|
1467
|
+
lines.push({ line: lineNum, raw, valid: false, error: e instanceof Error ? e.message : String(e) });
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
return { valid: lines.length > 0 && validCount === lines.length, totalLines: lines.length, validLines: validCount, invalidLines: lines.length - validCount, lines };
|
|
1471
|
+
}
|
|
1472
|
+
server.tool(
|
|
1473
|
+
"parse_har",
|
|
1474
|
+
"Parse a browser DevTools .har network export into a readable list of requests (method, URL, status, size, timing) plus aggregate summary stats.",
|
|
1475
|
+
{ har: z.string().describe("Raw HAR file contents (JSON)") },
|
|
1476
|
+
async ({ har }) => {
|
|
1477
|
+
const result = _parseHar(har);
|
|
1478
|
+
if ("error" in result) return errText(result);
|
|
1479
|
+
return text(JSON.stringify(result, null, 2));
|
|
1480
|
+
}
|
|
1481
|
+
);
|
|
1482
|
+
server.tool(
|
|
1483
|
+
"format_ndjson",
|
|
1484
|
+
"Validate newline-delimited JSON (NDJSON/JSON Lines), report per-line errors, and pretty-print each valid line.",
|
|
1485
|
+
{
|
|
1486
|
+
input: z.string().describe("NDJSON content: one JSON value per line"),
|
|
1487
|
+
indent: z.number().int().default(2).describe("Spaces per indent level")
|
|
1488
|
+
},
|
|
1489
|
+
async ({ input, indent }) => {
|
|
1490
|
+
const validation = _validateNdjson(input);
|
|
1491
|
+
if (!validation.valid) {
|
|
1492
|
+
const firstError = validation.lines.find((l) => !l.valid);
|
|
1493
|
+
return errText({ error: firstError ? `Line ${firstError.line}: ${firstError.error}` : "Invalid NDJSON" });
|
|
1494
|
+
}
|
|
1495
|
+
const formatted = validation.lines.map((l) => JSON.stringify(JSON.parse(l.raw), null, indent)).join("\n");
|
|
1496
|
+
return text(formatted);
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
function _decodePdfString(raw, isHex) {
|
|
1500
|
+
const bytes = [];
|
|
1501
|
+
if (isHex) {
|
|
1502
|
+
const hex = raw.replace(/\s+/g, "");
|
|
1503
|
+
const padded = hex.length % 2 === 0 ? hex : hex + "0";
|
|
1504
|
+
for (let i = 0; i < padded.length; i += 2) bytes.push(parseInt(padded.slice(i, i + 2), 16));
|
|
1505
|
+
} else {
|
|
1506
|
+
const s = raw.replace(/\\\r\n|\\\r|\\\n/g, "");
|
|
1507
|
+
for (let i = 0; i < s.length; i++) {
|
|
1508
|
+
const ch = s[i];
|
|
1509
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
1510
|
+
const next = s[i + 1];
|
|
1511
|
+
if (next === "n") {
|
|
1512
|
+
bytes.push(10);
|
|
1513
|
+
i++;
|
|
1514
|
+
} else if (next === "r") {
|
|
1515
|
+
bytes.push(13);
|
|
1516
|
+
i++;
|
|
1517
|
+
} else if (next === "t") {
|
|
1518
|
+
bytes.push(9);
|
|
1519
|
+
i++;
|
|
1520
|
+
} else if (next === "b") {
|
|
1521
|
+
bytes.push(8);
|
|
1522
|
+
i++;
|
|
1523
|
+
} else if (next === "f") {
|
|
1524
|
+
bytes.push(12);
|
|
1525
|
+
i++;
|
|
1526
|
+
} else if (next === "(" || next === ")" || next === "\\") {
|
|
1527
|
+
bytes.push(next.charCodeAt(0));
|
|
1528
|
+
i++;
|
|
1529
|
+
} else if (/[0-7]/.test(next)) {
|
|
1530
|
+
let oct = next;
|
|
1531
|
+
i++;
|
|
1532
|
+
for (let k = 0; k < 2 && i + 1 < s.length && /[0-7]/.test(s[i + 1]); k++) {
|
|
1533
|
+
i++;
|
|
1534
|
+
oct += s[i];
|
|
1535
|
+
}
|
|
1536
|
+
bytes.push(parseInt(oct, 8) & 255);
|
|
1537
|
+
} else {
|
|
1538
|
+
bytes.push(next.charCodeAt(0));
|
|
1539
|
+
i++;
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
bytes.push(ch.charCodeAt(0) & 255);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (bytes.length >= 2 && bytes[0] === 254 && bytes[1] === 255) {
|
|
1547
|
+
let out = "";
|
|
1548
|
+
for (let i = 2; i + 1 < bytes.length; i += 2) out += String.fromCharCode(bytes[i] << 8 | bytes[i + 1]);
|
|
1549
|
+
return out;
|
|
1550
|
+
}
|
|
1551
|
+
return bytes.map((b) => String.fromCharCode(b)).join("");
|
|
1552
|
+
}
|
|
1553
|
+
function _extractPdfField(dict, key) {
|
|
1554
|
+
const litMatch = dict.match(new RegExp(`/${key}\\s*\\(((?:\\\\.|[^\\\\()])*)\\)`));
|
|
1555
|
+
if (litMatch) return _decodePdfString(litMatch[1], false);
|
|
1556
|
+
const hexMatch = dict.match(new RegExp(`/${key}\\s*<([0-9A-Fa-f\\s]*)>`));
|
|
1557
|
+
if (hexMatch) return _decodePdfString(hexMatch[1], true);
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
function _findPdfObject(text2, objNum, gen) {
|
|
1561
|
+
const m = text2.match(new RegExp(`(?:^|[^0-9])${objNum}\\s+${gen}\\s+obj([\\s\\S]*?)endobj`));
|
|
1562
|
+
return m ? m[1] : null;
|
|
1563
|
+
}
|
|
1564
|
+
function _parsePdfDate(raw) {
|
|
1565
|
+
const m = raw.match(/^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([Z+-])?(\d{2})?'?(\d{2})?'?/);
|
|
1566
|
+
if (!m) return null;
|
|
1567
|
+
const [, y, mo = "01", d = "01", h = "00", mi = "00", s = "00", tz, tzh, tzm] = m;
|
|
1568
|
+
let iso = `${y}-${mo}-${d}T${h}:${mi}:${s}`;
|
|
1569
|
+
if (tz === "Z") iso += "Z";
|
|
1570
|
+
else if (tz === "+" || tz === "-") iso += `${tz}${tzh ?? "00"}:${tzm ?? "00"}`;
|
|
1571
|
+
return iso;
|
|
1572
|
+
}
|
|
1573
|
+
function _readPdfMetadata(bytes) {
|
|
1574
|
+
if (bytes.length < 8) return { error: "File too small to be a PDF" };
|
|
1575
|
+
const latin1 = (b) => {
|
|
1576
|
+
let out = "";
|
|
1577
|
+
for (let i = 0; i < b.length; i += 32768) out += String.fromCharCode(...b.subarray(i, i + 32768));
|
|
1578
|
+
return out;
|
|
1579
|
+
};
|
|
1580
|
+
const header = latin1(bytes.subarray(0, 16));
|
|
1581
|
+
const versionMatch = header.match(/%PDF-(\d\.\d)/);
|
|
1582
|
+
if (!versionMatch) return { error: "Not a valid PDF file (missing %PDF header)" };
|
|
1583
|
+
const version = versionMatch[1];
|
|
1584
|
+
const text2 = latin1(bytes);
|
|
1585
|
+
const trailerMatches = [...text2.matchAll(/trailer\s*<<([\s\S]*?)>>/g)];
|
|
1586
|
+
let infoDict = null;
|
|
1587
|
+
let encrypted = false;
|
|
1588
|
+
if (trailerMatches.length > 0) {
|
|
1589
|
+
const trailer = trailerMatches[trailerMatches.length - 1][1];
|
|
1590
|
+
encrypted = /\/Encrypt\s+\d+\s+\d+\s+R/.test(trailer);
|
|
1591
|
+
const infoRef = trailer.match(/\/Info\s+(\d+)\s+(\d+)\s+R/);
|
|
1592
|
+
if (infoRef) infoDict = _findPdfObject(text2, Number(infoRef[1]), Number(infoRef[2]));
|
|
1593
|
+
}
|
|
1594
|
+
const objBlocks = [...text2.matchAll(/(\d+)\s+(\d+)\s+obj([\s\S]*?)endobj/g)];
|
|
1595
|
+
if (!infoDict) {
|
|
1596
|
+
for (const b of objBlocks) {
|
|
1597
|
+
if (/\/(Title|Author|Producer|Creator|CreationDate)\b/.test(b[3]) && !/\/Type\s*\/(Page|Pages|Catalog|Font)\b/.test(b[3])) {
|
|
1598
|
+
infoDict = b[3];
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const title = infoDict ? _extractPdfField(infoDict, "Title") : null;
|
|
1604
|
+
const author = infoDict ? _extractPdfField(infoDict, "Author") : null;
|
|
1605
|
+
const subject = infoDict ? _extractPdfField(infoDict, "Subject") : null;
|
|
1606
|
+
const keywords = infoDict ? _extractPdfField(infoDict, "Keywords") : null;
|
|
1607
|
+
const creator = infoDict ? _extractPdfField(infoDict, "Creator") : null;
|
|
1608
|
+
const producer = infoDict ? _extractPdfField(infoDict, "Producer") : null;
|
|
1609
|
+
const creationDateRaw = infoDict ? _extractPdfField(infoDict, "CreationDate") : null;
|
|
1610
|
+
const modDateRaw = infoDict ? _extractPdfField(infoDict, "ModDate") : null;
|
|
1611
|
+
let pageCount = null;
|
|
1612
|
+
for (const b of objBlocks) {
|
|
1613
|
+
if (!/\/Type\s*\/Pages\b/.test(b[3])) continue;
|
|
1614
|
+
const countMatch = b[3].match(/\/Count\s+(\d+)/);
|
|
1615
|
+
if (countMatch) {
|
|
1616
|
+
const c = Number(countMatch[1]);
|
|
1617
|
+
if (pageCount === null || c > pageCount) pageCount = c;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (pageCount === null) {
|
|
1621
|
+
const pageMatches = text2.match(/\/Type\s*\/Page(?!s)\b/g);
|
|
1622
|
+
if (pageMatches) pageCount = pageMatches.length;
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
version,
|
|
1626
|
+
pageCount,
|
|
1627
|
+
title,
|
|
1628
|
+
author,
|
|
1629
|
+
subject,
|
|
1630
|
+
keywords,
|
|
1631
|
+
creator,
|
|
1632
|
+
producer,
|
|
1633
|
+
creationDate: creationDateRaw ? _parsePdfDate(creationDateRaw) ?? creationDateRaw : null,
|
|
1634
|
+
modDate: modDateRaw ? _parsePdfDate(modDateRaw) ?? modDateRaw : null,
|
|
1635
|
+
encrypted
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
server.tool("read_pdf_metadata", "Read PDF metadata (title, author, dates, page count, encryption flag) directly from the trailer/Info dictionary: no PDF rendering library required.", { pdf_base64: z.string().describe("Base64-encoded PDF bytes, optionally prefixed with a data: URL scheme") }, async ({ pdf_base64 }) => {
|
|
1639
|
+
const bytes = new Uint8Array(Buffer.from(pdf_base64.replace(/^data:.*;base64,/, ""), "base64"));
|
|
1640
|
+
const result = _readPdfMetadata(bytes);
|
|
1641
|
+
if ("error" in result) return errText(result);
|
|
1642
|
+
return text(JSON.stringify(result, null, 2));
|
|
1643
|
+
});
|
|
1644
|
+
function _parseJwks(input) {
|
|
1645
|
+
const trimmed = input.trim();
|
|
1646
|
+
if (!trimmed) return { error: "No input provided" };
|
|
1647
|
+
let data;
|
|
1648
|
+
try {
|
|
1649
|
+
data = JSON.parse(trimmed);
|
|
1650
|
+
} catch (e) {
|
|
1651
|
+
return { error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
1652
|
+
}
|
|
1653
|
+
let rawKeys;
|
|
1654
|
+
if (data && typeof data === "object" && Array.isArray(data.keys)) {
|
|
1655
|
+
rawKeys = data.keys;
|
|
1656
|
+
} else if (data && typeof data === "object" && typeof data.kty === "string") {
|
|
1657
|
+
rawKeys = [data];
|
|
1658
|
+
} else {
|
|
1659
|
+
return { error: 'Input must be a JWKS object with a "keys" array, or a single JWK object with a "kty" field' };
|
|
1660
|
+
}
|
|
1661
|
+
const base64urlByteLength = (s) => Math.floor(s.replace(/=+$/, "").length * 6 / 8);
|
|
1662
|
+
const keys = rawKeys.map((k) => {
|
|
1663
|
+
const key = k ?? {};
|
|
1664
|
+
const kty = typeof key.kty === "string" ? key.kty : "unknown";
|
|
1665
|
+
let keySize = null;
|
|
1666
|
+
let curve;
|
|
1667
|
+
if (kty === "RSA" && typeof key.n === "string") keySize = base64urlByteLength(key.n) * 8;
|
|
1668
|
+
else if ((kty === "EC" || kty === "OKP") && typeof key.crv === "string") curve = key.crv;
|
|
1669
|
+
else if (kty === "oct" && typeof key.k === "string") keySize = base64urlByteLength(key.k) * 8;
|
|
1670
|
+
return {
|
|
1671
|
+
kty,
|
|
1672
|
+
use: typeof key.use === "string" ? key.use : void 0,
|
|
1673
|
+
alg: typeof key.alg === "string" ? key.alg : void 0,
|
|
1674
|
+
kid: typeof key.kid === "string" ? key.kid : void 0,
|
|
1675
|
+
keySize,
|
|
1676
|
+
curve
|
|
1677
|
+
};
|
|
1678
|
+
});
|
|
1679
|
+
const kidCounts = /* @__PURE__ */ new Map();
|
|
1680
|
+
for (const k of keys) if (k.kid) kidCounts.set(k.kid, (kidCounts.get(k.kid) ?? 0) + 1);
|
|
1681
|
+
const duplicateKids = [...kidCounts.entries()].filter(([, c]) => c > 1).map(([kid]) => kid);
|
|
1682
|
+
return { keys, count: keys.length, duplicateKids };
|
|
1683
|
+
}
|
|
1684
|
+
server.tool("parse_jwks", "Parse a JSON Web Key Set (or a single bare JWK) and summarize each key's type, use, algorithm, key ID, and approximate size. Does not verify or use the keys cryptographically.", { input: z.string().describe('JWKS JSON (a "keys" array) or a single JWK object') }, async ({ input }) => {
|
|
1685
|
+
const result = _parseJwks(input);
|
|
1686
|
+
if ("error" in result) return errText(result);
|
|
1687
|
+
return text(JSON.stringify(result, null, 2));
|
|
1688
|
+
});
|
|
1401
1689
|
var transport = new StdioServerTransport();
|
|
1402
1690
|
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.6.0",
|
|
4
|
+
"description": "MCP server exposing 101 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",
|