it-tools-mcp 3.0.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/LICENSE +21 -0
- package/README.dockerhub.md +98 -0
- package/README.md +263 -0
- package/build/index.js +75 -0
- package/build/security.js +201 -0
- package/build/tools/color.js +67 -0
- package/build/tools/crypto.js +445 -0
- package/build/tools/dataFormat.js +517 -0
- package/build/tools/development.js +267 -0
- package/build/tools/encoding.js +240 -0
- package/build/tools/idGenerators.js +176 -0
- package/build/tools/math.js +306 -0
- package/build/tools/network.js +578 -0
- package/build/tools/text.js +678 -0
- package/build/tools/utility.js +407 -0
- package/package.json +94 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Security utilities for IT Tools MCP Server
|
|
4
|
+
*/
|
|
5
|
+
// Input size limits to prevent DoS attacks
|
|
6
|
+
export const INPUT_LIMITS = {
|
|
7
|
+
TEXT_MAX: 1000000, // 1MB for text input
|
|
8
|
+
JSON_MAX: 500000, // 500KB for JSON
|
|
9
|
+
HTML_MAX: 500000, // 500KB for HTML
|
|
10
|
+
XML_MAX: 500000, // 500KB for XML
|
|
11
|
+
YAML_MAX: 100000, // 100KB for YAML
|
|
12
|
+
CSV_MAX: 1000000, // 1MB for CSV
|
|
13
|
+
PASSWORD_MAX: 128, // Max password length
|
|
14
|
+
TOKEN_LENGTH_MAX: 1024, // Max token length
|
|
15
|
+
LIST_ITEMS_MAX: 10000, // Max list items
|
|
16
|
+
REGEX_MAX: 1000, // Max regex pattern length
|
|
17
|
+
};
|
|
18
|
+
// Base schema with common security validations
|
|
19
|
+
export const secureTextSchema = (maxLength = INPUT_LIMITS.TEXT_MAX) => z.string()
|
|
20
|
+
.max(maxLength, `Input too large (max ${maxLength} characters)`)
|
|
21
|
+
.refine((text) => !text.includes('\0'), "Null bytes not allowed");
|
|
22
|
+
// Secure schemas for different input types
|
|
23
|
+
export const secureSchemas = {
|
|
24
|
+
text: secureTextSchema(),
|
|
25
|
+
shortText: secureTextSchema(1000),
|
|
26
|
+
password: secureTextSchema(INPUT_LIMITS.PASSWORD_MAX),
|
|
27
|
+
json: secureTextSchema(INPUT_LIMITS.JSON_MAX),
|
|
28
|
+
html: secureTextSchema(INPUT_LIMITS.HTML_MAX),
|
|
29
|
+
xml: secureTextSchema(INPUT_LIMITS.XML_MAX),
|
|
30
|
+
yaml: secureTextSchema(INPUT_LIMITS.YAML_MAX),
|
|
31
|
+
csv: secureTextSchema(INPUT_LIMITS.CSV_MAX),
|
|
32
|
+
regex: secureTextSchema(INPUT_LIMITS.REGEX_MAX),
|
|
33
|
+
// Numeric with bounds
|
|
34
|
+
positiveInt: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
35
|
+
boundedInt: (min, max) => z.number().int().min(min).max(max),
|
|
36
|
+
// Safe URL validation
|
|
37
|
+
url: z.string().url().max(2048),
|
|
38
|
+
// Safe email validation
|
|
39
|
+
email: z.string().email().max(254),
|
|
40
|
+
// Base64 validation
|
|
41
|
+
base64: z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/, "Invalid Base64 format"),
|
|
42
|
+
// Hex validation
|
|
43
|
+
hexColor: z.string().regex(/^#?[0-9A-Fa-f]{6}$/, "Invalid hex color format"),
|
|
44
|
+
// Safe filename
|
|
45
|
+
filename: z.string()
|
|
46
|
+
.max(255)
|
|
47
|
+
.regex(/^[a-zA-Z0-9._-]+$/, "Filename contains invalid characters"),
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Rate limiting for tools (simple in-memory implementation)
|
|
51
|
+
*/
|
|
52
|
+
class SimpleRateLimiter {
|
|
53
|
+
requests = new Map();
|
|
54
|
+
windowMs;
|
|
55
|
+
maxRequests;
|
|
56
|
+
constructor(windowMs = 60000, maxRequests = 100) {
|
|
57
|
+
this.windowMs = windowMs;
|
|
58
|
+
this.maxRequests = maxRequests;
|
|
59
|
+
}
|
|
60
|
+
isAllowed(identifier) {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const windowStart = now - this.windowMs;
|
|
63
|
+
if (!this.requests.has(identifier)) {
|
|
64
|
+
this.requests.set(identifier, []);
|
|
65
|
+
}
|
|
66
|
+
const requests = this.requests.get(identifier);
|
|
67
|
+
// Remove old requests outside the window
|
|
68
|
+
while (requests.length > 0 && requests[0] < windowStart) {
|
|
69
|
+
requests.shift();
|
|
70
|
+
}
|
|
71
|
+
if (requests.length >= this.maxRequests) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
requests.push(now);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
reset(identifier) {
|
|
78
|
+
if (identifier) {
|
|
79
|
+
this.requests.delete(identifier);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.requests.clear();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Global rate limiter instance
|
|
87
|
+
export const rateLimiter = new SimpleRateLimiter();
|
|
88
|
+
/**
|
|
89
|
+
* Security wrapper for tool handlers
|
|
90
|
+
*/
|
|
91
|
+
export function secureToolHandler(handler, identifier = 'default') {
|
|
92
|
+
return async (params) => {
|
|
93
|
+
// Rate limiting check
|
|
94
|
+
if (!rateLimiter.isAllowed(identifier)) {
|
|
95
|
+
throw new Error('Rate limit exceeded. Please try again later.');
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return await handler(params);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
// Log error without exposing sensitive information
|
|
102
|
+
console.error(`Tool error for ${identifier}:`, error instanceof Error ? error.message : 'Unknown error');
|
|
103
|
+
// Return safe error message
|
|
104
|
+
throw new Error('Tool execution failed. Please check your input and try again.');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Input sanitization utilities
|
|
110
|
+
*/
|
|
111
|
+
export const sanitize = {
|
|
112
|
+
/**
|
|
113
|
+
* Remove potentially dangerous characters from text
|
|
114
|
+
*/
|
|
115
|
+
text: (input) => {
|
|
116
|
+
return input
|
|
117
|
+
.replace(/[\0\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control characters
|
|
118
|
+
.normalize('NFKC'); // Normalize Unicode
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* Escape HTML entities
|
|
122
|
+
*/
|
|
123
|
+
html: (input) => {
|
|
124
|
+
return input
|
|
125
|
+
.replace(/&/g, '&')
|
|
126
|
+
.replace(/</g, '<')
|
|
127
|
+
.replace(/>/g, '>')
|
|
128
|
+
.replace(/"/g, '"')
|
|
129
|
+
.replace(/'/g, ''');
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* Safe regex pattern validation
|
|
133
|
+
*/
|
|
134
|
+
regex: (pattern) => {
|
|
135
|
+
// Basic validation to prevent ReDoS
|
|
136
|
+
if (pattern.length > INPUT_LIMITS.REGEX_MAX) {
|
|
137
|
+
throw new Error('Regex pattern too long');
|
|
138
|
+
}
|
|
139
|
+
// Check for potentially dangerous patterns
|
|
140
|
+
const dangerousPatterns = [
|
|
141
|
+
/(\*\+|\+\*|\?\+|\+\?|\*\?|\?\*)/, // Nested quantifiers
|
|
142
|
+
/(\([^)]*){10,}/, // Excessive grouping
|
|
143
|
+
/(\|.*){20,}/, // Excessive alternation
|
|
144
|
+
];
|
|
145
|
+
for (const dangerous of dangerousPatterns) {
|
|
146
|
+
if (dangerous.test(pattern)) {
|
|
147
|
+
throw new Error('Potentially dangerous regex pattern detected');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return pattern;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Format bytes as human-readable string (e.g., 1.2 MB, 512 KB)
|
|
155
|
+
*/
|
|
156
|
+
function formatBytes(bytes) {
|
|
157
|
+
if (bytes < 1024)
|
|
158
|
+
return `${bytes} B`;
|
|
159
|
+
if (bytes < 1024 * 1024)
|
|
160
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
161
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
162
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
163
|
+
return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Memory and CPU usage monitoring (human-readable + raw values)
|
|
167
|
+
*/
|
|
168
|
+
export function getResourceUsage() {
|
|
169
|
+
const usage = process.memoryUsage();
|
|
170
|
+
const cpuUsage = process.cpuUsage();
|
|
171
|
+
const uptime = process.uptime();
|
|
172
|
+
return {
|
|
173
|
+
memory: {
|
|
174
|
+
heapUsed: formatBytes(usage.heapUsed),
|
|
175
|
+
heapUsedBytes: usage.heapUsed,
|
|
176
|
+
heapTotal: formatBytes(usage.heapTotal),
|
|
177
|
+
heapTotalBytes: usage.heapTotal,
|
|
178
|
+
external: formatBytes(usage.external),
|
|
179
|
+
externalBytes: usage.external,
|
|
180
|
+
rss: formatBytes(usage.rss),
|
|
181
|
+
rssBytes: usage.rss,
|
|
182
|
+
},
|
|
183
|
+
cpu: {
|
|
184
|
+
user: `${(cpuUsage.user / 1000).toFixed(1)} ms`, // microseconds to ms
|
|
185
|
+
userMicros: cpuUsage.user,
|
|
186
|
+
system: `${(cpuUsage.system / 1000).toFixed(1)} ms`,
|
|
187
|
+
systemMicros: cpuUsage.system,
|
|
188
|
+
},
|
|
189
|
+
uptime: `${uptime.toFixed(1)} s`,
|
|
190
|
+
uptimeSeconds: uptime,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Security headers for responses (if applicable)
|
|
195
|
+
*/
|
|
196
|
+
export const SECURITY_HEADERS = {
|
|
197
|
+
'X-Content-Type-Options': 'nosniff',
|
|
198
|
+
'X-Frame-Options': 'DENY',
|
|
199
|
+
'X-XSS-Protection': '1; mode=block',
|
|
200
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
201
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Color from "color";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export function registerColorTools(server) {
|
|
4
|
+
// Color conversion tools
|
|
5
|
+
server.tool("color-hex-to-rgb", "Convert HEX color to RGB", {
|
|
6
|
+
hex: z.string().describe("HEX color code (e.g., #FF5733 or FF5733)"),
|
|
7
|
+
}, async ({ hex }) => {
|
|
8
|
+
try {
|
|
9
|
+
const rgb = Color(hex).rgb().array();
|
|
10
|
+
return {
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `${hex} = RGB(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: `Error converting HEX to RGB: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
server.tool("color-rgb-to-hex", "Convert RGB color to HEX", {
|
|
31
|
+
r: z.number().describe("Red value (0-255)"),
|
|
32
|
+
g: z.number().describe("Green value (0-255)"),
|
|
33
|
+
b: z.number().describe("Blue value (0-255)"),
|
|
34
|
+
}, async ({ r, g, b }) => {
|
|
35
|
+
try {
|
|
36
|
+
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: "RGB values must be between 0 and 255.",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const hex = Color({ r, g, b }).hex().toUpperCase();
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: `RGB(${r}, ${g}, ${b}) = ${hex}`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Error converting RGB to HEX: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|