simplyutils-mcp 1.0.0 → 1.0.2

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/dist/server.js CHANGED
@@ -15,19 +15,23 @@ async function run(slug, work) {
15
15
  }
16
16
  }
17
17
  export function createServer() {
18
- const server = new McpServer({ name: "simplyutils", title: "SimplyUtils", version: "1.0.0", websiteUrl: tools.SITE }, { instructions: "Everyday developer and text utilities from SimplyUtils (simplyutils.com). Results are computed directly, with no AI and no external lookups. Each answer links to the matching free tool on simplyutils.com." });
18
+ const server = new McpServer({ name: "simplyutils", title: "SimplyUtils", version: "1.0.2", websiteUrl: tools.SITE }, { instructions: "Everyday developer and text utilities from SimplyUtils (simplyutils.com). Results are computed directly, with no AI and no external lookups. Each answer links to the matching free tool on simplyutils.com." });
19
19
  server.registerTool("format_json", {
20
20
  title: "Format or minify JSON",
21
- description: "Validate JSON and pretty-print or minify it, optionally sorting object keys. Returns the error position for invalid JSON.",
22
- inputSchema: { json: z.string().describe("The JSON text"), indent: z.enum(["2", "4", "tab", "minify"]).default("2"), sortKeys: z.boolean().default(false) },
21
+ description: "Validate JSON and return it pretty-printed or minified, optionally with object keys sorted A-Z (recursively; array order is kept). Returns the formatted JSON as text. Invalid JSON returns an error naming the problem and its position. Input up to 100 KB.",
22
+ inputSchema: {
23
+ json: z.string().describe("The JSON text to validate and format"),
24
+ indent: z.enum(["2", "4", "tab", "minify"]).default("2").describe("2 or 4 spaces, a tab, or minify to remove all whitespace"),
25
+ sortKeys: z.boolean().default(false).describe("Sort object keys alphabetically at every level"),
26
+ },
23
27
  annotations: readOnly,
24
28
  }, ({ json, indent, sortKeys }) => run("json-formatter", () => tools.formatJson(json, indent, sortKeys)));
25
29
  server.registerTool("generate_qr_code", {
26
30
  title: "Generate a QR code",
27
- description: "Make a static QR code for a link or text (up to 800 bytes). Returns a PNG image and the SVG markup. Static codes never expire.",
31
+ description: "Make a static QR code for a link or any text, up to 800 UTF-8 bytes. Returns a 512 px PNG image plus the SVG markup. The data is stored in the code itself, so it never expires and needs no account. Light-on-dark colors are allowed, but some older scanners need dark on light. Errors are returned for empty text, content over 800 bytes, or colors that are not six-digit hex.",
28
32
  inputSchema: {
29
33
  text: z.string().describe("Link or text to encode"),
30
- errorCorrection: z.enum(["L", "M", "Q", "H"]).default("M"),
34
+ errorCorrection: z.enum(["L", "M", "Q", "H"]).default("M").describe("Error correction: L 7%, M 15%, Q 25%, H 30% of the code can be damaged and still scan; higher is denser"),
31
35
  foreground: z.string().default("#000000").describe("Six-digit hex color for the modules"),
32
36
  background: z.string().default("#ffffff").describe("Six-digit hex color for the background"),
33
37
  },
@@ -46,10 +50,10 @@ export function createServer() {
46
50
  });
47
51
  server.registerTool("convert_units", {
48
52
  title: "Convert units",
49
- description: `Convert a value between units of the same category. Units (symbol or name): ${tools.unitList()}.`,
53
+ description: `Convert a number between two units of the same category and return a line such as "5 mi = 8.04672 km" (up to 10 significant digits). Units are matched by symbol or name, case-insensitive. Errors are returned for unknown units, units from another category, non-numeric values and temperatures below absolute zero. Months are 1/12 of a 365-day year. Units by category (symbols): ${tools.unitList()}.`,
50
54
  inputSchema: {
51
- value: z.string().describe("A plain number, e.g. 12.5"),
52
- category: z.enum(tools.unitCategories),
55
+ value: z.string().describe("A plain number such as 12.5, -40 or 1e6 (no commas or unit text)"),
56
+ category: z.enum(tools.unitCategories).describe("The kind of quantity; both units must belong to it"),
53
57
  from: z.string().describe("Unit symbol or name, e.g. km or kilometer"),
54
58
  to: z.string().describe("Unit symbol or name, e.g. mi or mile"),
55
59
  },
@@ -57,36 +61,50 @@ export function createServer() {
57
61
  }, ({ value, category, from, to }) => run("unit-converter", () => tools.convertUnits(value, category, from, to).text));
58
62
  server.registerTool("generate_password", {
59
63
  title: "Generate strong passwords",
60
- description: "Generate random passwords with a cryptographically secure generator. Each chosen character set appears at least once.",
64
+ description: "Generate random passwords with Node's cryptographically secure generator. Each enabled character set appears at least once and the result is shuffled. Returns one password per line; every call gives new results. An error is returned if all character sets are turned off. Passwords are not stored or logged.",
61
65
  inputSchema: {
62
- length: z.number().int().min(8).max(128).default(16),
63
- count: z.number().int().min(1).max(20).default(1),
64
- uppercase: z.boolean().default(true), lowercase: z.boolean().default(true), numbers: z.boolean().default(true), symbols: z.boolean().default(true),
66
+ length: z.number().int().min(8).max(128).default(16).describe("Characters per password, 8-128"),
67
+ count: z.number().int().min(1).max(20).default(1).describe("How many passwords to generate, 1-20"),
68
+ uppercase: z.boolean().default(true).describe("Include A-Z"),
69
+ lowercase: z.boolean().default(true).describe("Include a-z"),
70
+ numbers: z.boolean().default(true).describe("Include 0-9"),
71
+ symbols: z.boolean().default(true).describe("Include symbols such as !@#$%^&*"),
65
72
  },
66
73
  annotations: { ...readOnly, idempotentHint: false },
67
74
  }, ({ length, count, ...sets }) => run("password-generator", () => tools.generatePasswords(length, count, sets).join("\n")));
68
75
  server.registerTool("generate_uuid", {
69
76
  title: "Generate UUIDs",
70
- description: "Generate random version 4 UUIDs.",
71
- inputSchema: { count: z.number().int().min(1).max(50).default(1) },
77
+ description: "Generate random version 4 UUIDs (RFC 9562) in lowercase 8-4-4-4-12 form, one per line. Every call gives new values.",
78
+ inputSchema: { count: z.number().int().min(1).max(50).default(1).describe("How many UUIDs to generate, 1-50") },
72
79
  annotations: { ...readOnly, idempotentHint: false },
73
80
  }, ({ count }) => run("uuid-generator", () => tools.generateUuids(count).join("\n")));
74
81
  server.registerTool("hash_text", {
75
82
  title: "Hash text",
76
- description: "Hash UTF-8 text with MD5, SHA-1, SHA-256, SHA-384 or SHA-512 and return the hex digest. MD5 and SHA-1 are for checksums only, not security.",
77
- inputSchema: { text: z.string(), algorithm: z.enum(["md5", "sha1", "sha256", "sha384", "sha512"]).default("sha256") },
83
+ description: "Hash text (encoded as UTF-8) and return a line such as 'sha256: ba78...' with the lowercase hex digest. The same input always gives the same hash. MD5 and SHA-1 are only for checksums, not security; do not use plain hashes to store passwords. Input up to 100 KB.",
84
+ inputSchema: {
85
+ text: z.string().describe("The exact text to hash; spaces and line breaks change the result"),
86
+ algorithm: z.enum(["md5", "sha1", "sha256", "sha384", "sha512"]).default("sha256").describe("Hash algorithm; sha256 is the usual choice"),
87
+ },
78
88
  annotations: readOnly,
79
89
  }, ({ text: value, algorithm }) => run("hash-generator", () => `${algorithm}: ${tools.hashText(value, algorithm)}`));
80
90
  server.registerTool("base64", {
81
91
  title: "Base64 encode or decode",
82
- description: "Encode UTF-8 text to Base64 (standard or URL-safe) or decode Base64 back to text.",
83
- inputSchema: { text: z.string(), mode: z.enum(["encode", "decode"]), urlSafe: z.boolean().default(false) },
92
+ description: "Encode text (as UTF-8) to Base64, or decode Base64 back to text, and return the result. Decoding accepts standard and URL-safe alphabets and ignores whitespace; invalid Base64 returns an error. Bytes that are not valid UTF-8 are shown as replacement characters. Input up to 100 KB.",
93
+ inputSchema: {
94
+ text: z.string().describe("Text to encode, or Base64 to decode"),
95
+ mode: z.enum(["encode", "decode"]).describe("encode: text to Base64; decode: Base64 to text"),
96
+ urlSafe: z.boolean().default(false).describe("When encoding, use - and _ and drop = padding (for URLs and JWTs)"),
97
+ },
84
98
  annotations: readOnly,
85
99
  }, ({ text: value, mode, urlSafe }) => run("base64", () => tools.base64(value, mode, urlSafe)));
86
100
  server.registerTool("test_regex", {
87
101
  title: "Test a regular expression",
88
- description: "Run a JavaScript regular expression against text and list every match with its position and capture groups (up to 200 matches, 0.5 s limit).",
89
- inputSchema: { pattern: z.string().describe("Pattern without slashes"), flags: z.string().default("g").describe("JavaScript flags such as gi"), text: z.string() },
102
+ description: "Run a JavaScript (ECMAScript) regular expression against text. Returns the match count and, for each match, the matched text, its 0-based index, numbered capture groups and named groups. Returns 'No matches.' when nothing matches. All matches are listed (the g flag is added automatically), up to 200. Invalid patterns or flags return an error explaining why; patterns that run longer than 0.5 seconds are stopped with an error. Text up to 100 KB.",
103
+ inputSchema: {
104
+ pattern: z.string().describe("The pattern without surrounding slashes, for example (\\d+)-(?<unit>[a-z]+)"),
105
+ flags: z.string().default("g").describe("Any of d, g, i, m, s, u, v, y; for example gi for case-insensitive"),
106
+ text: z.string().describe("The text to search"),
107
+ },
90
108
  annotations: readOnly,
91
109
  }, ({ pattern, flags, text: value }) => run("regex-tester", () => {
92
110
  const matches = tools.testRegex(pattern, flags, value);
@@ -96,7 +114,7 @@ export function createServer() {
96
114
  }));
97
115
  server.registerTool("convert_timestamp", {
98
116
  title: "Convert a Unix timestamp or date",
99
- description: "Convert a Unix timestamp (seconds or milliseconds) or a date string to Unix seconds, milliseconds, ISO 8601, UTC and a chosen time zone.",
117
+ description: "Convert a Unix timestamp or a date to Unix seconds, Unix milliseconds, ISO 8601 (UTC), an HTTP-style UTC string and a readable date in the chosen time zone. Numbers with 13 or more digits are read as milliseconds, shorter ones as seconds. Dates without a time zone are read as UTC. Errors are returned for unreadable dates and unknown time zones.",
100
118
  inputSchema: { value: z.string().describe("e.g. 1767225600 or 2026-10-27T09:30:00Z"), timeZone: z.string().default("UTC").describe("IANA time zone, e.g. Asia/Kolkata") },
101
119
  annotations: readOnly,
102
120
  }, ({ value, timeZone }) => run("timestamp-converter", () => {
@@ -105,8 +123,8 @@ export function createServer() {
105
123
  }));
106
124
  server.registerTool("count_words", {
107
125
  title: "Count words and characters",
108
- description: "Count words, characters, sentences and paragraphs in text, with an estimated reading time.",
109
- inputSchema: { text: z.string() },
126
+ description: "Count words, characters (with and without spaces), sentences and paragraphs, and estimate reading time at 238 words per minute. Words are runs of letters or digits in any language; hyphenated words and contractions count once. Paragraphs are separated by blank lines. Input up to 100 KB.",
127
+ inputSchema: { text: z.string().describe("The text to analyze") },
110
128
  annotations: readOnly,
111
129
  }, ({ text: value }) => run("word-counter", () => {
112
130
  const r = tools.countWords(value);
package/dist/tools.js CHANGED
@@ -147,7 +147,9 @@ export function convertTimestamp(value, timeZone = "UTC") {
147
147
  date = new Date(Math.abs(number) >= 1e12 ? number : number * 1000);
148
148
  }
149
149
  else {
150
- date = new Date(text);
150
+ // An ISO date-time without a zone would otherwise use the server's local zone; read it as UTC.
151
+ const isoWithoutZone = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/.test(text);
152
+ date = new Date(isoWithoutZone ? text.replace(" ", "T") + "Z" : text);
151
153
  }
152
154
  if (Number.isNaN(date.getTime()))
153
155
  throw new Error("Enter a Unix timestamp (seconds or milliseconds) or a date such as 2026-10-27T09:30:00Z.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simplyutils-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server with everyday developer and text tools from SimplyUtils: JSON formatter, QR codes, unit converter, passwords, UUIDs, hashes, Base64, regex tester, timestamps and word counts.",
5
5
  "main": "dist/server.js",
6
6
  "scripts": {