@utilix-tech/mcp 0.80.0 → 0.81.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.81.0
6
+ - Add `validate_fence_language_tags` tool: scan a Markdown document's fenced code blocks and flag a missing, unknown, or inconsistently-cased language tag against a curated list of common syntax-highlighter identifiers.
7
+ - Add `count_sql_placeholders` tool: count positional (`?`, `$1`) and named (`:name`, `@name`) parameter placeholders in a SQL query, flagging mixed styles and numbered-sequence gaps or duplicates.
8
+
5
9
  ## 0.80.0
6
10
  - Add `diff_json_patch`, `apply_json_patch` tools: diff two JSON documents into an RFC 6902 JSON Patch operation list (add/remove/replace/move/copy/test), or apply a patch to a target document.
7
11
  - Add `parse_connection_string` tool: parse a database connection URI (postgres, mysql, mongodb, redis, amqp, and similar) into its scheme, credentials, hosts, database, and query parameters.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/mcp
2
2
 
3
- MCP server exposing 255 developer utility tools to Claude, Cursor, VS Code Copilot, and any [Model Context Protocol](https://modelcontextprotocol.io) compatible AI assistant.
3
+ MCP server exposing 257 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
 
@@ -281,7 +281,7 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
281
281
  | `color_palette` | Generate a color palette from a base color |
282
282
  | `color_shades` | Generate shades or tints from a base color |
283
283
 
284
- ### Code (22 tools)
284
+ ### Code (24 tools)
285
285
  | Tool | Description |
286
286
  |------|-------------|
287
287
  | `format_sql` | Format a SQL query with proper indentation |
@@ -306,6 +306,8 @@ claude mcp add utilix -- npx -y @utilix-tech/mcp
306
306
  | `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 |
307
307
  | `validate_aria` | Parse an HTML snippet and check every role and aria-* attribute against a curated table of WAI-ARIA 1.2 roles: unknown role names, an aria-* attribute not permitted on the given role, and a widget role missing its required aria-* state |
308
308
  | `parse_connection_string` | Parse a database connection URI (postgres, mysql, mongodb, redis, amqp, and similar) into its scheme, credentials, hosts, database, and query parameters |
309
+ | `validate_fence_language_tags` | Scan a Markdown document's fenced code blocks and flag a missing, unknown, or inconsistently-cased language tag against a curated list of common syntax-highlighter identifiers |
310
+ | `count_sql_placeholders` | Count positional (`?`, `$1`) and named (`:name`, `@name`) parameter placeholders in a SQL query, flagging mixed styles and numbered-sequence gaps or duplicates |
309
311
 
310
312
  ### Data (16 tools)
311
313
  | Tool | Description |
package/dist/index.js CHANGED
@@ -12744,5 +12744,368 @@ server.tool(
12744
12744
  return text(JSON.stringify(result, null, 2));
12745
12745
  }
12746
12746
  );
12747
+ var _KNOWN_LANGUAGE_TAGS_MCP = /* @__PURE__ */ new Set([
12748
+ "js",
12749
+ "jsx",
12750
+ "mjs",
12751
+ "cjs",
12752
+ "javascript",
12753
+ "ts",
12754
+ "tsx",
12755
+ "typescript",
12756
+ "json",
12757
+ "json5",
12758
+ "jsonc",
12759
+ "jsonl",
12760
+ "ndjson",
12761
+ "yaml",
12762
+ "yml",
12763
+ "toml",
12764
+ "ini",
12765
+ "cfg",
12766
+ "conf",
12767
+ "properties",
12768
+ "env",
12769
+ "dotenv",
12770
+ "html",
12771
+ "htm",
12772
+ "xml",
12773
+ "svg",
12774
+ "xhtml",
12775
+ "css",
12776
+ "scss",
12777
+ "sass",
12778
+ "less",
12779
+ "stylus",
12780
+ "styl",
12781
+ "markdown",
12782
+ "md",
12783
+ "mdx",
12784
+ "bash",
12785
+ "sh",
12786
+ "shell",
12787
+ "zsh",
12788
+ "fish",
12789
+ "console",
12790
+ "powershell",
12791
+ "ps1",
12792
+ "bat",
12793
+ "cmd",
12794
+ "python",
12795
+ "py",
12796
+ "py3",
12797
+ "ipynb",
12798
+ "ruby",
12799
+ "rb",
12800
+ "erb",
12801
+ "php",
12802
+ "java",
12803
+ "kotlin",
12804
+ "kt",
12805
+ "kts",
12806
+ "groovy",
12807
+ "gradle",
12808
+ "swift",
12809
+ "objectivec",
12810
+ "objc",
12811
+ "go",
12812
+ "golang",
12813
+ "rust",
12814
+ "rs",
12815
+ "c",
12816
+ "h",
12817
+ "cpp",
12818
+ "cc",
12819
+ "cxx",
12820
+ "hpp",
12821
+ "c++",
12822
+ "csharp",
12823
+ "cs",
12824
+ "fsharp",
12825
+ "fs",
12826
+ "perl",
12827
+ "pl",
12828
+ "lua",
12829
+ "r",
12830
+ "matlab",
12831
+ "m",
12832
+ "scala",
12833
+ "nim",
12834
+ "zig",
12835
+ "julia",
12836
+ "jl",
12837
+ "haskell",
12838
+ "hs",
12839
+ "elixir",
12840
+ "ex",
12841
+ "exs",
12842
+ "erlang",
12843
+ "erl",
12844
+ "clojure",
12845
+ "clj",
12846
+ "cljs",
12847
+ "dart",
12848
+ "ocaml",
12849
+ "ml",
12850
+ "sql",
12851
+ "mysql",
12852
+ "postgresql",
12853
+ "plsql",
12854
+ "sqlite",
12855
+ "graphql",
12856
+ "gql",
12857
+ "dockerfile",
12858
+ "docker",
12859
+ "makefile",
12860
+ "make",
12861
+ "cmake",
12862
+ "nginx",
12863
+ "apache",
12864
+ "diff",
12865
+ "patch",
12866
+ "git",
12867
+ "http",
12868
+ "proto",
12869
+ "protobuf",
12870
+ "csv",
12871
+ "tsv",
12872
+ "vim",
12873
+ "viml",
12874
+ "tex",
12875
+ "latex",
12876
+ "bibtex",
12877
+ "plaintext",
12878
+ "text",
12879
+ "txt",
12880
+ "nohighlight",
12881
+ "none",
12882
+ "asciidoc",
12883
+ "adoc",
12884
+ "rst",
12885
+ "vue",
12886
+ "svelte",
12887
+ "astro",
12888
+ "wasm",
12889
+ "wat",
12890
+ "handlebars",
12891
+ "hbs",
12892
+ "pug",
12893
+ "jade",
12894
+ "coffee",
12895
+ "coffeescript",
12896
+ "actionscript",
12897
+ "terraform",
12898
+ "tf",
12899
+ "hcl",
12900
+ "puppet",
12901
+ "ansible"
12902
+ ]);
12903
+ function _parseFenceLineMcp(line) {
12904
+ const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);
12905
+ if (!match) return null;
12906
+ const [, , marker, rest] = match;
12907
+ const char = marker[0];
12908
+ if (char === "`" && rest.includes("`")) return null;
12909
+ return { char, length: marker.length, infoString: rest.trim() };
12910
+ }
12911
+ function _isClosingFenceMcp(line, fenceChar, fenceLength) {
12912
+ const match = line.match(/^ {0,3}(`+|~+)\s*$/);
12913
+ if (!match) return false;
12914
+ const marker = match[1];
12915
+ return marker[0] === fenceChar && marker.length >= fenceLength;
12916
+ }
12917
+ function _validateFenceLanguageTagsMcp(markdown) {
12918
+ const lines = markdown.split("\n");
12919
+ const issues = [];
12920
+ let totalFences = 0;
12921
+ let open = null;
12922
+ for (let i = 0; i < lines.length; i++) {
12923
+ const lineNumber = i + 1;
12924
+ const line = lines[i];
12925
+ if (open) {
12926
+ if (_isClosingFenceMcp(line, open.char, open.length)) open = null;
12927
+ continue;
12928
+ }
12929
+ const fence = _parseFenceLineMcp(line);
12930
+ if (!fence) continue;
12931
+ totalFences++;
12932
+ const tag = fence.infoString.split(/\s+/)[0] ?? "";
12933
+ open = { char: fence.char, length: fence.length };
12934
+ if (tag === "") {
12935
+ issues.push({ line: lineNumber, tag, issue: "missing" });
12936
+ continue;
12937
+ }
12938
+ const lower = tag.toLowerCase();
12939
+ if (!_KNOWN_LANGUAGE_TAGS_MCP.has(lower)) {
12940
+ issues.push({ line: lineNumber, tag, issue: "unknown" });
12941
+ continue;
12942
+ }
12943
+ if (tag !== lower) {
12944
+ issues.push({ line: lineNumber, tag, issue: "inconsistent-case", suggestion: lower });
12945
+ }
12946
+ }
12947
+ return { totalFences, issues };
12948
+ }
12949
+ server.tool(
12950
+ "validate_fence_language_tags",
12951
+ "Scan a Markdown document's fenced code blocks (``` or ~~~) and flag ones with a missing, unknown, or inconsistently-cased language tag, checked against a curated list of common syntax-highlighter language identifiers.",
12952
+ {
12953
+ markdown: z.string().describe("The Markdown document to scan")
12954
+ },
12955
+ async ({ markdown }) => {
12956
+ const result = _validateFenceLanguageTagsMcp(markdown);
12957
+ return text(JSON.stringify(result, null, 2));
12958
+ }
12959
+ );
12960
+ var _SQL_IDENT_START_MCP = /[A-Za-z_]/;
12961
+ var _SQL_IDENT_CHAR_MCP = /[A-Za-z0-9_]/;
12962
+ function _readSqlIdentifierMcp(sql, start) {
12963
+ let end = start;
12964
+ while (end < sql.length && _SQL_IDENT_CHAR_MCP.test(sql[end])) end++;
12965
+ return sql.slice(start, end);
12966
+ }
12967
+ function _countSqlPlaceholdersMcp(sql) {
12968
+ const placeholders = [];
12969
+ let state = "normal";
12970
+ let i = 0;
12971
+ while (i < sql.length) {
12972
+ const ch = sql[i];
12973
+ const next = sql[i + 1];
12974
+ if (state === "single") {
12975
+ if (ch === "'" && next === "'") {
12976
+ i += 2;
12977
+ continue;
12978
+ }
12979
+ if (ch === "'") {
12980
+ state = "normal";
12981
+ i++;
12982
+ continue;
12983
+ }
12984
+ i++;
12985
+ continue;
12986
+ }
12987
+ if (state === "double") {
12988
+ if (ch === '"' && next === '"') {
12989
+ i += 2;
12990
+ continue;
12991
+ }
12992
+ if (ch === '"') {
12993
+ state = "normal";
12994
+ i++;
12995
+ continue;
12996
+ }
12997
+ i++;
12998
+ continue;
12999
+ }
13000
+ if (state === "line-comment") {
13001
+ if (ch === "\n") state = "normal";
13002
+ i++;
13003
+ continue;
13004
+ }
13005
+ if (state === "block-comment") {
13006
+ if (ch === "*" && next === "/") {
13007
+ state = "normal";
13008
+ i += 2;
13009
+ continue;
13010
+ }
13011
+ i++;
13012
+ continue;
13013
+ }
13014
+ if (ch === "'") {
13015
+ state = "single";
13016
+ i++;
13017
+ continue;
13018
+ }
13019
+ if (ch === '"') {
13020
+ state = "double";
13021
+ i++;
13022
+ continue;
13023
+ }
13024
+ if (ch === "-" && next === "-") {
13025
+ state = "line-comment";
13026
+ i += 2;
13027
+ continue;
13028
+ }
13029
+ if (ch === "/" && next === "*") {
13030
+ state = "block-comment";
13031
+ i += 2;
13032
+ continue;
13033
+ }
13034
+ if (ch === "?") {
13035
+ placeholders.push({ style: "anonymous", raw: "?", index: i });
13036
+ i++;
13037
+ continue;
13038
+ }
13039
+ if (ch === "$" && /[0-9]/.test(next ?? "")) {
13040
+ let end = i + 1;
13041
+ while (end < sql.length && /[0-9]/.test(sql[end])) end++;
13042
+ const raw = sql.slice(i, end);
13043
+ placeholders.push({ style: "numbered", raw, index: i, number: parseInt(raw.slice(1), 10) });
13044
+ i = end;
13045
+ continue;
13046
+ }
13047
+ if (ch === ":") {
13048
+ if (next === ":") {
13049
+ i += 2;
13050
+ continue;
13051
+ }
13052
+ if (next && _SQL_IDENT_START_MCP.test(next)) {
13053
+ const name = _readSqlIdentifierMcp(sql, i + 1);
13054
+ placeholders.push({ style: "named-colon", raw: `:${name}`, index: i, name });
13055
+ i += 1 + name.length;
13056
+ continue;
13057
+ }
13058
+ i++;
13059
+ continue;
13060
+ }
13061
+ if (ch === "@") {
13062
+ if (next === "@") {
13063
+ i += 2;
13064
+ continue;
13065
+ }
13066
+ if (next && _SQL_IDENT_START_MCP.test(next)) {
13067
+ const name = _readSqlIdentifierMcp(sql, i + 1);
13068
+ placeholders.push({ style: "named-at", raw: `@${name}`, index: i, name });
13069
+ i += 1 + name.length;
13070
+ continue;
13071
+ }
13072
+ i++;
13073
+ continue;
13074
+ }
13075
+ i++;
13076
+ }
13077
+ const counts = { anonymous: 0, numbered: 0, "named-colon": 0, "named-at": 0 };
13078
+ for (const p of placeholders) counts[p.style]++;
13079
+ const stylesUsed = ["anonymous", "numbered", "named-colon", "named-at"].filter((s) => counts[s] > 0);
13080
+ const numbers = placeholders.filter((p) => p.style === "numbered").map((p) => p.number);
13081
+ const maxNumber = numbers.length > 0 ? Math.max(...numbers) : 0;
13082
+ const seen = new Set(numbers);
13083
+ const numberedGaps = [];
13084
+ for (let n = 1; n < maxNumber; n++) {
13085
+ if (!seen.has(n)) numberedGaps.push(n);
13086
+ }
13087
+ const countedMap = /* @__PURE__ */ new Map();
13088
+ for (const n of numbers) countedMap.set(n, (countedMap.get(n) ?? 0) + 1);
13089
+ const numberedDuplicates = [...countedMap.entries()].filter(([, c]) => c > 1).map(([n]) => n).sort((a, b) => a - b);
13090
+ return {
13091
+ placeholders,
13092
+ counts,
13093
+ stylesUsed,
13094
+ mixedStyles: stylesUsed.length > 1,
13095
+ numberedGaps,
13096
+ numberedDuplicates
13097
+ };
13098
+ }
13099
+ server.tool(
13100
+ "count_sql_placeholders",
13101
+ "Count and list positional (?, $1) and named (:name, @name) parameter placeholders in a SQL query string, flagging a mix of placeholder styles in the same query and, for numbered placeholders, a gap or duplicate in the sequence. String literals and comments are skipped.",
13102
+ {
13103
+ sql: z.string().describe("The SQL query string to scan")
13104
+ },
13105
+ async ({ sql }) => {
13106
+ const result = _countSqlPlaceholdersMcp(sql);
13107
+ return text(JSON.stringify(result, null, 2));
13108
+ }
13109
+ );
12747
13110
  var transport = new StdioServerTransport();
12748
13111
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilix-tech/mcp",
3
- "version": "0.80.0",
3
+ "version": "0.81.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",