@utilix-tech/mcp 0.68.0 → 0.69.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.69.0
6
+ - Add `parse_commit_trailers` tool: parse a commit message's trailer block (Co-authored-by, Signed-off-by, Fixes, Reviewed-by, etc.) per git's RFC 822-style trailer convention, returning each trailer's key and value plus the body with the trailer block removed.
7
+ - Add `extract_markdown_footnotes` tool: extract every Markdown-Extra-style footnote reference and definition from a document, flagging references with no matching definition and definitions that are never referenced.
8
+
5
9
  ## 0.68.0
6
10
  - Add `build_robots_meta_tag`, `parse_robots_meta_tag` tools: build a `<meta name="robots" content="...">` tag from the same directive vocabulary as X-Robots-Tag, optionally scoped to a crawler via the name attribute, or parse an existing tag into the same shape.
7
11
  - Add `extract_svg_sprite_symbols` tool: parse an SVG sprite sheet's `<symbol>` definitions and list each one's id, viewBox, width, and height, alongside a ready-to-paste `<use href="#id"></use>` usage snippet.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 231 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 233 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
 
@@ -265,7 +265,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
265
265
  | `color_palette` | Generate a color palette from a base color |
266
266
  | `color_shades` | Generate shades or tints from a base color |
267
267
 
268
- ### Code (18 tools)
268
+ ### Code (20 tools)
269
269
  | Tool | Description |
270
270
  |------|-------------|
271
271
  | `format_sql` | Format a SQL query with proper indentation |
@@ -286,6 +286,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
286
286
  | `validate_package_exports` | Validate a package.json "exports" field: dual-package (ESM/CJS) misconfigurations like a missing default fallback or a require/import pointing at the wrong extension |
287
287
  | `audit_dependency_ranges` | Classify each package.json dependency's version range type and flag wildcard ranges, the "latest" tag, git/URL/file/link dependencies, and packages listed with conflicting or redundant ranges across sections |
288
288
  | `extract_svg_sprite_symbols` | Parse an SVG sprite sheet's `<symbol>` definitions and list each one's id, viewBox, width, and height, alongside a ready-to-paste `<use href="#id"></use>` usage snippet |
289
+ | `parse_commit_trailers` | Parse a commit message's trailer block (Co-authored-by, Signed-off-by, Fixes, Reviewed-by, etc.) per git's RFC 822-style trailer convention, returning each trailer's key/value plus the body with the trailer block removed |
290
+ | `extract_markdown_footnotes` | Extract every Markdown-Extra-style footnote reference and definition from a document, flagging references with no matching definition and definitions that are never referenced |
289
291
 
290
292
  ### Data (16 tools)
291
293
  | Tool | Description |
package/dist/index.js CHANGED
@@ -11010,5 +11010,115 @@ server.tool(
11010
11010
  return text(JSON.stringify(result, null, 2));
11011
11011
  }
11012
11012
  );
