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.
@@ -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, '&amp;')
126
+ .replace(/</g, '&lt;')
127
+ .replace(/>/g, '&gt;')
128
+ .replace(/"/g, '&quot;')
129
+ .replace(/'/g, '&#x27;');
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
+ }