@utilix-tech/mcp 0.95.0 → 0.97.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 +8 -4
- package/dist/index.js +263 -0
- package/package.json +2 -2
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.97.0
|
|
6
|
+
- Add `calculate_position_size` tool: 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.
|
|
7
|
+
- Add `detect_weasel_words` tool: scan text for weasel words, vague hedging phrases that imply evidence or consensus without naming a checkable source, like "studies show" or "many experts believe".
|
|
8
|
+
|
|
9
|
+
## 0.96.0
|
|
10
|
+
- Add `validate_routing_number` tool: validate a 9-digit US bank routing number via its weighted mod-10 checksum, identifying the routing number's type and Federal Reserve district from its first two digits.
|
|
11
|
+
- Add `audit_markdown_image_alt_text` tool: scan a Markdown document's inline images and flag alt text accessibility smells, missing/empty alt text, generic placeholder words, a redundant "image of" prefix, and alt text that's just the image's own filename.
|
|
12
|
+
|
|
5
13
|
## 0.95.0
|
|
6
14
|
- Add `read_png_chunks` tool: walk a PNG's chunk stream directly from its bytes to report every chunk's type, offset, size, and CRC-32 validity, plus the decoded IHDR header and common ancillary chunks (tEXt, pHYs, gAMA, tIME, acTL).
|
|
7
15
|
- Add `estimate_audio_file_size` tool: estimate an audio file's size before recording or exporting it, uncompressed PCM size from sample rate/bit depth/channels or compressed size from a target bitrate.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @utilix-tech/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing
|
|
3
|
+
MCP server exposing 287 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
|
|
|
@@ -134,7 +134,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
134
134
|
| `generate_random_data` | Generate random mock data: names, emails, UUIDs, IPs, etc. |
|
|
135
135
|
| `generate_csp_nonce` | Generate a cryptographically random CSP nonce with ready-to-paste header directive and script/style tag snippets |
|
|
136
136
|
|
|
137
|
-
### Text (
|
|
137
|
+
### Text (19 tools)
|
|
138
138
|
| Tool | Description |
|
|
139
139
|
|------|-------------|
|
|
140
140
|
| `convert_case` | Convert text case: camelCase, snake_case, PascalCase, etc. |
|
|
@@ -157,6 +157,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
157
157
|
| `analyze_keyword_density` | Count keyword/phrase frequency and density (occurrences per 100 words) across a block of text, with single-word or 2/3-word phrase modes |
|
|
158
158
|
| `check_dash_usage` | Scan text for likely dash misuse: a hyphen surrounded by spaces, an unconverted double hyphen, and inconsistent spacing around em dashes across the same document |
|
|
159
159
|
| `check_acronym_expansion` | Scan text for acronyms and flag any that are used before being expanded on first mention, or never expanded at all |
|
|
160
|
+
| `detect_weasel_words` | Scan text for weasel words: vague hedging phrases that imply evidence or consensus without naming a checkable source, like "studies show" or "many experts believe" |
|
|
160
161
|
|
|
161
162
|
### Time (6 tools)
|
|
162
163
|
| Tool | Description |
|
|
@@ -169,7 +170,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
169
170
|
| `convert_timezone` | Convert a date/time between timezones |
|
|
170
171
|
| `convert_iso8601_duration` | Parse an ISO 8601 duration into components and total seconds, or format seconds into a duration string |
|
|
171
172
|
|
|
172
|
-
### Units (
|
|
173
|
+
### Units (53 tools)
|
|
173
174
|
| Tool | Description |
|
|
174
175
|
|------|-------------|
|
|
175
176
|
| `convert_bytes` | Convert between B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
|
|
@@ -223,6 +224,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
223
224
|
| `detect_negative_amortization` | Given a loan's balance, annual rate, and a proposed monthly payment, check whether that payment covers the interest accruing each period; if not, report the shortfall and project how the balance grows instead of shrinking |
|
|
224
225
|
| `calculate_sales_commission` | Compute a sales commission from either a flat percent rate applied to the whole sale amount, or a tiered (marginal) rate structure where each tier applies only to the slice of sales within it, plus an optional base salary added to the total payout |
|
|
225
226
|
| `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 |
|
|
227
|
+
| `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 |
|
|
228
|
+
| `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 |
|
|
226
229
|
|
|
227
230
|
### API / Network (55 tools)
|
|
228
231
|
| Tool | Description |
|
|
@@ -328,7 +331,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
328
331
|
| `find_duplicate_lockfile_versions` | Parse an npm, yarn, or pnpm lockfile and report every package installed at more than one resolved version, with a count of install locations per version |
|
|
329
332
|
| `summarize_git_diff_stat` | Parse `git diff --stat` / `git log --stat` output into structured per-file insertion/deletion counts plus overall totals |
|
|
330
333
|
|
|
331
|
-
### Data (
|
|
334
|
+
### Data (22 tools)
|
|
332
335
|
| Tool | Description |
|
|
333
336
|
|------|-------------|
|
|
334
337
|
| `validate_yaml` | Validate YAML syntax |
|
|
@@ -352,6 +355,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
|
|
|
352
355
|
| `validate_markdown_heading_hierarchy` | Scan a Markdown document's ATX headings and flag heading-structure issues: skipped levels, multiple H1s, empty headings, duplicate headings, and a document that doesn't start at H1 |
|
|
353
356
|
| `check_markdown_internal_links` | Scan a Markdown document's ATX headings, compute the GitHub-style anchor slug for each, and check every [text](#slug) internal link against them, flagging links that point at a heading that doesn't exist |
|
|
354
357
|
| `summarize_csv_column_stats` | Parse CSV/TSV data and compute per-column summary statistics: null/unique counts for every column, plus min/max/mean/median/standard deviation for numeric columns or the most common value for categorical ones |
|
|
358
|
+
| `audit_markdown_image_alt_text` | Scan a Markdown document's inline images and flag alt text accessibility smells: missing/empty alt text, generic placeholder words, a redundant "image of"/"photo of" prefix, and alt text that's just the image's own filename |
|
|
355
359
|
|
|
356
360
|
### CSS (2 tools)
|
|
357
361
|
| Tool | Description |
|
package/dist/index.js
CHANGED
|
@@ -15254,5 +15254,268 @@ server.tool(
|
|
|
15254
15254
|
return text(JSON.stringify(result, null, 2));
|
|
15255
15255
|
}
|
|
15256
15256
|
);
|
|
15257
|
+
var _ABA_FEDERAL_RESERVE_BANKS_MCP = {
|
|
15258
|
+
1: "Boston",
|
|
15259
|
+
2: "New York",
|
|
15260
|
+
3: "Philadelphia",
|
|
15261
|
+
4: "Cleveland",
|
|
15262
|
+
5: "Richmond",
|
|
15263
|
+
6: "Atlanta",
|
|
15264
|
+
7: "Chicago",
|
|
15265
|
+
8: "St. Louis",
|
|
15266
|
+
9: "Minneapolis",
|
|
15267
|
+
10: "Kansas City",
|
|
15268
|
+
11: "Dallas",
|
|
15269
|
+
12: "San Francisco"
|
|
15270
|
+
};
|
|
15271
|
+
function _abaDistrictAndTypeMcp(prefix) {
|
|
15272
|
+
if (prefix >= 1 && prefix <= 12) {
|
|
15273
|
+
return { type: "primary", district: { code: prefix, bank: _ABA_FEDERAL_RESERVE_BANKS_MCP[prefix] } };
|
|
15274
|
+
}
|
|
15275
|
+
if (prefix >= 21 && prefix <= 32) {
|
|
15276
|
+
const code = prefix - 20;
|
|
15277
|
+
return { type: "thrift", district: { code, bank: _ABA_FEDERAL_RESERVE_BANKS_MCP[code] } };
|
|
15278
|
+
}
|
|
15279
|
+
if (prefix >= 61 && prefix <= 72) {
|
|
15280
|
+
const code = prefix - 60;
|
|
15281
|
+
return { type: "electronic", district: { code, bank: _ABA_FEDERAL_RESERVE_BANKS_MCP[code] } };
|
|
15282
|
+
}
|
|
15283
|
+
if (prefix === 80) return { type: "traveler-check", district: null };
|
|
15284
|
+
return { type: "unknown", district: null };
|
|
15285
|
+
}
|
|
15286
|
+
function _formatRoutingNumberMcp(digits) {
|
|
15287
|
+
return `${digits.slice(0, 4)} ${digits.slice(4, 8)} ${digits.slice(8)}`;
|
|
15288
|
+
}
|
|
15289
|
+
function _validateRoutingNumberMcp(input) {
|
|
15290
|
+
if (!input || !input.trim()) return { error: "Input is empty" };
|
|
15291
|
+
const digits = input.replace(/[\s-]/g, "");
|
|
15292
|
+
if (!/^\d+$/.test(digits)) return { error: "Routing number must contain only digits, spaces, or hyphens" };
|
|
15293
|
+
if (digits.length !== 9) return { error: `Routing number length (${digits.length}) must be exactly 9 digits` };
|
|
15294
|
+
const d = digits.split("").map(Number);
|
|
15295
|
+
const sum = 3 * (d[0] + d[3] + d[6]) + 7 * (d[1] + d[4] + d[7]) + 1 * (d[2] + d[5] + d[8]);
|
|
15296
|
+
const checksumValid = sum % 10 === 0;
|
|
15297
|
+
const prefix = Number(digits.slice(0, 2));
|
|
15298
|
+
const { type, district } = _abaDistrictAndTypeMcp(prefix);
|
|
15299
|
+
return {
|
|
15300
|
+
valid: checksumValid,
|
|
15301
|
+
checksumValid,
|
|
15302
|
+
length: digits.length,
|
|
15303
|
+
formatted: _formatRoutingNumberMcp(digits),
|
|
15304
|
+
routingNumberType: type,
|
|
15305
|
+
federalReserveDistrict: district
|
|
15306
|
+
};
|
|
15307
|
+
}
|
|
15308
|
+
server.tool(
|
|
15309
|
+
"validate_routing_number",
|
|
15310
|
+
"Validate a 9-digit US bank routing number (ABA/RTN) via the weighted mod-10 checksum from the ABA's routing number specification. Also identifies the routing number's type and Federal Reserve district from its first two digits.",
|
|
15311
|
+
{ routingNumber: z.string().describe("The 9-digit routing number, with optional spaces or hyphens") },
|
|
15312
|
+
async ({ routingNumber }) => {
|
|
15313
|
+
const result = _validateRoutingNumberMcp(routingNumber);
|
|
15314
|
+
if ("error" in result) return errText(result);
|
|
15315
|
+
return text(JSON.stringify(result, null, 2));
|
|
15316
|
+
}
|
|
15317
|
+
);
|
|
15318
|
+
var _MARKDOWN_IMAGE_RE_MCP = /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
15319
|
+
var _MARKDOWN_IMAGE_GENERIC_ALT_WORDS_MCP = /* @__PURE__ */ new Set([
|
|
15320
|
+
"image",
|
|
15321
|
+
"picture",
|
|
15322
|
+
"photo",
|
|
15323
|
+
"photograph",
|
|
15324
|
+
"pic",
|
|
15325
|
+
"img",
|
|
15326
|
+
"graphic",
|
|
15327
|
+
"screenshot",
|
|
15328
|
+
"icon",
|
|
15329
|
+
"logo"
|
|
15330
|
+
]);
|
|
15331
|
+
var _MARKDOWN_IMAGE_REDUNDANT_PREFIX_RE_MCP = /^(image|picture|photo|photograph)\s+of\s+/i;
|
|
15332
|
+
var _MARKDOWN_IMAGE_FILENAME_EXT_RE_MCP = /\.(png|jpe?g|gif|svg|webp|bmp|avif|tiff?)$/i;
|
|
15333
|
+
function _markdownImageBasenameWithoutExtMcp(src) {
|
|
15334
|
+
const withoutQuery = src.split(/[?#]/)[0];
|
|
15335
|
+
const file = withoutQuery.split("/").pop() ?? withoutQuery;
|
|
15336
|
+
return file.replace(_MARKDOWN_IMAGE_FILENAME_EXT_RE_MCP, "");
|
|
15337
|
+
}
|
|
15338
|
+
function _auditMarkdownImageAltTextMcp(markdown) {
|
|
15339
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
15340
|
+
const images = [];
|
|
15341
|
+
const issues = [];
|
|
15342
|
+
let inFence = false;
|
|
15343
|
+
let fenceMarker = "";
|
|
15344
|
+
lines.forEach((line, idx) => {
|
|
15345
|
+
const fenceMatch = line.match(/^\s*(```+|~~~+)/);
|
|
15346
|
+
if (fenceMatch) {
|
|
15347
|
+
if (!inFence) {
|
|
15348
|
+
inFence = true;
|
|
15349
|
+
fenceMarker = fenceMatch[1][0];
|
|
15350
|
+
} else if (fenceMatch[1][0] === fenceMarker) {
|
|
15351
|
+
inFence = false;
|
|
15352
|
+
}
|
|
15353
|
+
return;
|
|
15354
|
+
}
|
|
15355
|
+
if (inFence) return;
|
|
15356
|
+
const lineNo = idx + 1;
|
|
15357
|
+
_MARKDOWN_IMAGE_RE_MCP.lastIndex = 0;
|
|
15358
|
+
let match;
|
|
15359
|
+
while ((match = _MARKDOWN_IMAGE_RE_MCP.exec(line)) !== null) {
|
|
15360
|
+
const alt = match[1].trim();
|
|
15361
|
+
const src = match[2];
|
|
15362
|
+
images.push({ line: lineNo, alt, src });
|
|
15363
|
+
if (alt === "") {
|
|
15364
|
+
issues.push({
|
|
15365
|
+
line: lineNo,
|
|
15366
|
+
type: "empty-alt",
|
|
15367
|
+
src,
|
|
15368
|
+
message: `Image at line ${lineNo} has no alt text (src: ${src}). If this image is purely decorative, an intentionally empty alt is fine; otherwise it needs a description.`
|
|
15369
|
+
});
|
|
15370
|
+
continue;
|
|
15371
|
+
}
|
|
15372
|
+
if (_MARKDOWN_IMAGE_GENERIC_ALT_WORDS_MCP.has(alt.toLowerCase())) {
|
|
15373
|
+
issues.push({
|
|
15374
|
+
line: lineNo,
|
|
15375
|
+
type: "generic-alt",
|
|
15376
|
+
src,
|
|
15377
|
+
message: `Alt text "${alt}" at line ${lineNo} is a generic placeholder and doesn't describe the image's content.`
|
|
15378
|
+
});
|
|
15379
|
+
}
|
|
15380
|
+
if (_MARKDOWN_IMAGE_REDUNDANT_PREFIX_RE_MCP.test(alt)) {
|
|
15381
|
+
issues.push({
|
|
15382
|
+
line: lineNo,
|
|
15383
|
+
type: "redundant-prefix",
|
|
15384
|
+
src,
|
|
15385
|
+
message: `Alt text "${alt}" at line ${lineNo} starts with a redundant prefix; screen readers already announce images, so "${alt.replace(_MARKDOWN_IMAGE_REDUNDANT_PREFIX_RE_MCP, "")}" reads better.`
|
|
15386
|
+
});
|
|
15387
|
+
}
|
|
15388
|
+
const baseName = _markdownImageBasenameWithoutExtMcp(src);
|
|
15389
|
+
if (_MARKDOWN_IMAGE_FILENAME_EXT_RE_MCP.test(alt) || baseName !== "" && alt.toLowerCase() === baseName.toLowerCase()) {
|
|
15390
|
+
issues.push({
|
|
15391
|
+
line: lineNo,
|
|
15392
|
+
type: "filename-as-alt",
|
|
15393
|
+
src,
|
|
15394
|
+
message: `Alt text "${alt}" at line ${lineNo} looks like the image's own filename rather than a description.`
|
|
15395
|
+
});
|
|
15396
|
+
}
|
|
15397
|
+
}
|
|
15398
|
+
});
|
|
15399
|
+
return { images, issues, imageCount: images.length };
|
|
15400
|
+
}
|
|
15401
|
+
server.tool(
|
|
15402
|
+
"audit_markdown_image_alt_text",
|
|
15403
|
+
'Scan a Markdown document\'s inline images (``) and flag alt text accessibility smells: missing/empty alt text, a generic placeholder word used alone ("image", "photo"), alt text that\'s just the image\'s own filename, and a redundant "image of"/"photo of" prefix. Only inline image syntax is scanned; reference-style images and raw HTML `<img>` tags are not detected.',
|
|
15404
|
+
{ markdown: z.string().describe("The Markdown document to scan") },
|
|
15405
|
+
async ({ markdown }) => {
|
|
15406
|
+
const result = _auditMarkdownImageAltTextMcp(markdown);
|
|
15407
|
+
return text(JSON.stringify(result, null, 2));
|
|
15408
|
+
}
|
|
15409
|
+
);
|
|
15410
|
+
function _calculatePositionSizeMcp(input) {
|
|
15411
|
+
const { accountSize, riskPercent, entryPrice, stopLossPrice, targetPrice } = input;
|
|
15412
|
+
if (!(accountSize > 0)) return { error: "accountSize must be greater than 0" };
|
|
15413
|
+
if (!(riskPercent > 0) || riskPercent > 100) return { error: "riskPercent must be greater than 0 and at most 100" };
|
|
15414
|
+
if (!(entryPrice > 0)) return { error: "entryPrice must be greater than 0" };
|
|
15415
|
+
if (!(stopLossPrice > 0)) return { error: "stopLossPrice must be greater than 0" };
|
|
15416
|
+
if (entryPrice === stopLossPrice) return { error: "entryPrice and stopLossPrice must differ" };
|
|
15417
|
+
if (targetPrice !== void 0 && !(targetPrice > 0)) return { error: "targetPrice must be greater than 0" };
|
|
15418
|
+
const direction = stopLossPrice < entryPrice ? "long" : "short";
|
|
15419
|
+
const riskAmount = accountSize * (riskPercent / 100);
|
|
15420
|
+
const perUnitRisk = Math.abs(entryPrice - stopLossPrice);
|
|
15421
|
+
const positionSizeUnits = Math.floor(riskAmount / perUnitRisk);
|
|
15422
|
+
const positionValue = positionSizeUnits * entryPrice;
|
|
15423
|
+
const dollarRisk = positionSizeUnits * perUnitRisk;
|
|
15424
|
+
let riskRewardRatio = null;
|
|
15425
|
+
let targetOnCorrectSide = null;
|
|
15426
|
+
if (targetPrice !== void 0) {
|
|
15427
|
+
const reward = Math.abs(targetPrice - entryPrice);
|
|
15428
|
+
riskRewardRatio = Math.round(reward / perUnitRisk * 100) / 100;
|
|
15429
|
+
targetOnCorrectSide = direction === "long" ? targetPrice > entryPrice : targetPrice < entryPrice;
|
|
15430
|
+
}
|
|
15431
|
+
return {
|
|
15432
|
+
direction,
|
|
15433
|
+
riskAmount: Math.round(riskAmount * 100) / 100,
|
|
15434
|
+
perUnitRisk: Math.round(perUnitRisk * 1e4) / 1e4,
|
|
15435
|
+
positionSizeUnits,
|
|
15436
|
+
positionValue: Math.round(positionValue * 100) / 100,
|
|
15437
|
+
dollarRisk: Math.round(dollarRisk * 100) / 100,
|
|
15438
|
+
riskRewardRatio,
|
|
15439
|
+
targetOnCorrectSide
|
|
15440
|
+
};
|
|
15441
|
+
}
|
|
15442
|
+
server.tool(
|
|
15443
|
+
"calculate_position_size",
|
|
15444
|
+
"Compute a trade's position size from account size, risk percent per trade, entry price, and stop-loss price: the dollar risk divided by the per-unit stop distance, floored to a whole share/unit count. Also infers the trade direction (long/short) and, given an optional target price, the risk/reward ratio and whether the target sits on the correct side of entry.",
|
|
15445
|
+
{
|
|
15446
|
+
accountSize: z.number().describe("Total account size"),
|
|
15447
|
+
riskPercent: z.number().describe("Percent of the account willing to risk on this trade (0-100)"),
|
|
15448
|
+
entryPrice: z.number().describe("The planned entry price"),
|
|
15449
|
+
stopLossPrice: z.number().describe("The stop-loss price; must differ from entryPrice"),
|
|
15450
|
+
targetPrice: z.number().optional().describe("Optional take-profit price, used to compute the risk/reward ratio")
|
|
15451
|
+
},
|
|
15452
|
+
async ({ accountSize, riskPercent, entryPrice, stopLossPrice, targetPrice }) => {
|
|
15453
|
+
const result = _calculatePositionSizeMcp({ accountSize, riskPercent, entryPrice, stopLossPrice, targetPrice });
|
|
15454
|
+
if ("error" in result) return errText(result);
|
|
15455
|
+
return text(JSON.stringify(result, null, 2));
|
|
15456
|
+
}
|
|
15457
|
+
);
|
|
15458
|
+
var _WEASEL_PHRASES_MCP = [
|
|
15459
|
+
"many experts believe",
|
|
15460
|
+
"some experts say",
|
|
15461
|
+
"experts agree",
|
|
15462
|
+
"critics argue",
|
|
15463
|
+
"studies show",
|
|
15464
|
+
"studies have shown",
|
|
15465
|
+
"research shows",
|
|
15466
|
+
"research suggests",
|
|
15467
|
+
"it is widely believed",
|
|
15468
|
+
"it is widely known",
|
|
15469
|
+
"it is widely regarded",
|
|
15470
|
+
"it is generally accepted",
|
|
15471
|
+
"it is often said",
|
|
15472
|
+
"it has been suggested",
|
|
15473
|
+
"it is said that",
|
|
15474
|
+
"some say",
|
|
15475
|
+
"some people believe",
|
|
15476
|
+
"some people think",
|
|
15477
|
+
"many believe",
|
|
15478
|
+
"many people think",
|
|
15479
|
+
"many claim",
|
|
15480
|
+
"observers note",
|
|
15481
|
+
"sources say",
|
|
15482
|
+
"according to some",
|
|
15483
|
+
"widely regarded as",
|
|
15484
|
+
"many consider",
|
|
15485
|
+
"some scientists believe",
|
|
15486
|
+
"some argue",
|
|
15487
|
+
"reportedly",
|
|
15488
|
+
"allegedly"
|
|
15489
|
+
];
|
|
15490
|
+
function _weaselRegexMcp(phrase) {
|
|
15491
|
+
const tokens = phrase.split(/\s+/).map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
15492
|
+
return new RegExp("\\b" + tokens.join("\\s+") + "\\b", "gi");
|
|
15493
|
+
}
|
|
15494
|
+
function _detectWeaselWordsMcp(input) {
|
|
15495
|
+
const totalWords = (input.match(/[A-Za-z0-9']+/g) ?? []).length;
|
|
15496
|
+
const matches = [];
|
|
15497
|
+
for (const phrase of _WEASEL_PHRASES_MCP) {
|
|
15498
|
+
const re = _weaselRegexMcp(phrase);
|
|
15499
|
+
const indices = [];
|
|
15500
|
+
let m;
|
|
15501
|
+
while ((m = re.exec(input)) !== null) {
|
|
15502
|
+
indices.push(m.index);
|
|
15503
|
+
if (m.index === re.lastIndex) re.lastIndex++;
|
|
15504
|
+
}
|
|
15505
|
+
if (indices.length > 0) matches.push({ phrase, count: indices.length, indices });
|
|
15506
|
+
}
|
|
15507
|
+
matches.sort((a, b) => b.count - a.count || a.phrase.localeCompare(b.phrase));
|
|
15508
|
+
const totalMatches = matches.reduce((s, x) => s + x.count, 0);
|
|
15509
|
+
const density = totalWords > 0 ? Math.round(totalMatches / totalWords * 1e3) / 10 : 0;
|
|
15510
|
+
return { totalWords, totalMatches, density, matches };
|
|
15511
|
+
}
|
|
15512
|
+
server.tool(
|
|
15513
|
+
"detect_weasel_words",
|
|
15514
|
+
`Scan text for weasel words: vague hedging phrases that imply evidence or consensus without naming a checkable source ("studies show", "many experts believe", "it is widely known"). Dictionary-based heuristic over a curated phrase list, distinct from detect_filler_words' "hedge" category, which covers personal uncertainty ("maybe", "I think") rather than unattributed third-party authority.`,
|
|
15515
|
+
{ text: z.string().describe("The text to scan for weasel words") },
|
|
15516
|
+
async ({ text: input }) => {
|
|
15517
|
+
return text(JSON.stringify(_detectWeaselWordsMcp(input), null, 2));
|
|
15518
|
+
}
|
|
15519
|
+
);
|
|
15257
15520
|
var transport = new StdioServerTransport();
|
|
15258
15521
|
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.97.0",
|
|
4
|
+
"description": "MCP server exposing 287 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",
|