@utilix-tech/mcp 0.38.0 → 0.40.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 +9 -5
- package/dist/index.js +234 -0
- 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.40.0
|
|
6
|
+
- Add `generate_sri_hash` tool: compute SHA-256/384/512 Subresource Integrity hashes for a script or stylesheet's content, formatted as the `<algorithm>-<base64>` string an HTML `integrity` attribute expects.
|
|
7
|
+
- Add `analyze_email_headers` tool: parse a raw email header block into structured From/To/Subject fields, the Received hop chain in order, and SPF/DKIM/DMARC results from an Authentication-Results header.
|
|
8
|
+
|
|
9
|
+
## 0.39.0
|
|
10
|
+
- Add `compare_unit_prices` tool: compare two or more package options by price and quantity to find the true cost per unit, flag the cheapest, and report how much more expensive each other option is by percentage.
|
|
11
|
+
- Add `csv_to_sql_insert` tool: convert CSV data with a header row into parameterless SQL INSERT statements, with basic type inference (numbers/booleans unquoted, empty cells NULL) and ANSI/MySQL identifier quoting.
|
|
12
|
+
|
|
5
13
|
## 0.38.0
|
|
6
14
|
- Add `check_set_cookie_header` tool: parse a Set-Cookie header value and validate its attribute combination, flagging SameSite=None missing Secure, __Host-/__Secure- prefix violations, a missing Secure attribute, and a Max-Age past 400 days.
|
|
7
15
|
- Add `test_gitignore_patterns` tool: test a list of paths against a .gitignore file's patterns, following git's last-match-wins and negation rules, and report which pattern and line number decided each path's outcome.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 165 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
|
|
|
@@ -110,13 +110,14 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
110
110
|
| `base62_encode` | Encode text as Base62 (alphanumeric, URL-safe) |
|
|
111
111
|
| `base62_decode` | Decode a Base62 string back to text |
|
|
112
112
|
|
|
113
|
-
### Hashing (
|
|
113
|
+
### Hashing (5 tools)
|
|
114
114
|
| Tool | Description |
|
|
115
115
|
|------|-------------|
|
|
116
116
|
| `hash` | Hash a string with MD5, SHA-1, SHA-256, SHA-384, or SHA-512 |
|
|
117
117
|
| `hash_all` | Hash a string with all algorithms at once |
|
|
118
118
|
| `bcrypt_hash` | Hash a password using bcrypt |
|
|
119
119
|
| `bcrypt_verify` | Verify a password against a bcrypt hash |
|
|
120
|
+
| `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 |
|
|
120
121
|
|
|
121
122
|
### Generators (6 tools)
|
|
122
123
|
| Tool | Description |
|
|
@@ -157,7 +158,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
157
158
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
158
159
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
159
160
|
|
|
160
|
-
### Units (
|
|
161
|
+
### Units (24 tools)
|
|
161
162
|
| Tool | Description |
|
|
162
163
|
|------|-------------|
|
|
163
164
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -183,8 +184,9 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
183
184
|
| `calculate_emi` | Compute the Equated Monthly Installment for a loan from principal, annual rate, and tenure in years or months, with a year-by-year principal/interest breakdown |
|
|
184
185
|
| `calculate_savings_goal` | Solve for months to reach a savings goal at a fixed monthly contribution, or the monthly contribution required to hit a goal by a target month count |
|
|
185
186
|
| `calculate_roi` | Compute return on investment percent from an initial and final value, plus the annualized (CAGR) return given an optional holding period in years |
|
|
187
|
+
| `compare_unit_prices` | Compare two or more package options by price and quantity to find the true cost per unit, flag the cheapest, and report how much more expensive each other option is |
|
|
186
188
|
|
|
187
|
-
### API / Network (
|
|
189
|
+
### API / Network (16 tools)
|
|
188
190
|
| Tool | Description |
|
|
189
191
|
|------|-------------|
|
|
190
192
|
| `decode_jwt` | Decode a JWT: header, payload, and expiry |
|
|
@@ -202,6 +204,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
202
204
|
| `parse_ipv6_address` | Expand an IPv6 address to its full 8-group form or compress it to its RFC 5952 canonical shortest form, classify its address type, and handle an embedded IPv4 suffix or zone ID |
|
|
203
205
|
| `build_link_header` | Build and explain an HTTP Link response header from URL/rel pairs (preload, preconnect, prefetch, canonical, pagination, etc.) |
|
|
204
206
|
| `check_set_cookie_header` | Parse a Set-Cookie header and validate its attribute combination: SameSite=None requires Secure, __Host-/__Secure- prefix rules, missing Secure, overly long Max-Age |
|
|
207
|
+
| `analyze_email_headers` | Parse a raw email header block into structured fields, the Received hop chain, and SPF/DKIM/DMARC results from an Authentication-Results header |
|
|
205
208
|
|
|
206
209
|
### Color (4 tools)
|
|
207
210
|
| Tool | Description |
|
|
@@ -227,7 +230,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
227
230
|
| `lint_conventional_commit` | Lint a commit message against the Conventional Commits v1.0.0 spec: header format, blank-line separation, footer tokens, and BREAKING CHANGE casing |
|
|
228
231
|
| `test_gitignore_patterns` | Test a list of paths against a .gitignore file's patterns, following git's last-match-wins and negation rules |
|
|
229
232
|
|
|
230
|
-
### Data (
|
|
233
|
+
### Data (9 tools)
|
|
231
234
|
| Tool | Description |
|
|
232
235
|
|------|-------------|
|
|
233
236
|
| `validate_yaml` | Validate YAML syntax |
|
|
@@ -238,6 +241,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
238
241
|
| `diff_env_example` | Diff a .env file against its .env.example template: flags keys missing from either side, plus empty placeholder values |
|
|
239
242
|
| `parse_markdown_front_matter` | Split a Markdown document into its YAML front matter fields and body content |
|
|
240
243
|
| `generate_markdown_toc` | Scan a Markdown document's ATX headings and build a nested table of contents with GitHub-style anchor slugs |
|
|
244
|
+
| `csv_to_sql_insert` | Convert CSV data with a header row into parameterless SQL INSERT statements, with basic type inference and ANSI/MySQL identifier quoting |
|
|
241
245
|
|
|
242
246
|
### CSS (2 tools)
|
|
243
247
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -6596,5 +6596,239 @@ server.tool(
|
|
|
6596
6596
|
return text(JSON.stringify(result, null, 2));
|
|
6597
6597
|
}
|
|
6598
6598
|
);
|
|
6599
|
+
function _compareUnitPricesMcp(items) {
|
|
6600
|
+
if (!Array.isArray(items) || items.length < 2) {
|
|
6601
|
+
return { error: "At least 2 items are required to compare" };
|
|
6602
|
+
}
|
|
6603
|
+
for (const item of items) {
|
|
6604
|
+
if (!item.label || typeof item.label !== "string") {
|
|
6605
|
+
return { error: "Each item must have a non-empty label" };
|
|
6606
|
+
}
|
|
6607
|
+
if (typeof item.price !== "number" || !Number.isFinite(item.price) || item.price <= 0) {
|
|
6608
|
+
return { error: `Item "${item.label}" must have a price greater than zero` };
|
|
6609
|
+
}
|
|
6610
|
+
if (typeof item.quantity !== "number" || !Number.isFinite(item.quantity) || item.quantity <= 0) {
|
|
6611
|
+
return { error: `Item "${item.label}" must have a quantity greater than zero` };
|
|
6612
|
+
}
|
|
6613
|
+
}
|
|
6614
|
+
const round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
6615
|
+
const unitPrices = items.map((item) => item.price / item.quantity);
|
|
6616
|
+
const bestUnitPrice = Math.min(...unitPrices);
|
|
6617
|
+
const results = items.map((item, i) => ({
|
|
6618
|
+
label: item.label,
|
|
6619
|
+
price: round4(item.price),
|
|
6620
|
+
quantity: round4(item.quantity),
|
|
6621
|
+
unitPrice: round4(unitPrices[i]),
|
|
6622
|
+
isBest: unitPrices[i] === bestUnitPrice,
|
|
6623
|
+
percentMoreThanBest: round4((unitPrices[i] - bestUnitPrice) / bestUnitPrice * 100)
|
|
6624
|
+
}));
|
|
6625
|
+
const bestLabel = results.find((r) => r.isBest).label;
|
|
6626
|
+
return { items: results, bestLabel };
|
|
6627
|
+
}
|
|
6628
|
+
server.tool(
|
|
6629
|
+
"compare_unit_prices",
|
|
6630
|
+
"Compare two or more package options by price and quantity to find the true cost per unit (price / quantity) for each, flag the cheapest, and report how much more expensive each other option is by percentage. Unit-agnostic: pass quantity in whatever unit makes the options comparable (grams, litres, item count, etc.), no conversion is done.",
|
|
6631
|
+
{
|
|
6632
|
+
items: z.array(z.object({
|
|
6633
|
+
label: z.string().describe("Name for this option"),
|
|
6634
|
+
price: z.number().describe("Total price of this option (must be greater than zero)"),
|
|
6635
|
+
quantity: z.number().describe("Quantity in whatever unit makes options comparable (must be greater than zero)")
|
|
6636
|
+
})).min(2).describe("Two or more package options to compare")
|
|
6637
|
+
},
|
|
6638
|
+
async ({ items }) => {
|
|
6639
|
+
const result = _compareUnitPricesMcp(items);
|
|
6640
|
+
if ("error" in result) return errText(result);
|
|
6641
|
+
return text(JSON.stringify(result, null, 2));
|
|
6642
|
+
}
|
|
6643
|
+
);
|
|
6644
|
+
function _parseCsvRowForSqlMcp(line, delimiter) {
|
|
6645
|
+
const fields = [];
|
|
6646
|
+
let cur = "";
|
|
6647
|
+
let inQuote = false;
|
|
6648
|
+
for (let i = 0; i < line.length; i++) {
|
|
6649
|
+
if (line[i] === '"') {
|
|
6650
|
+
if (inQuote && line[i + 1] === '"') {
|
|
6651
|
+
cur += '"';
|
|
6652
|
+
i++;
|
|
6653
|
+
} else inQuote = !inQuote;
|
|
6654
|
+
} else if (line[i] === delimiter && !inQuote) {
|
|
6655
|
+
fields.push(cur);
|
|
6656
|
+
cur = "";
|
|
6657
|
+
} else {
|
|
6658
|
+
cur += line[i];
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
fields.push(cur);
|
|
6662
|
+
return fields;
|
|
6663
|
+
}
|
|
6664
|
+
function _quoteSqlIdentifierMcp(name, dialect) {
|
|
6665
|
+
if (dialect === "mysql") return "`" + name.replace(/`/g, "``") + "`";
|
|
6666
|
+
return '"' + name.replace(/"/g, '""') + '"';
|
|
6667
|
+
}
|
|
6668
|
+
function _formatSqlValueMcp(raw) {
|
|
6669
|
+
const trimmed = raw.trim();
|
|
6670
|
+
if (trimmed === "") return "NULL";
|
|
6671
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return trimmed;
|
|
6672
|
+
if (/^(true|false)$/i.test(trimmed)) return trimmed.toLowerCase();
|
|
6673
|
+
return "'" + trimmed.replace(/'/g, "''") + "'";
|
|
6674
|
+
}
|
|
6675
|
+
function _csvToSqlInsertMcp(input, tableName, dialect, batchSize) {
|
|
6676
|
+
if (!input || input.trim() === "") return { error: "Input is empty" };
|
|
6677
|
+
const lines = input.replace(/\r\n/g, "\n").split("\n").filter((l) => l.trim() !== "");
|
|
6678
|
+
if (lines.length === 0) return { error: "No data found" };
|
|
6679
|
+
const allRows = lines.map((l) => _parseCsvRowForSqlMcp(l, ","));
|
|
6680
|
+
const headers = allRows[0].map((h) => h.trim());
|
|
6681
|
+
const rows = allRows.slice(1);
|
|
6682
|
+
const table = (tableName ?? "").trim() || "my_table";
|
|
6683
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table)) {
|
|
6684
|
+
return { error: "tableName must start with a letter or underscore and contain only letters, digits, and underscores" };
|
|
6685
|
+
}
|
|
6686
|
+
const dial = dialect === "mysql" ? "mysql" : "ansi";
|
|
6687
|
+
const batch = batchSize !== void 0 && Number.isFinite(batchSize) && batchSize > 0 ? Math.floor(batchSize) : 1;
|
|
6688
|
+
const quotedTable = _quoteSqlIdentifierMcp(table, dial);
|
|
6689
|
+
const quotedColumns = headers.map((h) => _quoteSqlIdentifierMcp(h, dial)).join(", ");
|
|
6690
|
+
const statements = [];
|
|
6691
|
+
for (let i = 0; i < rows.length; i += batch) {
|
|
6692
|
+
const chunk = rows.slice(i, i + batch);
|
|
6693
|
+
const tuples = chunk.map((row) => "(" + headers.map((_, idx) => _formatSqlValueMcp(row[idx] ?? "")).join(", ") + ")");
|
|
6694
|
+
statements.push(`INSERT INTO ${quotedTable} (${quotedColumns}) VALUES ${tuples.join(", ")};`);
|
|
6695
|
+
}
|
|
6696
|
+
return {
|
|
6697
|
+
sql: statements.join("\n"),
|
|
6698
|
+
rowCount: rows.length,
|
|
6699
|
+
columnCount: headers.length,
|
|
6700
|
+
columns: headers
|
|
6701
|
+
};
|
|
6702
|
+
}
|
|
6703
|
+
server.tool(
|
|
6704
|
+
"csv_to_sql_insert",
|
|
6705
|
+
"Paste CSV data and get parameterless SQL INSERT statements back, with basic type inference: unquoted for integers/decimals and true/false, NULL for empty cells, single-quoted (with '' escaping) for everything else. Column and table identifiers are quoted per the chosen dialect so reserved words and mixed-case names round-trip safely.",
|
|
6706
|
+
{
|
|
6707
|
+
csv: z.string().describe("CSV data with a header row"),
|
|
6708
|
+
tableName: z.string().optional().describe('Target table name, must be a valid SQL identifier (default "my_table")'),
|
|
6709
|
+
dialect: z.enum(["ansi", "mysql"]).optional().describe('Identifier quoting style: ANSI double quotes or MySQL backticks (default "ansi")'),
|
|
6710
|
+
batchSize: z.number().optional().describe("Rows per INSERT statement (default 1, one statement per row)")
|
|
6711
|
+
},
|
|
6712
|
+
async ({ csv, tableName, dialect, batchSize }) => {
|
|
6713
|
+
const result = _csvToSqlInsertMcp(csv, tableName, dialect, batchSize);
|
|
6714
|
+
if ("error" in result) return errText(result);
|
|
6715
|
+
return text(JSON.stringify(result, null, 2));
|
|
6716
|
+
}
|
|
6717
|
+
);
|
|
6718
|
+
var _SRI_ALGORITHMS_MCP = ["SHA-256", "SHA-384", "SHA-512"];
|
|
6719
|
+
var _SRI_NODE_ALG_MAP_MCP = {
|
|
6720
|
+
"SHA-256": "sha256",
|
|
6721
|
+
"SHA-384": "sha384",
|
|
6722
|
+
"SHA-512": "sha512"
|
|
6723
|
+
};
|
|
6724
|
+
function _generateSriAllMcp(content) {
|
|
6725
|
+
if (!content) return { error: "Content is required" };
|
|
6726
|
+
const results = _SRI_ALGORITHMS_MCP.map((algorithm) => {
|
|
6727
|
+
const nodeAlg = _SRI_NODE_ALG_MAP_MCP[algorithm];
|
|
6728
|
+
const base64 = createHash(nodeAlg).update(content, "utf8").digest("base64");
|
|
6729
|
+
return { algorithm, base64, integrity: `${nodeAlg}-${base64}` };
|
|
6730
|
+
});
|
|
6731
|
+
return { results };
|
|
6732
|
+
}
|
|
6733
|
+
server.tool(
|
|
6734
|
+
"generate_sri_hash",
|
|
6735
|
+
"Compute a Subresource Integrity (SRI) hash for a script or stylesheet's content: digest it with SHA-256, SHA-384, and SHA-512 and return each as the `<algorithm>-<base64>` string an HTML `integrity` attribute expects. Returns all three algorithms at once.",
|
|
6736
|
+
{
|
|
6737
|
+
content: z.string().min(1).describe("The exact file/script content to hash, byte-for-byte")
|
|
6738
|
+
},
|
|
6739
|
+
async ({ content }) => {
|
|
6740
|
+
const result = _generateSriAllMcp(content);
|
|
6741
|
+
if ("error" in result) return errText(result);
|
|
6742
|
+
return text(JSON.stringify(result, null, 2));
|
|
6743
|
+
}
|
|
6744
|
+
);
|
|
6745
|
+
function _analyzeEmailHeadersMcp(raw) {
|
|
6746
|
+
if (!raw || !raw.trim()) return { error: "Raw email header text is required" };
|
|
6747
|
+
const normalized = raw.replace(/\r\n/g, "\n");
|
|
6748
|
+
const blockEnd = normalized.indexOf("\n\n");
|
|
6749
|
+
const headerBlock = blockEnd === -1 ? normalized : normalized.slice(0, blockEnd);
|
|
6750
|
+
const rawLines = headerBlock.split("\n");
|
|
6751
|
+
const unfolded = [];
|
|
6752
|
+
for (const line of rawLines) {
|
|
6753
|
+
if (/^[ \t]/.test(line) && unfolded.length > 0) {
|
|
6754
|
+
unfolded[unfolded.length - 1] += " " + line.trim();
|
|
6755
|
+
} else if (line.trim() !== "") {
|
|
6756
|
+
unfolded.push(line);
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
const headers = [];
|
|
6760
|
+
for (const line of unfolded) {
|
|
6761
|
+
const colonIdx = line.indexOf(":");
|
|
6762
|
+
if (colonIdx === -1) continue;
|
|
6763
|
+
const name = line.slice(0, colonIdx).trim();
|
|
6764
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
6765
|
+
if (!name) continue;
|
|
6766
|
+
headers.push({ name, value });
|
|
6767
|
+
}
|
|
6768
|
+
if (headers.length === 0) {
|
|
6769
|
+
return { error: 'No parseable headers found \u2014 expected lines like "From: ..." or "Received: ..."' };
|
|
6770
|
+
}
|
|
6771
|
+
const findFirst = (name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? null;
|
|
6772
|
+
const findAll = (name) => headers.filter((h) => h.name.toLowerCase() === name.toLowerCase());
|
|
6773
|
+
const receivedHops = findAll("received").map((h, i) => {
|
|
6774
|
+
const fromMatch = h.value.match(/from\s+([^\s;]+(?:\s*\([^)]*\))?)/i);
|
|
6775
|
+
const byMatch = h.value.match(/\bby\s+([^\s;]+)/i);
|
|
6776
|
+
const segments = h.value.split(";");
|
|
6777
|
+
const date = segments.length > 1 ? segments[segments.length - 1].trim() : null;
|
|
6778
|
+
return {
|
|
6779
|
+
index: i + 1,
|
|
6780
|
+
raw: h.value,
|
|
6781
|
+
from: fromMatch ? fromMatch[1].trim() : null,
|
|
6782
|
+
by: byMatch ? byMatch[1].trim() : null,
|
|
6783
|
+
date: date || null
|
|
6784
|
+
};
|
|
6785
|
+
});
|
|
6786
|
+
const authResultsHeader = findFirst("authentication-results");
|
|
6787
|
+
const authResults = [];
|
|
6788
|
+
if (authResultsHeader) {
|
|
6789
|
+
const re = /\b(spf|dkim|dmarc)=(\w+)/gi;
|
|
6790
|
+
let m;
|
|
6791
|
+
while ((m = re.exec(authResultsHeader)) !== null) {
|
|
6792
|
+
authResults.push({ mechanism: m[1].toLowerCase(), result: m[2].toLowerCase() });
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
const findAuth = (mechanism) => authResults.find((a) => a.mechanism === mechanism)?.result ?? null;
|
|
6796
|
+
const warnings = [];
|
|
6797
|
+
if (receivedHops.length === 0) {
|
|
6798
|
+
warnings.push("No Received headers found \u2014 this may be a partial header block rather than a full raw source.");
|
|
6799
|
+
}
|
|
6800
|
+
if (!authResultsHeader) {
|
|
6801
|
+
warnings.push("No Authentication-Results header found \u2014 SPF/DKIM/DMARC cannot be determined from this header block alone.");
|
|
6802
|
+
}
|
|
6803
|
+
return {
|
|
6804
|
+
headers,
|
|
6805
|
+
from: findFirst("from"),
|
|
6806
|
+
to: findFirst("to"),
|
|
6807
|
+
subject: findFirst("subject"),
|
|
6808
|
+
date: findFirst("date"),
|
|
6809
|
+
messageId: findFirst("message-id"),
|
|
6810
|
+
returnPath: findFirst("return-path"),
|
|
6811
|
+
replyTo: findFirst("reply-to"),
|
|
6812
|
+
receivedHops,
|
|
6813
|
+
hopCount: receivedHops.length,
|
|
6814
|
+
authResults,
|
|
6815
|
+
spf: findAuth("spf"),
|
|
6816
|
+
dkim: findAuth("dkim"),
|
|
6817
|
+
dmarc: findAuth("dmarc"),
|
|
6818
|
+
warnings
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
server.tool(
|
|
6822
|
+
"analyze_email_headers",
|
|
6823
|
+
`Parse a raw email header block (e.g. Gmail's "Show original", or the headers from a .eml file) into structured fields, the Received hop chain in the order they appear, and any SPF/DKIM/DMARC verdicts already present in an Authentication-Results header. Never performs a live DNS/SPF/DKIM check, it only reports what the pasted headers already claim.`,
|
|
6824
|
+
{
|
|
6825
|
+
headers: z.string().min(1).describe("The raw email header block (From/To/Received/Authentication-Results/...)")
|
|
6826
|
+
},
|
|
6827
|
+
async ({ headers }) => {
|
|
6828
|
+
const result = _analyzeEmailHeadersMcp(headers);
|
|
6829
|
+
if ("error" in result) return errText(result);
|
|
6830
|
+
return text(JSON.stringify(result, null, 2));
|
|
6831
|
+
}
|
|
6832
|
+
);
|
|
6599
6833
|
var transport = new StdioServerTransport();
|
|
6600
6834
|
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.40.0",
|
|
4
4
|
"description": "MCP server exposing 152 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",
|