@utilix-tech/mcp 0.66.0 → 0.67.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,9 @@
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.67.0
6
+ - Add `analyze_regex_groups` tool: parse a regex pattern's capture groups (numbered and named) and backreferences, flagging any backreference pointing at a group that doesn't exist or hasn't finished being defined yet at that point in the pattern.
7
+
5
8
  ## 0.66.0
6
9
  - Add `build_x_robots_tag`, `parse_x_robots_tag` tools: build an X-Robots-Tag response header from flag directives (noindex, nofollow, etc.) plus max-snippet/max-image-preview/max-video-preview/unavailable_after value directives, optionally scoped to a crawler user-agent, or parse an existing header into the same shape.
7
10
  - Add `lookup_websocket_opcode`, `list_websocket_opcodes` tools: look up what a WebSocket frame opcode means per RFC 6455's 4-bit opcode space, or list every opcode.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 227 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 228 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
 
@@ -276,6 +276,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
276
276
  | `lookup_mime` | Look up the MIME type for a file extension, or the extension(s) for a MIME type |
277
277
  | `parse_github_url` | Parse a github.com URL (repo, pull request, issue, commit, file, or release) into structured components |
278
278
  | `detect_redos` | Statically flag catastrophic-backtracking (ReDoS) risk in a regex: nested quantifiers, overlapping alternation, adjacent overlapping quantifiers |
