@utilix-tech/mcp 0.98.0 → 0.99.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 +5 -3
- package/dist/index.js +124 -0
- package/package.json +2 -2
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.99.0
|
|
6
|
+
- Add `calculate_dividend_yield` tool: compute a stock's dividend yield from annual dividend per share and share price, plus payout ratio and retention ratio when trailing EPS is given.
|
|
7
|
+
- Add `check_known_hosts_fingerprints` tool: compute SHA256/MD5 fingerprints for ssh_known_hosts entries the way `ssh-keygen -lf` does, decoding key type and RSA bit length to flag weak or deprecated keys.
|
|
8
|
+
|
|
5
9
|
## 0.98.0
|
|
6
10
|
- Add `convert_timecode` tool: convert between a frame count, SMPTE timecode (HH:MM:SS:FF), and seconds at a given frame rate, handling NTSC drop-frame timecode's skipped frame numbers correctly.
|
|
7
11
|
- Add `validate_geojson` tool: validate a GeoJSON document against RFC 7946's structural rules, geometry type/coordinates shape, polygon ring closure and winding, and a FeatureCollection's required fields.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 291 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
|
|
|
@@ -114,7 +114,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
114
114
|
| `base62_encode` | Encode text as Base62 (alphanumeric, URL-safe) |
|
|
115
115
|
| `base62_decode` | Decode a Base62 string back to text |
|
|
116
116
|
|
|
117
|
-
### Hashing (
|
|
117
|
+
### Hashing (7 tools)
|
|
118
118
|
| Tool | Description |
|
|
119
119
|
|------|-------------|
|
|
120
120
|
| `hash` | Hash a string with MD5, SHA-1, SHA-256, SHA-384, or SHA-512 |
|
|
@@ -123,6 +123,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
123
123
|
| `bcrypt_verify` | Verify a password against a bcrypt hash |
|
|
124
124
|
| `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 |
|
|
125
125
|
| `generate_etag` | Compute a content-hash ETag (MD5/SHA-1/SHA-256) for a response header, with an optional weak-validator prefix |
|
|
126
|
+
| `check_known_hosts_fingerprints` | Parse an ssh_known_hosts file and compute each key's SHA256/MD5 fingerprint the way `ssh-keygen -lf` does, decoding key type and RSA bit length to flag weak or deprecated keys |
|
|
126
127
|
|
|
127
128
|
### Generators (7 tools)
|
|
128
129
|
| Tool | Description |
|
|
@@ -171,7 +172,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
171
172
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
172
173
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
173
174
|
|
|
174
|
-
### Units (
|
|
175
|
+
### Units (54 tools)
|
|
175
176
|
| Tool | Description |
|
|
176
177
|
|------|-------------|
|
|
177
178
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -228,6 +229,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
228
229
|
| `intersect_semver_ranges` | Determine whether two semver ranges overlap and, if so, the intersecting range, supporting the same caret/tilde/comparison-operator/exact-version syntax as the semver tool |
|
|
229
230
|
| `validate_routing_number` | Validate a 9-digit US bank routing number (ABA/RTN) via its weighted mod-10 checksum, and identify its routing number type and Federal Reserve district |
|
|
230
231
|
| `calculate_position_size` | Compute a trade's position size from account size, risk percent per trade, entry price, and stop-loss price, plus the risk/reward ratio against an optional target price |
|
|
232
|
+
| `calculate_dividend_yield` | Compute a stock's dividend yield from annual dividend per share and share price, plus payout ratio and retention ratio when trailing EPS is given |
|
|
231
233
|
|
|
232
234
|
### API / Network (55 tools)
|
|
233
235
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -15744,6 +15744,130 @@ server.tool(
|
|
|
15744
15744
|
return text(JSON.stringify(result, null, 2));
|
|
15745
15745
|
}
|
|
15746
15746
|
);
|
|
15747
|
+
function _calculateDividendYieldMcp(input) {
|
|
15748
|
+
const { annualDividendPerShare, sharePrice, epsPerShare } = input;
|
|
15749
|
+
if (!(annualDividendPerShare >= 0)) return { error: "annualDividendPerShare must be greater than or equal to zero" };
|
|
15750
|
+
if (!(sharePrice > 0)) return { error: "sharePrice must be greater than zero" };
|
|
15751
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
15752
|
+
const result = {
|
|
15753
|
+
annualDividendPerShare: round4(annualDividendPerShare),
|
|
15754
|
+
sharePrice: round4(sharePrice),
|
|
15755
|
+
dividendYieldPercent: round4(annualDividendPerShare / sharePrice * 100)
|
|
15756
|
+
};
|
|
15757
|
+
if (epsPerShare !== void 0) {
|
|
15758
|
+
if (!(epsPerShare > 0)) return { error: "epsPerShare must be greater than zero when provided" };
|
|
15759
|
+
const payoutRatioPercent = annualDividendPerShare / epsPerShare * 100;
|
|
15760
|
+
result.epsPerShare = round4(epsPerShare);
|
|
15761
|
+
result.payoutRatioPercent = round4(payoutRatioPercent);
|
|
15762
|
+
result.retentionRatioPercent = round4(100 - payoutRatioPercent);
|
|
15763
|
+
}
|
|
15764
|
+
return result;
|
|
15765
|
+
}
|
|
15766
|
+
server.tool(
|
|
15767
|
+
"calculate_dividend_yield",
|
|
15768
|
+
"Compute a stock's dividend yield (annual dividend per share / share price) and, given trailing EPS, its payout ratio (dividend / EPS) and retention ratio (100 - payout ratio). A payout ratio over 100% is valid and reported as-is. Pure arithmetic, no live quote feed.",
|
|
15769
|
+
{
|
|
15770
|
+
annualDividendPerShare: z.number().describe("Total dividend paid per share over the trailing year"),
|
|
15771
|
+
sharePrice: z.number().describe("Current share price"),
|
|
15772
|
+
epsPerShare: z.number().optional().describe("Optional trailing earnings per share, used to compute payout and retention ratio")
|
|
15773
|
+
},
|
|
15774
|
+
async ({ annualDividendPerShare, sharePrice, epsPerShare }) => {
|
|
15775
|
+
const result = _calculateDividendYieldMcp({ annualDividendPerShare, sharePrice, epsPerShare });
|
|
15776
|
+
if ("error" in result) return errText(result);
|
|
15777
|
+
return text(JSON.stringify(result, null, 2));
|
|
15778
|
+
}
|
|
15779
|
+
);
|
|
15780
|
+
function _readSshStringFieldMcp(buf, offset) {
|
|
15781
|
+
if (offset + 4 > buf.length) return null;
|
|
15782
|
+
const len = buf.readUInt32BE(offset);
|
|
15783
|
+
const start = offset + 4;
|
|
15784
|
+
const end = start + len;
|
|
15785
|
+
if (end > buf.length) return null;
|
|
15786
|
+
return { value: buf.subarray(start, end), next: end };
|
|
15787
|
+
}
|
|
15788
|
+
function _mpintBitLengthMcp(buf) {
|
|
15789
|
+
let i = 0;
|
|
15790
|
+
while (i < buf.length - 1 && buf[i] === 0) i++;
|
|
15791
|
+
const trimmed = buf.subarray(i);
|
|
15792
|
+
if (trimmed.length === 0) return 0;
|
|
15793
|
+
let bits = (trimmed.length - 1) * 8;
|
|
15794
|
+
let firstByte = trimmed[0];
|
|
15795
|
+
while (firstByte > 0) {
|
|
15796
|
+
bits++;
|
|
15797
|
+
firstByte >>= 1;
|
|
15798
|
+
}
|
|
15799
|
+
return bits;
|
|
15800
|
+
}
|
|
15801
|
+
function _analyzeKnownHostsKeyMcp(keyType, blob) {
|
|
15802
|
+
if (keyType === "ssh-dss") {
|
|
15803
|
+
return { weak: true, weakReason: "DSA (ssh-dss) keys are deprecated and considered weak" };
|
|
15804
|
+
}
|
|
15805
|
+
if (keyType === "ssh-rsa") {
|
|
15806
|
+
const typeField = _readSshStringFieldMcp(blob, 0);
|
|
15807
|
+
if (!typeField) return { weak: false };
|
|
15808
|
+
const eField = _readSshStringFieldMcp(blob, typeField.next);
|
|
15809
|
+
if (!eField) return { weak: false };
|
|
15810
|
+
const nField = _readSshStringFieldMcp(blob, eField.next);
|
|
15811
|
+
if (!nField) return { weak: false };
|
|
15812
|
+
const bits = _mpintBitLengthMcp(nField.value);
|
|
15813
|
+
return bits > 0 && bits < 2048 ? { bits, weak: true, weakReason: `RSA modulus is only ${bits} bits; 2048+ is recommended` } : { bits, weak: false };
|
|
15814
|
+
}
|
|
15815
|
+
if (keyType === "ssh-ed25519") {
|
|
15816
|
+
return { bits: 256, weak: false };
|
|
15817
|
+
}
|
|
15818
|
+
if (keyType.startsWith("ecdsa-sha2-")) {
|
|
15819
|
+
const curve = keyType.split("-").pop() ?? "";
|
|
15820
|
+
const bits = curve === "nistp256" ? 256 : curve === "nistp384" ? 384 : curve === "nistp521" ? 521 : void 0;
|
|
15821
|
+
return { bits, weak: false };
|
|
15822
|
+
}
|
|
15823
|
+
return { weak: false };
|
|
15824
|
+
}
|
|
15825
|
+
function _checkKnownHostsFingerprintsMcp(input) {
|
|
15826
|
+
if (typeof input !== "string" || input.trim().length === 0) {
|
|
15827
|
+
return { error: "input is required" };
|
|
15828
|
+
}
|
|
15829
|
+
const lines = input.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
15830
|
+
if (lines.length === 0) {
|
|
15831
|
+
return { error: "No known_hosts entries found" };
|
|
15832
|
+
}
|
|
15833
|
+
const entries = [];
|
|
15834
|
+
for (const line of lines) {
|
|
15835
|
+
const parts = line.split(/\s+/);
|
|
15836
|
+
let idx = 0;
|
|
15837
|
+
if (parts[0] === "@cert-authority" || parts[0] === "@revoked") idx = 1;
|
|
15838
|
+
if (parts.length < idx + 3) return { error: `Malformed line: "${line}"` };
|
|
15839
|
+
const host = parts[idx];
|
|
15840
|
+
const keyType = parts[idx + 1];
|
|
15841
|
+
const base64Key = parts[idx + 2];
|
|
15842
|
+
const blob = Buffer.from(base64Key, "base64");
|
|
15843
|
+
if (blob.length === 0) return { error: `Invalid base64 key for host "${host}"` };
|
|
15844
|
+
const sha256 = createHash("sha256").update(blob).digest("base64").replace(/=+$/, "");
|
|
15845
|
+
const md5 = createHash("md5").update(blob).digest("hex").replace(/(.{2})(?=.)/g, "$1:");
|
|
15846
|
+
const analysis = _analyzeKnownHostsKeyMcp(keyType, blob);
|
|
15847
|
+
entries.push({
|
|
15848
|
+
host,
|
|
15849
|
+
keyType,
|
|
15850
|
+
fingerprintSha256: `SHA256:${sha256}`,
|
|
15851
|
+
fingerprintMd5: `MD5:${md5}`,
|
|
15852
|
+
bits: analysis.bits,
|
|
15853
|
+
weak: analysis.weak,
|
|
15854
|
+
weakReason: analysis.weakReason
|
|
15855
|
+
});
|
|
15856
|
+
}
|
|
15857
|
+
return { entries };
|
|
15858
|
+
}
|
|
15859
|
+
server.tool(
|
|
15860
|
+
"check_known_hosts_fingerprints",
|
|
15861
|
+
"Parse an ssh_known_hosts file (or a single `host keytype base64key` line) and compute each entry's SHA256 and MD5 fingerprint the same way `ssh-keygen -lf` does, decoding the key type and, for RSA, the modulus bit length, flagging a weak RSA key (<2048 bits) or a deprecated ssh-dss (DSA) key.",
|
|
15862
|
+
{
|
|
15863
|
+
input: z.string().describe("ssh_known_hosts file contents, or a single entry line")
|
|
15864
|
+
},
|
|
15865
|
+
async ({ input }) => {
|
|
15866
|
+
const result = _checkKnownHostsFingerprintsMcp(input);
|
|
15867
|
+
if ("error" in result) return errText(result);
|
|
15868
|
+
return text(JSON.stringify(result, null, 2));
|
|
15869
|
+
}
|
|
15870
|
+
);
|
|
15747
15871
|
var _WEASEL_PHRASES_MCP = [
|
|
15748
15872
|
"many experts believe",
|
|
15749
15873
|
"some experts say",
|
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.99.0",
|
|
4
|
+
"description": "MCP server exposing 291 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",
|