11013
+ var _TRAILER_LINE_REGEX_MCP = /^([A-Za-z][A-Za-z0-9-]*): (.+)$/;
11014
+ var _TRAILER_CONTINUATION_REGEX_MCP = /^[ \t]+\S/;
11015
+ function _parseCommitTrailersMcp(message) {
11016
+ const lines = message.replace(/\r\n/g, "\n").split("\n");
11017
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
11018
+ if (lines.length === 0) return { error: "Empty input" };
11019
+ let start = 0;
11020
+ for (let i = lines.length - 1; i >= 0; i--) {
11021
+ if (lines[i].trim() === "") {
11022
+ start = i + 1;
11023
+ break;
11024
+ }
11025
+ }
11026
+ const paragraph = lines.slice(start);
11027
+ const trailers = [];
11028
+ let valid = true;
11029
+ for (const line of paragraph) {
11030
+ const m = line.match(_TRAILER_LINE_REGEX_MCP);
11031
+ if (m) {
11032
+ trailers.push({ key: m[1], value: m[2].trim() });
11033
+ } else if (_TRAILER_CONTINUATION_REGEX_MCP.test(line) && trailers.length > 0) {
11034
+ trailers[trailers.length - 1].value += " " + line.trim();
11035
+ } else {
11036
+ valid = false;
11037
+ break;
11038
+ }
11039
+ }
11040
+ if (!valid || trailers.length === 0) {
11041
+ return { trailers: [], body: lines.join("\n").trim(), hasTrailers: false };
11042
+ }
11043
+ const body = lines.slice(0, start).join("\n").trim();
11044
+ return { trailers, body, hasTrailers: true };
11045
+ }
11046
+ server.tool(
11047
+ "parse_commit_trailers",
11048
+ `Parse a commit message's trailer block (Co-authored-by, Signed-off-by, Fixes, Reviewed-by, etc.) per git's RFC 822-style trailer convention (git-interpret-trailers): the last paragraph of the message, where every line is either a "Key: value" pair or an indented continuation of the previous trailer's value. Returns the trailers plus the message body with the trailer block removed.`,
11049
+ {
11050
+ message: z.string().min(1).describe("The full commit message")
11051
+ },
11052
+ async ({ message }) => {
11053
+ const result = _parseCommitTrailersMcp(message);
11054
+ return text(JSON.stringify(result, null, 2));
11055
+ }
11056
+ );
11057
+ var _FOOTNOTE_DEFINITION_LINE_REGEX_MCP = /^\[\^([^\]]+)\]:\s?(.*)$/;
11058
+ var _FOOTNOTE_CONTINUATION_REGEX_MCP = /^(?: {4}|\t)/;
11059
+ var _FOOTNOTE_REFERENCE_REGEX_MCP = /\[\^([^\]]+)\]/g;
11060
+ function _extractMarkdownFootnotesMcp(markdown) {
11061
+ const trimmed = markdown.trim();
11062
+ if (!trimmed) return { error: "Empty input" };
11063
+ if (!trimmed.includes("[^")) return { error: "No footnote references or definitions found" };
11064
+ const lines = markdown.replace(/\r\n/g, "\n").split("\n");
11065
+ const definitions = [];
11066
+ const definedLabels = /* @__PURE__ */ new Set();
11067
+ const consumed = new Array(lines.length).fill(false);
11068
+ let i = 0;
11069
+ while (i < lines.length) {
11070
+ const match = lines[i].match(_FOOTNOTE_DEFINITION_LINE_REGEX_MCP);
11071
+ if (!match) {
11072
+ i++;
11073
+ continue;
11074
+ }
11075
+ const label = match[1];
11076
+ const textLines = [match[2]];
11077
+ consumed[i] = true;
11078
+ let j = i + 1;
11079
+ while (j < lines.length && (_FOOTNOTE_CONTINUATION_REGEX_MCP.test(lines[j]) || lines[j].trim() === "" && j + 1 < lines.length && _FOOTNOTE_CONTINUATION_REGEX_MCP.test(lines[j + 1]))) {
11080
+ textLines.push(lines[j].trim() === "" ? "" : lines[j].replace(_FOOTNOTE_CONTINUATION_REGEX_MCP, ""));
11081
+ consumed[j] = true;
11082
+ j++;
11083
+ }
11084
+ if (!definedLabels.has(label)) {
11085
+ definedLabels.add(label);
11086
+ definitions.push({ label, text: textLines.join("\n").trim() });
11087
+ }
11088
+ i = j;
11089
+ }
11090
+ const referenceScanText = lines.filter((_, idx) => !consumed[idx]).join("\n");
11091
+ const refOrder = [];
11092
+ const refCounts = /* @__PURE__ */ new Map();
11093
+ for (const m of referenceScanText.matchAll(_FOOTNOTE_REFERENCE_REGEX_MCP)) {
11094
+ const label = m[1];
11095
+ if (!refCounts.has(label)) {
11096
+ refCounts.set(label, 0);
11097
+ refOrder.push(label);
11098
+ }
11099
+ refCounts.set(label, (refCounts.get(label) ?? 0) + 1);
11100
+ }
11101
+ const references = refOrder.map((label) => ({ label, count: refCounts.get(label) ?? 0 }));
11102
+ const referencedSet = new Set(refOrder);
11103
+ const unresolvedReferences = refOrder.filter((label) => !definedLabels.has(label));
11104
+ const unusedDefinitions = definitions.filter((d) => !referencedSet.has(d.label)).map((d) => d.label);
11105
+ const referenceCount = refOrder.reduce((sum, label) => sum + (refCounts.get(label) ?? 0), 0);
11106
+ if (definitions.length === 0 && references.length === 0) {
11107
+ return { error: "No footnote references or definitions found" };
11108
+ }
11109
+ return { definitionCount: definitions.length, referenceCount, definitions, references, unresolvedReferences, unusedDefinitions };
11110
+ }
11111
+ server.tool(
11112
+ "extract_markdown_footnotes",
11113
+ "Extract every Markdown-Extra-style footnote reference ([^label]) and definition ([^label]: text, with 4-space/tab-indented continuation lines) from a document, then flag references with no matching definition and definitions that are never referenced.",
11114
+ {
11115
+ markdown: z.string().min(1).describe("The Markdown document text")
11116
+ },
11117
+ async ({ markdown }) => {
11118
+ const result = _extractMarkdownFootnotesMcp(markdown);
11119
+ if ("error" in result) return errText(result);
11120
+ return text(JSON.stringify(result, null, 2));
11121
+ }
11122
+ );
11013
11123
  var transport = new StdioServerTransport();
11014
11124
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.68.0",
3
+ "version": "0.69.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",