279
+ | `analyze_regex_groups` | Parse a regex's capture groups (numbered and named) and backreferences, flagging a backreference pointing at a group that doesn't exist or hasn't finished being defined yet |
279
280
  | `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 |
280
281
  | `test_gitignore_patterns` | Test a list of paths against a .gitignore file's patterns, following git's last-match-wins and negation rules |
281
282
  | `validate_editorconfig` | Validate an .editorconfig file: root flag, glob sections, and structural mistakes against the EditorConfig spec |
package/dist/index.js CHANGED
@@ -4318,6 +4318,123 @@ server.tool(
4318
4318
  return text(JSON.stringify(result, null, 2));
4319
4319
  }
4320
4320
  );
4321
+ function _analyzeRegexGroupsMcp(pattern) {
4322
+ let valid = true;
4323
+ let compileError;
4324
+ try {
4325
+ new RegExp(pattern);
4326
+ } catch (e) {
4327
+ valid = false;
4328
+ compileError = e instanceof Error ? e.message : String(e);
4329
+ }
4330
+ const groups = [];
4331
+ const openStack = [];
4332
+ const backreferences = [];
4333
+ const issues = [];
4334
+ let groupCount = 0;
4335
+ let inClass = false;
4336
+ for (let i = 0; i < pattern.length; i++) {
4337
+ const ch = pattern[i];
4338
+ if (ch === "\\" && i + 1 < pattern.length) {
4339
+ const next = pattern[i + 1];
4340
+ if (!inClass && /[1-9]/.test(next)) {
4341
+ let j = i + 1;
4342
+ while (j < pattern.length && /[0-9]/.test(pattern[j])) j++;
4343
+ const raw = pattern.slice(i, j);
4344
+ const target = parseInt(pattern.slice(i + 1, j), 10);
4345
+ backreferences.push({ raw, target, start: i, resolved: false, definedBeforeUse: false });
4346
+ i = j - 1;
4347
+ continue;
4348
+ }
4349
+ if (!inClass && next === "k" && pattern[i + 2] === "<") {
4350
+ const close = pattern.indexOf(">", i + 3);
4351
+ if (close !== -1) {
4352
+ const name = pattern.slice(i + 3, close);
4353
+ const raw = pattern.slice(i, close + 1);
4354
+ backreferences.push({ raw, target: name, start: i, resolved: false, definedBeforeUse: false });
4355
+ i = close;
4356
+ continue;
4357
+ }
4358
+ }
4359
+ i++;
4360
+ continue;
4361
+ }
4362
+ if (ch === "[" && !inClass) {
4363
+ inClass = true;
4364
+ continue;
4365
+ }
4366
+ if (ch === "]" && inClass) {
4367
+ inClass = false;
4368
+ continue;
4369
+ }
4370
+ if (inClass) continue;
4371
+ if (ch === "(") {
4372
+ if (pattern[i + 1] === "?") {
4373
+ const c2 = pattern[i + 2];
4374
+ if (c2 === ":" || c2 === "=" || c2 === "!") {
4375
+ openStack.push({ index: -1, name: null, start: i });
4376
+ continue;
4377
+ }
4378
+ if (c2 === "<" && (pattern[i + 3] === "=" || pattern[i + 3] === "!")) {
4379
+ openStack.push({ index: -1, name: null, start: i });
4380
+ continue;
4381
+ }
4382
+ if (c2 === "<") {
4383
+ const close = pattern.indexOf(">", i + 3);
4384
+ const name = close !== -1 ? pattern.slice(i + 3, close) : null;
4385
+ groupCount++;
4386
+ openStack.push({ index: groupCount, name, start: i });
4387
+ if (close !== -1) i = close;
4388
+ continue;
4389
+ }
4390
+ openStack.push({ index: -1, name: null, start: i });
4391
+ continue;
4392
+ }
4393
+ groupCount++;
4394
+ openStack.push({ index: groupCount, name: null, start: i });
4395
+ continue;
4396
+ }
4397
+ if (ch === ")") {
4398
+ const open = openStack.pop();
4399
+ if (open && open.index !== -1) {
4400
+ groups.push({ index: open.index, name: open.name, start: open.start, end: i });
4401
+ }
4402
+ continue;
4403
+ }
4404
+ }
4405
+ for (const open of openStack) {
4406
+ if (open.index !== -1) groups.push({ index: open.index, name: open.name, start: open.start, end: -1 });
4407
+ }
4408
+ groups.sort((a, b) => a.index - b.index);
4409
+ const byIndex = new Map(groups.map((g) => [g.index, g]));
4410
+ const byName = new Map(groups.filter((g) => g.name !== null).map((g) => [g.name, g]));
4411
+ for (const b of backreferences) {
4412
+ const g = typeof b.target === "number" ? byIndex.get(b.target) : byName.get(b.target);
4413
+ if (!g) {
4414
+ b.resolved = false;
4415
+ b.definedBeforeUse = false;
4416
+ const label = typeof b.target === "number" ? `group ${b.target}` : `named group "${b.target}"`;
4417
+ issues.push({ severity: "error", message: `Backreference ${b.raw} at position ${b.start} points to ${label}, which doesn't exist in this pattern.` });
4418
+ } else {
4419
+ b.resolved = true;
4420
+ b.definedBeforeUse = g.end !== -1 && g.end < b.start;
4421
+ if (!b.definedBeforeUse) {
4422
+ const label = `group ${g.index}${g.name ? ` ("${g.name}")` : ""}`;
4423
+ issues.push({ severity: "error", message: `Backreference ${b.raw} at position ${b.start} references ${label}, which hasn't finished being defined yet at that point in the pattern.` });
4424
+ }
4425
+ }
4426
+ }
4427
+ return { pattern, valid, ...compileError ? { compileError } : {}, groups, backreferences, issues };
4428
+ }
4429
+ server.tool(
4430
+ "analyze_regex_groups",
4431
+ "Parse a regex pattern's capture groups (numbered and named) and backreferences, and flag any backreference pointing at a group that doesn't exist or hasn't finished being defined yet at that point in the pattern (a forward reference or self-reference). Structural, non-AI check. Pass the pattern body only, no delimiters or flags.",
4432
+ { pattern: z.string().describe("The regex pattern body to analyze, e.g. (?<year>\\d{4})-(?<month>\\d{2})\\k<year>") },
4433
+ async ({ pattern }) => {
4434
+ const result = _analyzeRegexGroupsMcp(pattern);
4435
+ return text(JSON.stringify(result, null, 2));
4436
+ }
4437
+ );
4321
4438
  var _CC_NUM_FIELDS_MCP = ["maxAge", "sMaxAge", "staleWhileRevalidate", "staleIfError"];
4322
4439
  function _humanizeCacheSecondsMcp(s) {
4323
4440
  if (s === 0) return "0 seconds";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "MCP server exposing 198 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",