it-tools-mcp 3.0.8 → 3.0.10

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.
@@ -4,7 +4,7 @@
4
4
  [![Docker Image Size](https://img.shields.io/docker/image-size/wrenchpilot/it-tools-mcp/latest?refresh=1)](https://hub.docker.com/r/wrenchpilot/it-tools-mcp)
5
5
  [![Build Status](https://github.com/wrenchpilot/it-tools-mcp/workflows/Build%20and%20Push%20to%20Docker%20Hub/badge.svg)](https://github.com/wrenchpilot/it-tools-mcp/actions)
6
6
 
7
- A comprehensive Model Context Protocol (MCP) server that provides access to **75 IT tools and utilities** commonly used by developers, system administrators, and IT professionals. This server exposes a complete set of tools for encoding/decoding, text manipulation, hashing, network utilities, and many other common development and IT tasks.
7
+ A comprehensive Model Context Protocol (MCP) server that provides access to **86 IT tools and utilities** commonly used by developers, system administrators, and IT professionals. This server exposes a complete set of tools for encoding/decoding, text manipulation, hashing, network utilities, and many other common development and IT tasks.
8
8
 
9
9
  ## Using with VS Code
10
10
 
@@ -56,7 +56,7 @@ Add to your VS Code `settings.json`:
56
56
  }
57
57
  ```
58
58
 
59
- See the complete list of all 75 tools with detailed parameters on [GitHub](https://github.com/wrenchpilot/it-tools-mcp#available-tools)
59
+ See the complete list of all 86 tools with detailed parameters on [GitHub](https://github.com/wrenchpilot/it-tools-mcp#available-tools)
60
60
 
61
61
  ## 📸 Examples in Action
62
62
 
@@ -76,7 +76,7 @@ Built with **TypeScript**, **Zod** validation, and **MCP SDK** for robust, type-
76
76
 
77
77
  This project was developed using **VS Code**, **Copilot Chat Agent**, **Playwright MCP**, and the **Claude Sonnet 4 Model**, showcasing modern AI-assisted software development:
78
78
 
79
- - 🔧 **All 75 tools** designed and implemented with AI assistance
79
+ - 🔧 **All 86 tools** designed and implemented with AI assistance
80
80
  - 📦 **Complete Docker setup** with GitHub Actions CI/CD pipeline
81
81
  - 🔍 **Schema optimization** with systematic validation cleanup
82
82
  - 📚 **Comprehensive documentation** and tool catalogs
@@ -86,6 +86,7 @@ This project was developed using **VS Code**, **Copilot Chat Agent**, **Playwrig
86
86
  ```text
87
87
  src/
88
88
  ├── index.ts # Main MCP server
89
+ ├── security.ts # Security settings
89
90
  └── tools/ # Tool modules by category
90
91
  ├── encoding.ts # Base64, URL, HTML encoding
91
92
  ├── crypto.ts # Hashing, JWT, passwords
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **📝 Note**: A condensed version of this README is automatically synced to [Docker Hub](https://hub.docker.com/r/wrenchpilot/it-tools-mcp) due to character limits.
8
8
 
9
- A comprehensive Model Context Protocol (MCP) server that provides access to 75 IT tools and utilities commonly used by developers, system administrators, and IT professionals. This server exposes a complete set of tools for encoding/decoding, text manipulation, hashing, network utilities, and many other common development and IT tasks.
9
+ A comprehensive Model Context Protocol (MCP) server that provides access to 86 IT tools and utilities commonly used by developers, system administrators, and IT professionals. This server exposes a complete set of tools for encoding/decoding, text manipulation, hashing, network utilities, and many other common development and IT tasks.
10
10
 
11
11
  ## 📦 Installation & Setup
12
12
 
@@ -80,16 +80,16 @@ echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"base64-enc
80
80
 
81
81
  ## 🛠️ Tool Categories
82
82
 
83
- This MCP server provides **75 tools** across **8 categories**:
83
+ This MCP server provides **86 tools** across **8 categories**:
84
84
 
85
85
  - **🔧 Encoding & Decoding** (9 tools): Base64, URL, HTML entities, text-to-binary, Unicode
86
86
  - **📝 Data Format** (11 tools): JSON, XML, YAML, SQL, TOML, Markdown ↔ HTML conversion
87
87
  - **🔐 Security & Crypto** (12 tools): Hashing (MD5, SHA1-512), HMAC, JWT, bcrypt, passwords, tokens
88
88
  - **✨ Text Processing** (16 tools): Case conversion, stats, diff, ASCII art, NATO alphabet, slugify
89
- - **🌐 Network & Web** (8 tools): IPv4/IPv6 subnets, URL parsing, MAC addresses, phone formatting
89
+ - **🌐 Network & System** (19 tools): IPv4/IPv6 subnets, URL parsing, MAC addresses, phone formatting, ps, top, cat, head, tail, grep, ping, nslookup, telnet, dig, ssh, random-port, mac-address-generate, ip/ipv6 calculators
90
90
  - **🔢 Math & Calculations** (6 tools): Expression evaluation, base conversion, temperature, percentages
91
- - **🆔 ID & Code Generators** (4 tools): UUID, ULID, QR codes, SVG placeholders
92
- - **🔧 Development & Utilities** (9 tools): Regex testing, cron expressions, color conversion, MIME types
91
+ - **🆔 ID & Code Generators** (4 tools: UUID, ULID, QR codes, SVG placeholders
92
+ - **🛠️ Utility Tools** (9 tools): Color, MIME, HTTP, device info, email normalization, etc.
93
93
 
94
94
  ## 📸 Screenshot Examples
95
95
 
@@ -159,14 +159,25 @@ This MCP server provides **75 tools** across **8 categories**:
159
159
  | `lorem-ipsum-generator` | Generate Lorem Ipsum | `type?: 'words' \| 'sentences' \| 'paragraphs'`, `count?: number` |
160
160
  | `numeronym-generator` | Generate numeronyms | `text: string` |
161
161
  | `emoji-search` | Search emojis | `query: string` |
162
- | **Network & Web** | | |
162
+ | **Network & System** | | |
163
+ | `ps` | List running processes | None |
164
+ | `top` | Show top processes (by CPU) | None |
165
+ | `cat` | Display file content | `file: string` |
166
+ | `head` | Show first N lines of file | `file: string`, `lines?: number` |
167
+ | `tail` | Show last N lines of file | `file: string`, `lines?: number` |
168
+ | `grep` | Search for pattern in file | `file: string`, `pattern: string` |
169
+ | `ping` | Ping a host | `target: string`, `count?: number` |
170
+ | `nslookup` | DNS lookup (A/AAAA/CNAME) | `target: string` |
171
+ | `telnet` | Test TCP connectivity | `target: string`, `port: number` |
172
+ | `dig` | DNS query (custom type) | `target: string`, `type?: string` |
173
+ | `ssh` | SSH command execution | `target: string`, `user: string`, `command: string` |
163
174
  | `ip-subnet-calculator` | Calculate IPv4 subnet | `ip: string`, `cidr: number` |
164
175
  | `ipv4-subnet-calc` | Enhanced IPv4 subnet calc | `cidr: string` |
165
176
  | `ipv6-ula-generator` | Generate IPv6 ULA | `globalId?: string` |
166
177
  | `url-parse` | Parse URL components | `url: string` |
167
178
  | `random-port` | Generate random ports | `count?: number`, `min?: number`, `max?: number`, `exclude?: number[]` |
168
- | `mac-address-generate` | Generate MAC address | `prefix?: string`, `separator?: ':' \| '-'` |
169
- | `phone-format` | Format phone numbers | `phoneNumber: string`, `countryCode?: string` |
179
+ | `mac-address-generate` | Generate MAC address | `prefix?: string`, `separator?: ':' | '-'` |
180
+ | `phone-format` | Parse and format phone numbers | `phoneNumber: string`, `countryCode?: string` |
170
181
  | `iban-validate` | Validate IBAN | `iban: string` |
171
182
  | **Math & Calculations** | | |
172
183
  | `math-evaluate` | Evaluate expressions | `expression: string` |
@@ -260,6 +271,7 @@ This showcases how AI can accelerate development while maintaining code quality,
260
271
  ```text
261
272
  src/
262
273
  ├── index.ts # Main MCP server
274
+ ├── security.ts # Security settings
263
275
  └── tools/ # Tool modules by category
264
276
  ├── encoding.ts # Base64, URL, HTML encoding
265
277
  ├── crypto.ts # Hashing, JWT, passwords
@@ -504,4 +504,37 @@ ${differences.map((diff, i) => `${i + 1}. ${diff}`).join('\n')}`,
504
504
  };
505
505
  }
506
506
  });
507
+ // Phone number formatter
508
+ server.tool("phone-format", "Parse and format phone numbers", {
509
+ phoneNumber: z.string().describe("Phone number to parse and format"),
510
+ countryCode: z.string().optional().describe("Country code (e.g., 'US', 'GB', 'FR')"),
511
+ }, async ({ phoneNumber, countryCode }) => {
512
+ try {
513
+ const { isValidPhoneNumber, parsePhoneNumber } = await import("libphonenumber-js");
514
+ // First check if it's a valid phone number
515
+ if (!isValidPhoneNumber(phoneNumber, countryCode)) {
516
+ throw new Error("Invalid phone number format");
517
+ }
518
+ // Parse the phone number
519
+ const parsedNumber = parsePhoneNumber(phoneNumber, countryCode);
520
+ return {
521
+ content: [
522
+ {
523
+ type: "text",
524
+ text: `Phone Number Formatting:\n\nOriginal: ${phoneNumber}\nCountry: ${parsedNumber.country || 'Unknown'}\nNational: ${parsedNumber.formatNational()}\nInternational: ${parsedNumber.formatInternational()}\nE.164: ${parsedNumber.format('E.164')}\nURI: ${parsedNumber.getURI()}\n\nDetails:\nType: ${parsedNumber.getType() || 'Unknown'}\nCountry Code: +${parsedNumber.countryCallingCode}\nNational Number: ${parsedNumber.nationalNumber}\nValid: ${parsedNumber.isValid()}\n\n✅ Formatted using libphonenumber-js library for accuracy.`,
525
+ },
526
+ ],
527
+ };
528
+ }
529
+ catch (error) {
530
+ return {
531
+ content: [
532
+ {
533
+ type: "text",
534
+ text: `Error formatting phone number: ${error instanceof Error ? error.message : 'Unknown error'}\n\n💡 Tips:\n• Include country code (e.g., +1 555-123-4567)\n• Use standard formats (e.g., (555) 123-4567)\n• Specify country code parameter if needed\n• Examples: "+1-555-123-4567", "555-123-4567" with countryCode="US"`,
535
+ },
536
+ ],
537
+ };
538
+ }
539
+ });
507
540
  }
