@utilix-tech/mcp 0.7.0 → 0.8.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 +8 -2
- package/dist/index.js +131 -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 104 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
|
|
|
@@ -121,7 +121,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
121
121
|
| `generate_qr` | Generate a QR code as an SVG string |
|
|
122
122
|
| `generate_random_data` | Generate random mock data: names, emails, UUIDs, IPs, etc. |
|
|
123
123
|
|
|
124
|
-
### Text (
|
|
124
|
+
### Text (11 tools)
|
|
125
125
|
| Tool | Description |
|
|
126
126
|
|------|-------------|
|
|
127
127
|
| `convert_case` | Convert text case: camelCase, snake_case, PascalCase, etc. |
|
|
@@ -134,6 +134,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
134
134
|
| `html_to_markdown` | Convert HTML to Markdown |
|
|
135
135
|
| `line_operations` | Sort, deduplicate, reverse, shuffle, trim, or number lines |
|
|
136
136
|
| `detect_passive_voice` | Flag sentences written in passive voice, e.g. "was written", "were approved by the team" |
|
|
137
|
+
| `score_readability` | Score text with Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog Index |
|
|
137
138
|
|
|
138
139
|
### Time (5 tools)
|
|
139
140
|
| Tool | Description |
|
|
@@ -241,6 +242,11 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
241
242
|
|------|-------------|
|
|
242
243
|
| `read_pdf_metadata` | Read title, author, dates, page count, and encryption flag from a PDF's trailer/Info dictionary: no PDF rendering library required |
|
|
243
244
|
|
|
245
|
+
### Audio (1 tool)
|
|
246
|
+
| Tool | Description |
|
|
247
|
+
|------|-------------|
|
|
248
|
+
| `read_wav_info` | Read sample rate, channel count, bit depth, and duration from a WAV file's RIFF/fmt/data chunk headers: no audio library required |
|
|
249
|
+
|
|
244
250
|
## Requirements
|
|
245
251
|
|
|
246
252
|
- Node.js 18+
|
package/dist/index.js
CHANGED
|
@@ -1876,5 +1876,136 @@ server.tool("parse_jwks", "Parse a JSON Web Key Set (or a single bare JWK) and s
|
|
|
1876
1876
|
if ("error" in result) return errText(result);
|
|
1877
1877
|
return text(JSON.stringify(result, null, 2));
|
|
1878
1878
|
});
|
|
1879
|
+
var _WAV_AUDIO_FORMAT_LABELS = { 1: "PCM", 3: "IEEE float", 6: "A-law", 7: "mu-law", 65534: "Extensible" };
|
|
1880
|
+
function _readWavAscii4(bytes, offset) {
|
|
1881
|
+
return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
1882
|
+
}
|
|
1883
|
+
function _readWavInfo(bytes) {
|
|
1884
|
+
if (bytes.length < 12) return { error: "File is too small to be a valid WAV file" };
|
|
1885
|
+
if (_readWavAscii4(bytes, 0) !== "RIFF") return { error: 'Not a RIFF file (missing "RIFF" header)' };
|
|
1886
|
+
if (_readWavAscii4(bytes, 8) !== "WAVE") return { error: 'Not a WAVE file (missing "WAVE" format marker)' };
|
|
1887
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1888
|
+
const fileSize = view.getUint32(4, true) + 8;
|
|
1889
|
+
let offset = 12;
|
|
1890
|
+
let fmt = null;
|
|
1891
|
+
let dataSize = null;
|
|
1892
|
+
while (offset + 8 <= bytes.length) {
|
|
1893
|
+
const chunkId = _readWavAscii4(bytes, offset);
|
|
1894
|
+
const chunkSize = view.getUint32(offset + 4, true);
|
|
1895
|
+
const dataOffset = offset + 8;
|
|
1896
|
+
if (chunkId === "fmt ") {
|
|
1897
|
+
if (dataOffset + 16 > bytes.length) return { error: 'Truncated "fmt " chunk' };
|
|
1898
|
+
fmt = {
|
|
1899
|
+
audioFormat: view.getUint16(dataOffset, true),
|
|
1900
|
+
channels: view.getUint16(dataOffset + 2, true),
|
|
1901
|
+
sampleRate: view.getUint32(dataOffset + 4, true),
|
|
1902
|
+
byteRate: view.getUint32(dataOffset + 8, true),
|
|
1903
|
+
blockAlign: view.getUint16(dataOffset + 12, true),
|
|
1904
|
+
bitsPerSample: view.getUint16(dataOffset + 14, true)
|
|
1905
|
+
};
|
|
1906
|
+
} else if (chunkId === "data") {
|
|
1907
|
+
dataSize = Math.min(chunkSize, bytes.length - dataOffset);
|
|
1908
|
+
}
|
|
1909
|
+
offset = dataOffset + chunkSize + chunkSize % 2;
|
|
1910
|
+
if (fmt && dataSize !== null) break;
|
|
1911
|
+
}
|
|
1912
|
+
if (!fmt) return { error: 'No "fmt " chunk found in this WAV file' };
|
|
1913
|
+
if (dataSize === null) return { error: 'No "data" chunk found in this WAV file' };
|
|
1914
|
+
if (fmt.byteRate <= 0) return { error: 'Invalid byte rate in "fmt " chunk' };
|
|
1915
|
+
const durationSeconds = Math.round(dataSize / fmt.byteRate * 1e3) / 1e3;
|
|
1916
|
+
return {
|
|
1917
|
+
audioFormat: fmt.audioFormat,
|
|
1918
|
+
audioFormatLabel: _WAV_AUDIO_FORMAT_LABELS[fmt.audioFormat] ?? `Unknown (0x${fmt.audioFormat.toString(16)})`,
|
|
1919
|
+
channels: fmt.channels,
|
|
1920
|
+
sampleRate: fmt.sampleRate,
|
|
1921
|
+
byteRate: fmt.byteRate,
|
|
1922
|
+
blockAlign: fmt.blockAlign,
|
|
1923
|
+
bitsPerSample: fmt.bitsPerSample,
|
|
1924
|
+
dataSize,
|
|
1925
|
+
durationSeconds,
|
|
1926
|
+
fileSize
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
server.tool("read_wav_info", "Read sample rate, channel count, bit depth, and duration directly from a WAV file's RIFF/fmt/data chunk headers. No audio library required.", { wav_base64: z.string().describe("Base64-encoded WAV file bytes, optionally prefixed with a data: URL scheme") }, async ({ wav_base64 }) => {
|
|
1930
|
+
const bytes = new Uint8Array(Buffer.from(wav_base64.replace(/^data:.*;base64,/, ""), "base64"));
|
|
1931
|
+
const result = _readWavInfo(bytes);
|
|
1932
|
+
if ("error" in result) return errText(result);
|
|
1933
|
+
return text(JSON.stringify(result, null, 2));
|
|
1934
|
+
});
|
|
1935
|
+
function _readabilitySplitIntoSentences(text2) {
|
|
1936
|
+
const matches = text2.match(/[^.!?]+[.!?]*/g);
|
|
1937
|
+
if (!matches) return [];
|
|
1938
|
+
return matches.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1939
|
+
}
|
|
1940
|
+
function _countReadabilitySyllables(word) {
|
|
1941
|
+
const w = word.toLowerCase().replace(/[^a-z]/g, "");
|
|
1942
|
+
if (w.length === 0) return 0;
|
|
1943
|
+
if (w.length <= 3) return 1;
|
|
1944
|
+
let count = 0;
|
|
1945
|
+
let prevWasVowel = false;
|
|
1946
|
+
for (const ch of w) {
|
|
1947
|
+
const isVowel = "aeiouy".includes(ch);
|
|
1948
|
+
if (isVowel && !prevWasVowel) count++;
|
|
1949
|
+
prevWasVowel = isVowel;
|
|
1950
|
+
}
|
|
1951
|
+
if (w.endsWith("e") && !w.endsWith("le") && count > 1) count--;
|
|
1952
|
+
if (w.endsWith("le") && w.length > 2 && !"aeiouy".includes(w[w.length - 3])) count++;
|
|
1953
|
+
return Math.max(count, 1);
|
|
1954
|
+
}
|
|
1955
|
+
function _readabilityGradeLabel(fleschReadingEase) {
|
|
1956
|
+
if (fleschReadingEase >= 90) return "Very easy (5th grade)";
|
|
1957
|
+
if (fleschReadingEase >= 80) return "Easy (6th grade)";
|
|
1958
|
+
if (fleschReadingEase >= 70) return "Fairly easy (7th grade)";
|
|
1959
|
+
if (fleschReadingEase >= 60) return "Standard (8th-9th grade)";
|
|
1960
|
+
if (fleschReadingEase >= 50) return "Fairly difficult (10th-12th grade)";
|
|
1961
|
+
if (fleschReadingEase >= 30) return "Difficult (college)";
|
|
1962
|
+
return "Very difficult (college graduate)";
|
|
1963
|
+
}
|
|
1964
|
+
function _round1Readability(n) {
|
|
1965
|
+
return Math.round(n * 10) / 10;
|
|
1966
|
+
}
|
|
1967
|
+
function _scoreReadability(input) {
|
|
1968
|
+
const sentences = _readabilitySplitIntoSentences(input);
|
|
1969
|
+
const words = input.match(/[a-zA-Z']+/g) ?? [];
|
|
1970
|
+
const sentenceCount = Math.max(sentences.length, 1);
|
|
1971
|
+
const wordCount = words.length;
|
|
1972
|
+
const syllableCounts = words.map(_countReadabilitySyllables);
|
|
1973
|
+
const syllableCount = syllableCounts.reduce((a, b) => a + b, 0);
|
|
1974
|
+
const complexWordCount = syllableCounts.filter((c) => c >= 3).length;
|
|
1975
|
+
if (wordCount === 0) {
|
|
1976
|
+
return {
|
|
1977
|
+
wordCount: 0,
|
|
1978
|
+
sentenceCount: 0,
|
|
1979
|
+
syllableCount: 0,
|
|
1980
|
+
complexWordCount: 0,
|
|
1981
|
+
avgWordsPerSentence: 0,
|
|
1982
|
+
avgSyllablesPerWord: 0,
|
|
1983
|
+
fleschReadingEase: 0,
|
|
1984
|
+
fleschKincaidGrade: 0,
|
|
1985
|
+
gunningFog: 0,
|
|
1986
|
+
readingLevel: "No text to score"
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
const avgWordsPerSentence = wordCount / sentenceCount;
|
|
1990
|
+
const avgSyllablesPerWord = syllableCount / wordCount;
|
|
1991
|
+
const fleschReadingEase = _round1Readability(206.835 - 1.015 * avgWordsPerSentence - 84.6 * avgSyllablesPerWord);
|
|
1992
|
+
const fleschKincaidGrade = _round1Readability(0.39 * avgWordsPerSentence + 11.8 * avgSyllablesPerWord - 15.59);
|
|
1993
|
+
const gunningFog = _round1Readability(0.4 * (avgWordsPerSentence + 100 * (complexWordCount / wordCount)));
|
|
1994
|
+
return {
|
|
1995
|
+
wordCount,
|
|
1996
|
+
sentenceCount: sentences.length,
|
|
1997
|
+
syllableCount,
|
|
1998
|
+
complexWordCount,
|
|
1999
|
+
avgWordsPerSentence: _round1Readability(avgWordsPerSentence),
|
|
2000
|
+
avgSyllablesPerWord: _round1Readability(avgSyllablesPerWord),
|
|
2001
|
+
fleschReadingEase,
|
|
2002
|
+
fleschKincaidGrade,
|
|
2003
|
+
gunningFog,
|
|
2004
|
+
readingLevel: _readabilityGradeLabel(fleschReadingEase)
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
server.tool("score_readability", "Score text with Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog Index, using a heuristic syllable counter.", { text: z.string() }, async ({ text: input }) => {
|
|
2008
|
+
return { content: [{ type: "text", text: JSON.stringify(_scoreReadability(input), null, 2) }] };
|
|
2009
|
+
});
|
|
1879
2010
|
var transport = new StdioServerTransport();
|
|
1880
2011
|
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.8.0",
|
|
4
|
+
"description": "MCP server exposing 104 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",
|