@utilix-tech/mcp 0.96.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 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.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
+
5
9
  ## 0.96.0
6
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.
7
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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 285 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
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 (18 tools)
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 (52 tools)
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 |
@@ -224,6 +225,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
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 |
226
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 |
227
229
 
228
230
  ### API / Network (55 tools)
229
231
  | Tool | Description |
package/dist/index.js CHANGED
@@ -15407,5 +15407,115 @@ server.tool(
15407
15407
  return text(JSON.stringify(result, null, 2));
15408
15408
  }
15409
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
+ );
15410
15520
  var transport = new StdioServerTransport();
15411
15521
  await server.connect(transport);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.96.0",
4
- "description": "MCP server exposing 285 Utilix developer tools to Claude, Cursor, VS Code Copilot, and any MCP-compatible AI assistant.",
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",