@@ -1,4 +1,13 @@
1
1
  import { z } from "zod";
2
+ import { Client as SSHClient } from "ssh2";
3
+ import ping from "ping";
4
+ import dns from "dns";
5
+ import Telnet from "telnet-client";
6
+ import psList from "ps-list";
7
+ import fs from "fs";
8
+ import readLastLines from "read-last-lines";
9
+ // For Telnet import (telnet-client exports as an object, not a class)
10
+ const TelnetClient = Telnet.Telnet || Telnet;
2
11
  export function registerNetworkTools(server) {
3
12
  // IP address tools
4
13
  server.tool("ip-subnet-calculator", "Calculate subnet information for IPv4", {
@@ -440,60 +449,7 @@ ${prefix ? `Used prefix: ${prefix}` : 'Randomly generated'}`,
440
449
  };
441
450
  }
442
451
  });
443
- // Phone number formatter
444
- server.tool("phone-format", "Parse and format phone numbers", {
445
- phoneNumber: z.string().describe("Phone number to parse and format"),
446
- countryCode: z.string().optional().describe("Country code (e.g., 'US', 'GB', 'FR')"),
447
- }, async ({ phoneNumber, countryCode }) => {
448
- try {
449
- const { isValidPhoneNumber, parsePhoneNumber } = await import("libphonenumber-js");
450
- // First check if it's a valid phone number
451
- if (!isValidPhoneNumber(phoneNumber, countryCode)) {
452
- throw new Error("Invalid phone number format");
453
- }
454
- // Parse the phone number
455
- const parsedNumber = parsePhoneNumber(phoneNumber, countryCode);
456
- return {
457
- content: [
458
- {
459
- type: "text",
460
- text: `Phone Number Formatting:
461
-
462
- Original: ${phoneNumber}
463
- Country: ${parsedNumber.country || 'Unknown'}
464
- National: ${parsedNumber.formatNational()}
465
- International: ${parsedNumber.formatInternational()}
466
- E.164: ${parsedNumber.format('E.164')}
467
- URI: ${parsedNumber.getURI()}
468
-
469
- Details:
470
- Type: ${parsedNumber.getType() || 'Unknown'}
471
- Country Code: +${parsedNumber.countryCallingCode}
472
- National Number: ${parsedNumber.nationalNumber}
473
- Valid: ${parsedNumber.isValid()}
474
-
475
- ✅ Formatted using libphonenumber-js library for accuracy.`,
476
- },
477
- ],
478
- };
479
- }
480
- catch (error) {
481
- return {
482
- content: [
483
- {
484
- type: "text",
485
- text: `Error formatting phone number: ${error instanceof Error ? error.message : 'Unknown error'}
486
-
487
- 💡 Tips:
488
- • Include country code (e.g., +1 555-123-4567)
489
- • Use standard formats (e.g., (555) 123-4567)
490
- • Specify country code parameter if needed
491
- • Examples: "+1-555-123-4567", "555-123-4567" with countryCode="US"`,
492
- },
493
- ],
494
- };
495
- }
496
- });
452
+ // Phone number formatter tool moved to dataFormat.ts
497
453
  // IBAN validator (using iban library)
498
454
  server.tool("iban-validate", "Validate and parse IBAN (International Bank Account Number)", {
499
455
  iban: z.string().describe("IBAN to validate and parse"),
@@ -575,4 +531,194 @@ Common issues:
575
531
  };
576
532
  }
577
533
  });
534
+ // SSH Tool (using ssh2)
535
+ server.tool("ssh", "Connect to a target via SSH", {
536
+ target: z.string().describe("Target host"),
537
+ user: z.string().describe("Username"),
538
+ command: z.string().describe("Command to run on remote host"),
539
+ privateKey: z.string().optional().describe("Private key for authentication (PEM format, optional)")
540
+ }, async ({ target, user, command, privateKey }) => {
541
+ return new Promise((resolve) => {
542
+ const conn = new SSHClient();
543
+ let output = "";
544
+ conn.on("ready", () => {
545
+ conn.exec(command, (err, stream) => {
546
+ if (err) {
547
+ resolve({ content: [{ type: "text", text: `SSH error: ${err.message}` }] });
548
+ conn.end();
549
+ return;
550
+ }
551
+ stream.on("close", () => {
552
+ conn.end();
553
+ resolve({ content: [{ type: "text", text: output }] });
554
+ }).on("data", (data) => {
555
+ output += data.toString();
556
+ }).stderr.on("data", (data) => {
557
+ output += data.toString();
558
+ });
559
+ });
560
+ }).on("error", (err) => {
561
+ resolve({ content: [{ type: "text", text: `SSH connection error: ${err.message}` }] });
562
+ }).connect({
563
+ host: target,
564
+ username: user,
565
+ ...(privateKey ? { privateKey } : {})
566
+ });
567
+ });
568
+ });
569
+ // Ping Tool (using ping)
570
+ server.tool("ping", "Ping a host to check connectivity", {
571
+ target: z.string().describe("Host to ping"),
572
+ count: z.number().default(4).describe("Number of ping attempts")
573
+ }, async ({ target, count }) => {
574
+ try {
575
+ const res = await ping.promise.probe(target, { min_reply: count });
576
+ return {
577
+ content: [
578
+ { type: "text", text: `Ping to ${target}:\nAlive: ${res.alive}\nTime: ${res.time} ms\nOutput: ${res.output}` }
579
+ ]
580
+ };
581
+ }
582
+ catch (error) {
583
+ return { content: [{ type: "text", text: `Ping failed: ${error instanceof Error ? error.message : error}` }] };
584
+ }
585
+ });
586
+ // nslookup Tool (using dns)
587
+ server.tool("nslookup", "Perform DNS lookup on a hostname or IP address", {
588
+ target: z.string().describe("Hostname or IP address")
589
+ }, async ({ target }) => {
590
+ return new Promise((resolve) => {
591
+ dns.lookup(target, (err, address, family) => {
592
+ if (err) {
593
+ resolve({ content: [{ type: "text", text: `nslookup failed: ${err.message}` }] });
594
+ }
595
+ else {
596
+ resolve({ content: [{ type: "text", text: `Address: ${address}\nFamily: IPv${family}` }] });
597
+ }
598
+ });
599
+ });
600
+ });
601
+ // telnet Tool (using telnet-client)
602
+ server.tool("telnet", "Test TCP connectivity to a host and port", {
603
+ target: z.string().describe("Host to connect to"),
604
+ port: z.number().describe("Port number")
605
+ }, async ({ target, port }) => {
606
+ const connection = new TelnetClient();
607
+ const params = {
608
+ host: target,
609
+ port: port,
610
+ shellPrompt: /[$%#>]$/,
611
+ timeout: 1500,
612
+ };
613
+ try {
614
+ await connection.connect(params);
615
+ await connection.end();
616
+ return { content: [{ type: "text", text: `Telnet to ${target}:${port} succeeded.` }] };
617
+ }
618
+ catch (error) {
619
+ return { content: [{ type: "text", text: `Telnet failed: ${error instanceof Error ? error.message : error}` }] };
620
+ }
621
+ });
622
+ // dig Tool (using dns.resolve)
623
+ server.tool("dig", "Perform DNS lookup with dig command", {
624
+ target: z.string().describe("Hostname or IP address"),
625
+ type: z.string().default("A").describe("DNS record type")
626
+ }, async ({ target, type }) => {
627
+ return new Promise((resolve) => {
628
+ dns.resolve(target, type, (err, addresses) => {
629
+ if (err) {
630
+ resolve({ content: [{ type: "text", text: `dig failed: ${err.message}` }] });
631
+ }
632
+ else {
633
+ resolve({ content: [{ type: "text", text: `${type} records for ${target}:\n${JSON.stringify(addresses, null, 2)}` }] });
634
+ }
635
+ });
636
+ });
637
+ });
638
+ // ps Tool (using ps-list)
639
+ server.tool("ps", "List running processes", {}, async () => {
640
+ try {
641
+ const processes = await psList();
642
+ // Defensive: handle missing properties and filter out bad entries
643
+ const output = processes
644
+ .map(p => {
645
+ const pid = p.pid ?? 'N/A';
646
+ const name = p.name ?? 'N/A';
647
+ return `${pid}\t${name}`;
648
+ })
649
+ .join("\n");
650
+ return { content: [{ type: "text", text: output || 'No processes found.' }] };
651
+ }
652
+ catch (error) {
653
+ // Log error for debugging
654
+ console.error('ps error:', error);
655
+ return { content: [{ type: "text", text: `ps failed: ${error instanceof Error ? error.message : error}` }] };
656
+ }
657
+ });
658
+ // cat Tool (using fs)
659
+ server.tool("cat", "Display content of a file", {
660
+ file: z.string().describe("File path")
661
+ }, async ({ file }) => {
662
+ try {
663
+ const data = fs.readFileSync(file, "utf8");
664
+ return { content: [{ type: "text", text: data }] };
665
+ }
666
+ catch (error) {
667
+ return { content: [{ type: "text", text: `cat failed: ${error instanceof Error ? error.message : error}` }] };
668
+ }
669
+ });
670
+ // top Tool (using ps-list, show top 10 by CPU)
671
+ server.tool("top", "Display system processes (snapshot)", {}, async () => {
672
+ try {
673
+ const processes = await psList();
674
+ const sorted = processes.sort((a, b) => (b.cpu || 0) - (a.cpu || 0)).slice(0, 10);
675
+ const output = sorted.map(p => `${p.pid}\t${p.name}\tCPU: ${p.cpu || 0}%\tMEM: ${p.memory || 0}`).join("\n");
676
+ return { content: [{ type: "text", text: output }] };
677
+ }
678
+ catch (error) {
679
+ return { content: [{ type: "text", text: `top failed: ${error instanceof Error ? error.message : error}` }] };
680
+ }
681
+ });
682
+ // grep Tool (using grep-js)
683
+ server.tool("grep", "Search for patterns in files", {
684
+ pattern: z.string().describe("Pattern to search for"),
685
+ file: z.string().describe("File path")
686
+ }, async ({ pattern, file }) => {
687
+ try {
688
+ const data = fs.readFileSync(file, "utf8");
689
+ const lines = data.split("\n");
690
+ const matches = lines.filter(line => line.includes(pattern));
691
+ return { content: [{ type: "text", text: matches.join("\n") }] };
692
+ }
693
+ catch (error) {
694
+ return { content: [{ type: "text", text: `grep failed: ${error instanceof Error ? error.message : error}` }] };
695
+ }
696
+ });
697
+ // head Tool (using fs)
698
+ server.tool("head", "Display the beginning of a file", {
699
+ file: z.string().describe("File path"),
700
+ lines: z.number().default(10).describe("Number of lines")
701
+ }, async ({ file, lines }) => {
702
+ try {
703
+ const data = fs.readFileSync(file, "utf8");
704
+ const out = data.split("\n").slice(0, lines).join("\n");
705
+ return { content: [{ type: "text", text: out }] };
706
+ }
707
+ catch (error) {
708
+ return { content: [{ type: "text", text: `head failed: ${error instanceof Error ? error.message : error}` }] };
709
+ }
710
+ });
711
+ // tail Tool (using read-last-lines)
712
+ server.tool("tail", "Display the end of a file", {
713
+ file: z.string().describe("File path"),
714
+ lines: z.number().default(10).describe("Number of lines")
715
+ }, async ({ file, lines }) => {
716
+ try {
717
+ const out = await readLastLines.read(file, lines);
718
+ return { content: [{ type: "text", text: out }] };
719
+ }
720
+ catch (error) {
721
+ return { content: [{ type: "text", text: `tail failed: ${error instanceof Error ? error.message : error}` }] };
722
+ }
723
+ });
578
724
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "it-tools-mcp",
3
- "version": "3.0.8",
3
+ "version": "3.0.10",
4
4
  "description": "MCP server providing access to various IT tools and utilities for developers",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",
@@ -13,9 +13,8 @@
13
13
  "start:node": "node build/index.js",
14
14
  "dev": "tsc && node build/index.js",
15
15
  "test": "node tests/test-server.mjs",
16
- "test:math": "node tests/math-tool-test.mjs",
16
+ "test:all": "node tests/all-tools-test.mjs",
17
17
  "test:security": "node tests/security-test.mjs",
18
- "test:all": "npm run test && npm run test:security && npm run test:math",
19
18
  "docker:build": "docker buildx build --platform linux/amd64,linux/arm64 --provenance=true --sbom=true -t it-tools-mcp .",
20
19
  "docker:build:local": "docker build -t it-tools-mcp .",
21
20
  "docker:run": "docker-compose up --build",
@@ -56,7 +55,10 @@
56
55
  "@types/marked": "^5.0.2",
57
56
  "@types/mime-types": "^3.0.1",
58
57
  "@types/node": "^22.15.32",
58
+ "@types/ping": "^0.4.4",
59
+ "@types/shell-escape": "^0.2.3",
59
60
  "@types/speakeasy": "^2.0.10",
61
+ "@types/ssh2": "^1.15.5",
60
62
  "@types/turndown": "^5.0.5",
61
63
  "@types/validator": "^13.15.2",
62
64
  "@types/xml-formatter": "^1.2.0",
@@ -82,11 +84,16 @@
82
84
  "marked": "^15.0.12",
83
85
  "mathjs": "^14.5.2",
84
86
  "mime-types": "^3.0.1",
85
- "node-fetch": "^3.3.2",
86
87
  "papaparse": "^5.5.3",
88
+ "ping": "^0.4.4",
89
+ "ps-list": "^8.1.1",
87
90
  "qrcode": "^1.5.4",
91
+ "read-last-lines": "^1.8.0",
92
+ "shell-escape": "^0.2.0",
88
93
  "speakeasy": "^2.0.0",
89
94
  "sql-formatter": "^15.6.5",
95
+ "ssh2": "^1.16.0",
96
+ "telnet-client": "^2.2.5",
90
97
  "turndown": "^7.2.0",
91
98
  "ulid": "^3.0.1",
92
99
  "validator": "^13.15.15",