@utilix-tech/mcp 0.79.0 → 0.80.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 +4 -0
- package/README.md +6 -3
- package/dist/index.js +298 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
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.80.0
|
|
6
|
+
- Add `diff_json_patch`, `apply_json_patch` tools: diff two JSON documents into an RFC 6902 JSON Patch operation list (add/remove/replace/move/copy/test), or apply a patch to a target document.
|
|
7
|
+
- Add `parse_connection_string` tool: parse a database connection URI (postgres, mysql, mongodb, redis, amqp, and similar) into its scheme, credentials, hosts, database, and query parameters.
|
|
8
|
+
|
|
5
9
|
## 0.79.0
|
|
6
10
|
- Add `calculate_real_rate_of_return` tool: apply the Fisher equation to solve for whichever of the nominal rate, inflation rate, or inflation-adjusted real rate is missing, given exactly two of the three.
|
|
7
11
|
- Add `calculate_retry_backoff_schedule` tool: compute the full sequence of delays an exponential-backoff retry policy would actually produce, optionally capped and with full/equal jitter ranges, plus the total best-case and worst-case wait across all retries.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 255 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 (18 tools)
|
|
79
79
|
| Tool | Description |
|
|
80
80
|
|------|-------------|
|
|
81
81
|
| `format_json` | Pretty-print and validate a JSON string |
|
|
@@ -86,6 +86,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
86
86
|
| `jsonpath` | Evaluate a JSONPath expression |
|
|
87
87
|
| `resolve_json_pointer` | Resolve an RFC 6901 JSON Pointer against a JSON document |
|
|
88
88
|
| `apply_json_merge_patch` | Apply an RFC 7396 JSON Merge Patch document to a target JSON document |
|
|
89
|
+
| `diff_json_patch` | Compare two JSON documents and generate an RFC 6902 JSON Patch operation list |
|
|
90
|
+
| `apply_json_patch` | Apply a sequence of RFC 6902 JSON Patch operations (add, remove, replace, move, copy, test) to a target document |
|
|
89
91
|
| `validate_json` | Validate JSON against a JSON Schema |
|
|
90
92
|
| `yaml_to_json` | Convert YAML to JSON |
|
|
91
93
|
| `json_to_yaml` | Convert JSON to YAML |
|
|
@@ -279,7 +281,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
279
281
|
| `color_palette` | Generate a color palette from a base color |
|
|
280
282
|
| `color_shades` | Generate shades or tints from a base color |
|
|
281
283
|
|
|
282
|
-
### Code (
|
|
284
|
+
### Code (22 tools)
|
|
283
285
|
| Tool | Description |
|
|
284
286
|
|------|-------------|
|
|
285
287
|
| `format_sql` | Format a SQL query with proper indentation |
|
|
@@ -303,6 +305,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
303
305
|
| `parse_commit_trailers` | Parse a commit message's trailer block (Co-authored-by, Signed-off-by, Fixes, Reviewed-by, etc.) per git's RFC 822-style trailer convention, returning each trailer's key/value plus the body with the trailer block removed |
|
|
304
306
|
| `extract_markdown_footnotes` | Extract every Markdown-Extra-style footnote reference and definition from a document, flagging references with no matching definition and definitions that are never referenced |
|
|
305
307
|
| `validate_aria` | Parse an HTML snippet and check every role and aria-* attribute against a curated table of WAI-ARIA 1.2 roles: unknown role names, an aria-* attribute not permitted on the given role, and a widget role missing its required aria-* state |
|
|
308
|
+
| `parse_connection_string` | Parse a database connection URI (postgres, mysql, mongodb, redis, amqp, and similar) into its scheme, credentials, hosts, database, and query parameters |
|
|
306
309
|
|
|
307
310
|
### Data (16 tools)
|
|
308
311
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -12446,5 +12446,303 @@ server.tool(
|
|
|
12446
12446
|
return text(JSON.stringify(result, null, 2));
|
|
12447
12447
|
}
|
|
12448
12448
|
);
|
|
12449
|
+
function _escapeJsonPointerTokenMcp(token) {
|
|
12450
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
12451
|
+
}
|
|
12452
|
+
function _unescapeJsonPointerTokenMcp(token) {
|
|
12453
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
12454
|
+
}
|
|
12455
|
+
function _parseJsonPointerTokensMcp(pointer) {
|
|
12456
|
+
if (pointer === "") return [];
|
|
12457
|
+
if (!pointer.startsWith("/")) throw new Error(`Invalid JSON Pointer "${pointer}": must start with "/"`);
|
|
12458
|
+
return pointer.slice(1).split("/").map(_unescapeJsonPointerTokenMcp);
|
|
12459
|
+
}
|
|
12460
|
+
function _isJsonPatchObjectMcp(value) {
|
|
12461
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12462
|
+
}
|
|
12463
|
+
function _jsonPatchDeepEqualMcp(a, b) {
|
|
12464
|
+
if (a === b) return true;
|
|
12465
|
+
if (typeof a !== typeof b) return false;
|
|
12466
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
12467
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
12468
|
+
return a.every((v, i) => _jsonPatchDeepEqualMcp(v, b[i]));
|
|
12469
|
+
}
|
|
12470
|
+
if (_isJsonPatchObjectMcp(a) && _isJsonPatchObjectMcp(b)) {
|
|
12471
|
+
const aKeys = Object.keys(a);
|
|
12472
|
+
const bKeys = Object.keys(b);
|
|
12473
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
12474
|
+
return aKeys.every((k) => k in b && _jsonPatchDeepEqualMcp(a[k], b[k]));
|
|
12475
|
+
}
|
|
12476
|
+
return false;
|
|
12477
|
+
}
|
|
12478
|
+
function _jsonPatchGetByTokensMcp(doc, tokens) {
|
|
12479
|
+
let current = doc;
|
|
12480
|
+
for (const token of tokens) {
|
|
12481
|
+
if (Array.isArray(current)) {
|
|
12482
|
+
const index = token === "-" ? current.length - 1 : parseInt(token, 10);
|
|
12483
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length) return { found: false, value: void 0 };
|
|
12484
|
+
current = current[index];
|
|
12485
|
+
} else if (_isJsonPatchObjectMcp(current)) {
|
|
12486
|
+
if (!(token in current)) return { found: false, value: void 0 };
|
|
12487
|
+
current = current[token];
|
|
12488
|
+
} else {
|
|
12489
|
+
return { found: false, value: void 0 };
|
|
12490
|
+
}
|
|
12491
|
+
}
|
|
12492
|
+
return { found: true, value: current };
|
|
12493
|
+
}
|
|
12494
|
+
function _diffJsonPatchMcp(a, b) {
|
|
12495
|
+
const ops = [];
|
|
12496
|
+
_diffJsonPatchValueMcp(a, b, "", ops);
|
|
12497
|
+
return ops;
|
|
12498
|
+
}
|
|
12499
|
+
function _diffJsonPatchValueMcp(a, b, path, ops) {
|
|
12500
|
+
if (_jsonPatchDeepEqualMcp(a, b)) return;
|
|
12501
|
+
if (_isJsonPatchObjectMcp(a) && _isJsonPatchObjectMcp(b)) {
|
|
12502
|
+
for (const key of Object.keys(a)) {
|
|
12503
|
+
if (!(key in b)) ops.push({ op: "remove", path: `${path}/${_escapeJsonPointerTokenMcp(key)}` });
|
|
12504
|
+
}
|
|
12505
|
+
for (const key of Object.keys(b)) {
|
|
12506
|
+
const childPath = `${path}/${_escapeJsonPointerTokenMcp(key)}`;
|
|
12507
|
+
if (!(key in a)) {
|
|
12508
|
+
ops.push({ op: "add", path: childPath, value: b[key] });
|
|
12509
|
+
} else {
|
|
12510
|
+
_diffJsonPatchValueMcp(a[key], b[key], childPath, ops);
|
|
12511
|
+
}
|
|
12512
|
+
}
|
|
12513
|
+
return;
|
|
12514
|
+
}
|
|
12515
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
12516
|
+
const minLen = Math.min(a.length, b.length);
|
|
12517
|
+
for (let i = 0; i < minLen; i++) _diffJsonPatchValueMcp(a[i], b[i], `${path}/${i}`, ops);
|
|
12518
|
+
if (b.length > a.length) {
|
|
12519
|
+
for (let i = a.length; i < b.length; i++) ops.push({ op: "add", path: `${path}/${i}`, value: b[i] });
|
|
12520
|
+
} else if (a.length > b.length) {
|
|
12521
|
+
for (let i = a.length - 1; i >= b.length; i--) ops.push({ op: "remove", path: `${path}/${i}` });
|
|
12522
|
+
}
|
|
12523
|
+
return;
|
|
12524
|
+
}
|
|
12525
|
+
ops.push({ op: "replace", path, value: b });
|
|
12526
|
+
}
|
|
12527
|
+
function _applyJsonPatchMcp(target, operations) {
|
|
12528
|
+
let doc = JSON.parse(JSON.stringify(target ?? null));
|
|
12529
|
+
const applyOne = (op, index) => {
|
|
12530
|
+
if (op.path === void 0 || op.op === void 0) return `Operation ${index}: missing "op" or "path"`;
|
|
12531
|
+
let tokens;
|
|
12532
|
+
try {
|
|
12533
|
+
tokens = _parseJsonPointerTokensMcp(op.path);
|
|
12534
|
+
} catch (e) {
|
|
12535
|
+
return `Operation ${index}: ${e.message}`;
|
|
12536
|
+
}
|
|
12537
|
+
const setAt = (tgtTokens, value) => {
|
|
12538
|
+
if (tgtTokens.length === 0) {
|
|
12539
|
+
doc = value;
|
|
12540
|
+
return null;
|
|
12541
|
+
}
|
|
12542
|
+
const parentTokens = tgtTokens.slice(0, -1);
|
|
12543
|
+
const lastToken = tgtTokens[tgtTokens.length - 1];
|
|
12544
|
+
const { found, value: parent } = _jsonPatchGetByTokensMcp(doc, parentTokens);
|
|
12545
|
+
if (!found) return `Operation ${index}: path "${op.path}" has no parent`;
|
|
12546
|
+
if (Array.isArray(parent)) {
|
|
12547
|
+
const idx = lastToken === "-" ? parent.length : parseInt(lastToken, 10);
|
|
12548
|
+
if (!Number.isInteger(idx) || idx < 0 || idx > parent.length) return `Operation ${index}: array index "${lastToken}" out of bounds`;
|
|
12549
|
+
parent.splice(idx, 0, value);
|
|
12550
|
+
} else if (_isJsonPatchObjectMcp(parent)) {
|
|
12551
|
+
parent[lastToken] = value;
|
|
12552
|
+
} else {
|
|
12553
|
+
return `Operation ${index}: cannot add into a non-object, non-array value at "${op.path}"`;
|
|
12554
|
+
}
|
|
12555
|
+
return null;
|
|
12556
|
+
};
|
|
12557
|
+
const replaceAt = (tgtTokens, value) => {
|
|
12558
|
+
if (tgtTokens.length === 0) {
|
|
12559
|
+
doc = value;
|
|
12560
|
+
return null;
|
|
12561
|
+
}
|
|
12562
|
+
const parentTokens = tgtTokens.slice(0, -1);
|
|
12563
|
+
const lastToken = tgtTokens[tgtTokens.length - 1];
|
|
12564
|
+
const { found, value: parent } = _jsonPatchGetByTokensMcp(doc, parentTokens);
|
|
12565
|
+
if (!found) return `Operation ${index}: path "${op.path}" has no parent`;
|
|
12566
|
+
if (Array.isArray(parent)) {
|
|
12567
|
+
const idx = lastToken === "-" ? parent.length - 1 : parseInt(lastToken, 10);
|
|
12568
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= parent.length) return `Operation ${index}: array index "${lastToken}" out of bounds`;
|
|
12569
|
+
parent[idx] = value;
|
|
12570
|
+
} else if (_isJsonPatchObjectMcp(parent)) {
|
|
12571
|
+
if (!(lastToken in parent)) return `Operation ${index}: path "${op.path}" does not exist`;
|
|
12572
|
+
parent[lastToken] = value;
|
|
12573
|
+
} else {
|
|
12574
|
+
return `Operation ${index}: cannot replace into a non-object, non-array value at "${op.path}"`;
|
|
12575
|
+
}
|
|
12576
|
+
return null;
|
|
12577
|
+
};
|
|
12578
|
+
const removeAt = (tgtTokens) => {
|
|
12579
|
+
if (tgtTokens.length === 0) return { error: `Operation ${index}: cannot remove the whole document (path "")` };
|
|
12580
|
+
const parentTokens = tgtTokens.slice(0, -1);
|
|
12581
|
+
const lastToken = tgtTokens[tgtTokens.length - 1];
|
|
12582
|
+
const { found, value: parent } = _jsonPatchGetByTokensMcp(doc, parentTokens);
|
|
12583
|
+
if (!found) return { error: `Operation ${index}: path "${op.path}" has no parent` };
|
|
12584
|
+
if (Array.isArray(parent)) {
|
|
12585
|
+
const idx = lastToken === "-" ? parent.length - 1 : parseInt(lastToken, 10);
|
|
12586
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= parent.length) return { error: `Operation ${index}: array index "${lastToken}" out of bounds` };
|
|
12587
|
+
const [removed] = parent.splice(idx, 1);
|
|
12588
|
+
return { error: null, removed };
|
|
12589
|
+
}
|
|
12590
|
+
if (_isJsonPatchObjectMcp(parent)) {
|
|
12591
|
+
if (!(lastToken in parent)) return { error: `Operation ${index}: path "${op.path}" does not exist` };
|
|
12592
|
+
const removed = parent[lastToken];
|
|
12593
|
+
delete parent[lastToken];
|
|
12594
|
+
return { error: null, removed };
|
|
12595
|
+
}
|
|
12596
|
+
return { error: `Operation ${index}: cannot remove from a non-object, non-array value at "${op.path}"` };
|
|
12597
|
+
};
|
|
12598
|
+
switch (op.op) {
|
|
12599
|
+
case "add":
|
|
12600
|
+
return setAt(tokens, op.value);
|
|
12601
|
+
case "replace":
|
|
12602
|
+
return replaceAt(tokens, op.value);
|
|
12603
|
+
case "remove":
|
|
12604
|
+
return removeAt(tokens).error;
|
|
12605
|
+
case "test": {
|
|
12606
|
+
const { found, value } = _jsonPatchGetByTokensMcp(doc, tokens);
|
|
12607
|
+
if (!found) return `Operation ${index}: path "${op.path}" does not exist`;
|
|
12608
|
+
if (!_jsonPatchDeepEqualMcp(value, op.value)) return `Operation ${index}: test failed at "${op.path}"`;
|
|
12609
|
+
return null;
|
|
12610
|
+
}
|
|
12611
|
+
case "move": {
|
|
12612
|
+
if (!op.from) return `Operation ${index}: "move" requires "from"`;
|
|
12613
|
+
let fromTokens;
|
|
12614
|
+
try {
|
|
12615
|
+
fromTokens = _parseJsonPointerTokensMcp(op.from);
|
|
12616
|
+
} catch (e) {
|
|
12617
|
+
return `Operation ${index}: ${e.message}`;
|
|
12618
|
+
}
|
|
12619
|
+
const removal = removeAt(fromTokens);
|
|
12620
|
+
if (removal.error) return removal.error;
|
|
12621
|
+
return setAt(tokens, removal.removed);
|
|
12622
|
+
}
|
|
12623
|
+
case "copy": {
|
|
12624
|
+
if (!op.from) return `Operation ${index}: "copy" requires "from"`;
|
|
12625
|
+
let fromTokens;
|
|
12626
|
+
try {
|
|
12627
|
+
fromTokens = _parseJsonPointerTokensMcp(op.from);
|
|
12628
|
+
} catch (e) {
|
|
12629
|
+
return `Operation ${index}: ${e.message}`;
|
|
12630
|
+
}
|
|
12631
|
+
const { found, value } = _jsonPatchGetByTokensMcp(doc, fromTokens);
|
|
12632
|
+
if (!found) return `Operation ${index}: "from" path "${op.from}" does not exist`;
|
|
12633
|
+
return setAt(tokens, JSON.parse(JSON.stringify(value)));
|
|
12634
|
+
}
|
|
12635
|
+
default:
|
|
12636
|
+
return `Operation ${index}: unknown op "${op.op}"`;
|
|
12637
|
+
}
|
|
12638
|
+
};
|
|
12639
|
+
for (let i = 0; i < operations.length; i++) {
|
|
12640
|
+
const err = applyOne(operations[i], i);
|
|
12641
|
+
if (err) return { error: err };
|
|
12642
|
+
}
|
|
12643
|
+
return { result: doc };
|
|
12644
|
+
}
|
|
12645
|
+
server.tool(
|
|
12646
|
+
"diff_json_patch",
|
|
12647
|
+
"Compare two JSON documents and generate an RFC 6902 JSON Patch (a list of add/remove/replace operations) that transforms the first into the second. Object keys produce add/remove/replace; arrays are compared position by position (not a minimal edit-distance diff, so a reordering is not detected as a move).",
|
|
12648
|
+
{
|
|
12649
|
+
a: z.string().describe("The source JSON document, as a JSON string"),
|
|
12650
|
+
b: z.string().describe("The target JSON document, as a JSON string")
|
|
12651
|
+
},
|
|
12652
|
+
async ({ a, b }) => {
|
|
12653
|
+
let aParsed;
|
|
12654
|
+
try {
|
|
12655
|
+
aParsed = JSON.parse(a);
|
|
12656
|
+
} catch {
|
|
12657
|
+
return errText({ error: "First document is not valid JSON" });
|
|
12658
|
+
}
|
|
12659
|
+
let bParsed;
|
|
12660
|
+
try {
|
|
12661
|
+
bParsed = JSON.parse(b);
|
|
12662
|
+
} catch {
|
|
12663
|
+
return errText({ error: "Second document is not valid JSON" });
|
|
12664
|
+
}
|
|
12665
|
+
return text(JSON.stringify({ patch: _diffJsonPatchMcp(aParsed, bParsed) }, null, 2));
|
|
12666
|
+
}
|
|
12667
|
+
);
|
|
12668
|
+
server.tool(
|
|
12669
|
+
"apply_json_patch",
|
|
12670
|
+
"Apply a sequence of RFC 6902 JSON Patch operations (add, remove, replace, move, copy, test) to a target JSON document and return the result, or an error naming the first operation that failed and why.",
|
|
12671
|
+
{
|
|
12672
|
+
target: z.string().describe("The document to patch, as a JSON string"),
|
|
12673
|
+
patch: z.string().describe("The RFC 6902 JSON Patch operation array, as a JSON string")
|
|
12674
|
+
},
|
|
12675
|
+
async ({ target, patch }) => {
|
|
12676
|
+
let targetParsed;
|
|
12677
|
+
try {
|
|
12678
|
+
targetParsed = JSON.parse(target);
|
|
12679
|
+
} catch {
|
|
12680
|
+
return errText({ error: "Target is not valid JSON" });
|
|
12681
|
+
}
|
|
12682
|
+
let operations;
|
|
12683
|
+
try {
|
|
12684
|
+
operations = JSON.parse(patch);
|
|
12685
|
+
} catch {
|
|
12686
|
+
return errText({ error: "Patch is not valid JSON" });
|
|
12687
|
+
}
|
|
12688
|
+
if (!Array.isArray(operations)) return errText({ error: "Patch must be a JSON array of RFC 6902 operations" });
|
|
12689
|
+
const result = _applyJsonPatchMcp(targetParsed, operations);
|
|
12690
|
+
if ("error" in result) return errText(result);
|
|
12691
|
+
return text(JSON.stringify(result, null, 2));
|
|
12692
|
+
}
|
|
12693
|
+
);
|
|
12694
|
+
var _CONNECTION_STRING_RE_MCP = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/(?:([^@/?#]*)@)?([^/?#]*)(?:\/([^?#]*))?(?:\?([^#]*))?$/;
|
|
12695
|
+
function _parseConnectionHostMcp(segment) {
|
|
12696
|
+
const ipv6Match = segment.match(/^\[([^\]]+)\](?::(\d+))?$/);
|
|
12697
|
+
if (ipv6Match) return { host: ipv6Match[1], port: ipv6Match[2] ? parseInt(ipv6Match[2], 10) : null };
|
|
12698
|
+
const lastColon = segment.lastIndexOf(":");
|
|
12699
|
+
if (lastColon === -1) return { host: segment, port: null };
|
|
12700
|
+
const portPart = segment.slice(lastColon + 1);
|
|
12701
|
+
if (!/^\d+$/.test(portPart)) return { host: segment, port: null };
|
|
12702
|
+
return { host: segment.slice(0, lastColon), port: parseInt(portPart, 10) };
|
|
12703
|
+
}
|
|
12704
|
+
function _parseConnectionStringMcp(input) {
|
|
12705
|
+
if (!input || !input.trim()) return { error: "Connection string is empty" };
|
|
12706
|
+
const trimmed = input.trim();
|
|
12707
|
+
const match = trimmed.match(_CONNECTION_STRING_RE_MCP);
|
|
12708
|
+
if (!match) return { error: "Not a valid scheme://[user[:password]@]host[,host2][/database][?params] connection string" };
|
|
12709
|
+
const [, scheme, userinfo, hostPart, databasePart, queryPart] = match;
|
|
12710
|
+
let user = null;
|
|
12711
|
+
let password = null;
|
|
12712
|
+
if (userinfo) {
|
|
12713
|
+
const sepIndex = userinfo.indexOf(":");
|
|
12714
|
+
if (sepIndex === -1) {
|
|
12715
|
+
user = decodeURIComponent(userinfo);
|
|
12716
|
+
} else {
|
|
12717
|
+
user = decodeURIComponent(userinfo.slice(0, sepIndex));
|
|
12718
|
+
password = decodeURIComponent(userinfo.slice(sepIndex + 1));
|
|
12719
|
+
}
|
|
12720
|
+
}
|
|
12721
|
+
const hosts = hostPart ? hostPart.split(",").filter(Boolean).map(_parseConnectionHostMcp) : [];
|
|
12722
|
+
const database = databasePart ? decodeURIComponent(databasePart) : null;
|
|
12723
|
+
const params = {};
|
|
12724
|
+
if (queryPart) {
|
|
12725
|
+
for (const pair of queryPart.split("&")) {
|
|
12726
|
+
if (!pair) continue;
|
|
12727
|
+
const eqIndex = pair.indexOf("=");
|
|
12728
|
+
const key = eqIndex === -1 ? pair : pair.slice(0, eqIndex);
|
|
12729
|
+
const value = eqIndex === -1 ? "" : pair.slice(eqIndex + 1);
|
|
12730
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
12731
|
+
}
|
|
12732
|
+
}
|
|
12733
|
+
return { scheme, user, password, hosts, database, params };
|
|
12734
|
+
}
|
|
12735
|
+
server.tool(
|
|
12736
|
+
"parse_connection_string",
|
|
12737
|
+
"Parse a database connection URI (postgres://, mysql://, mongodb://, mongodb+srv://, redis://, amqp://, and similar generic scheme:// connection strings) into its scheme, credentials, one or more hosts (mongodb-style comma-separated host lists included), database/path, and query parameters. Pure offline parse: never connects, resolves DNS, or validates the driver/scheme combination.",
|
|
12738
|
+
{
|
|
12739
|
+
connectionString: z.string().describe("The database connection string to parse")
|
|
12740
|
+
},
|
|
12741
|
+
async ({ connectionString }) => {
|
|
12742
|
+
const result = _parseConnectionStringMcp(connectionString);
|
|
12743
|
+
if ("error" in result) return errText(result);
|
|
12744
|
+
return text(JSON.stringify(result, null, 2));
|
|
12745
|
+
}
|
|
12746
|
+
);
|
|
12449
12747
|
var transport = new StdioServerTransport();
|
|
12450
12748
|
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.80.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",
|