ntfy-mcp-server 1.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.
Files changed (59) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +423 -0
  3. package/dist/config/index.d.ts +23 -0
  4. package/dist/config/index.js +111 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +108 -0
  7. package/dist/mcp-server/resources/ntfyResource/getNtfyTopic.d.ts +2 -0
  8. package/dist/mcp-server/resources/ntfyResource/getNtfyTopic.js +111 -0
  9. package/dist/mcp-server/resources/ntfyResource/index.d.ts +12 -0
  10. package/dist/mcp-server/resources/ntfyResource/index.js +72 -0
  11. package/dist/mcp-server/resources/ntfyResource/types.d.ts +27 -0
  12. package/dist/mcp-server/resources/ntfyResource/types.js +8 -0
  13. package/dist/mcp-server/server.d.ts +40 -0
  14. package/dist/mcp-server/server.js +245 -0
  15. package/dist/mcp-server/tools/ntfyTool/index.d.ts +11 -0
  16. package/dist/mcp-server/tools/ntfyTool/index.js +110 -0
  17. package/dist/mcp-server/tools/ntfyTool/ntfyMessage.d.ts +9 -0
  18. package/dist/mcp-server/tools/ntfyTool/ntfyMessage.js +289 -0
  19. package/dist/mcp-server/tools/ntfyTool/types.d.ts +252 -0
  20. package/dist/mcp-server/tools/ntfyTool/types.js +144 -0
  21. package/dist/mcp-server/utils/registrationHelper.d.ts +48 -0
  22. package/dist/mcp-server/utils/registrationHelper.js +63 -0
  23. package/dist/services/ntfy/constants.d.ts +37 -0
  24. package/dist/services/ntfy/constants.js +37 -0
  25. package/dist/services/ntfy/errors.d.ts +79 -0
  26. package/dist/services/ntfy/errors.js +134 -0
  27. package/dist/services/ntfy/index.d.ts +33 -0
  28. package/dist/services/ntfy/index.js +56 -0
  29. package/dist/services/ntfy/publisher.d.ts +66 -0
  30. package/dist/services/ntfy/publisher.js +229 -0
  31. package/dist/services/ntfy/subscriber.d.ts +81 -0
  32. package/dist/services/ntfy/subscriber.js +502 -0
  33. package/dist/services/ntfy/types.d.ts +161 -0
  34. package/dist/services/ntfy/types.js +4 -0
  35. package/dist/services/ntfy/utils.d.ts +85 -0
  36. package/dist/services/ntfy/utils.js +410 -0
  37. package/dist/types-global/errors.d.ts +35 -0
  38. package/dist/types-global/errors.js +39 -0
  39. package/dist/types-global/mcp.d.ts +30 -0
  40. package/dist/types-global/mcp.js +25 -0
  41. package/dist/types-global/tool.d.ts +61 -0
  42. package/dist/types-global/tool.js +99 -0
  43. package/dist/utils/errorHandler.d.ts +98 -0
  44. package/dist/utils/errorHandler.js +271 -0
  45. package/dist/utils/idGenerator.d.ts +94 -0
  46. package/dist/utils/idGenerator.js +149 -0
  47. package/dist/utils/index.d.ts +13 -0
  48. package/dist/utils/index.js +16 -0
  49. package/dist/utils/logger.d.ts +36 -0
  50. package/dist/utils/logger.js +92 -0
  51. package/dist/utils/rateLimiter.d.ts +115 -0
  52. package/dist/utils/rateLimiter.js +180 -0
  53. package/dist/utils/requestContext.d.ts +68 -0
  54. package/dist/utils/requestContext.js +91 -0
  55. package/dist/utils/sanitization.d.ts +224 -0
  56. package/dist/utils/sanitization.js +367 -0
  57. package/dist/utils/security.d.ts +26 -0
  58. package/dist/utils/security.js +27 -0
  59. package/package.json +47 -0
@@ -0,0 +1,91 @@
1
+ import { logger } from './logger.js';
2
+ /**
3
+ * Request context utilities class
4
+ */
5
+ export class RequestContextService {
6
+ /**
7
+ * Private constructor to enforce singleton pattern
8
+ */
9
+ constructor() {
10
+ this.config = {};
11
+ logger.debug('RequestContext service initialized');
12
+ }
13
+ /**
14
+ * Get the singleton RequestContextService instance
15
+ * @returns RequestContextService instance
16
+ */
17
+ static getInstance() {
18
+ if (!RequestContextService.instance) {
19
+ RequestContextService.instance = new RequestContextService();
20
+ }
21
+ return RequestContextService.instance;
22
+ }
23
+ /**
24
+ * Configure service settings
25
+ * @param config New configuration
26
+ * @returns Updated configuration
27
+ */
28
+ configure(config) {
29
+ this.config = {
30
+ ...this.config,
31
+ ...config
32
+ };
33
+ logger.debug('RequestContext configuration updated');
34
+ return { ...this.config };
35
+ }
36
+ /**
37
+ * Get current configuration
38
+ * @returns Current configuration
39
+ */
40
+ getConfig() {
41
+ return { ...this.config };
42
+ }
43
+ /**
44
+ * Create a request context with unique ID and timestamp
45
+ * @param additionalContext Additional context properties
46
+ * @returns Request context object
47
+ */
48
+ createRequestContext(additionalContext = {}) {
49
+ const requestId = crypto.randomUUID();
50
+ const timestamp = new Date().toISOString();
51
+ return {
52
+ requestId,
53
+ timestamp,
54
+ ...additionalContext
55
+ };
56
+ }
57
+ /**
58
+ * Generate a secure random string
59
+ * @param length Length of the string
60
+ * @param chars Character set to use
61
+ * @returns Random string
62
+ */
63
+ generateSecureRandomString(length = 32, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') {
64
+ const randomValues = new Uint8Array(length);
65
+ crypto.getRandomValues(randomValues);
66
+ let result = '';
67
+ for (let i = 0; i < length; i++) {
68
+ result += chars[randomValues[i] % chars.length];
69
+ }
70
+ return result;
71
+ }
72
+ }
73
+ // Create and export singleton instance
74
+ export const requestContextService = RequestContextService.getInstance();
75
+ // Export convenience functions that delegate to the singleton instance
76
+ export const configureContext = (config) => {
77
+ return requestContextService.configure(config);
78
+ };
79
+ export const createRequestContext = (additionalContext = {}) => {
80
+ return requestContextService.createRequestContext(additionalContext);
81
+ };
82
+ export const generateSecureRandomString = (length = 32, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') => {
83
+ return requestContextService.generateSecureRandomString(length, chars);
84
+ };
85
+ // Export default utilities
86
+ export default {
87
+ requestContextService,
88
+ configureContext,
89
+ createRequestContext,
90
+ generateSecureRandomString
91
+ };
@@ -0,0 +1,224 @@
1
+ import sanitizeHtml from 'sanitize-html';
2
+ /**
3
+ * Options for path sanitization
4
+ */
5
+ export interface PathSanitizeOptions {
6
+ /** Restrict paths to a specific root directory */
7
+ rootDir?: string;
8
+ /** Normalize Windows-style paths to POSIX-style */
9
+ toPosix?: boolean;
10
+ /** Allow absolute paths (if false, converts to relative paths) */
11
+ allowAbsolute?: boolean;
12
+ }
13
+ /**
14
+ * Context-specific input sanitization options
15
+ */
16
+ export interface SanitizeStringOptions {
17
+ /** Handle content differently based on context */
18
+ context?: 'text' | 'html' | 'attribute' | 'url' | 'javascript';
19
+ /** Custom allowed tags when using html context */
20
+ allowedTags?: string[];
21
+ /** Custom allowed attributes when using html context */
22
+ allowedAttributes?: Record<string, string[]>;
23
+ }
24
+ /**
25
+ * Configuration for HTML sanitization
26
+ */
27
+ export interface HtmlSanitizeConfig {
28
+ /** Allowed HTML tags */
29
+ allowedTags?: string[];
30
+ /** Allowed HTML attributes (global or per-tag) */
31
+ allowedAttributes?: sanitizeHtml.IOptions['allowedAttributes'];
32
+ /** Allow preserving comments - uses allowedTags internally */
33
+ preserveComments?: boolean;
34
+ /** Custom URL sanitizer */
35
+ transformTags?: sanitizeHtml.IOptions['transformTags'];
36
+ }
37
+ /**
38
+ * Sanitization class for handling various input sanitization tasks
39
+ */
40
+ export declare class Sanitization {
41
+ private static instance;
42
+ /** Default list of sensitive fields for sanitizing logs */
43
+ private sensitiveFields;
44
+ /** Default sanitize-html configuration */
45
+ private defaultHtmlSanitizeConfig;
46
+ /**
47
+ * Private constructor to enforce singleton pattern
48
+ */
49
+ private constructor();
50
+ /**
51
+ * Get the singleton Sanitization instance
52
+ * @returns Sanitization instance
53
+ */
54
+ static getInstance(): Sanitization;
55
+ /**
56
+ * Set sensitive fields for log sanitization
57
+ * @param fields Array of field names to consider sensitive
58
+ */
59
+ setSensitiveFields(fields: string[]): void;
60
+ /**
61
+ * Get the current list of sensitive fields
62
+ * @returns Array of sensitive field names
63
+ */
64
+ getSensitiveFields(): string[];
65
+ /**
66
+ * Sanitize HTML content using sanitize-html library
67
+ * @param input HTML string to sanitize
68
+ * @param config Optional custom sanitization config
69
+ * @returns Sanitized HTML
70
+ */
71
+ sanitizeHtml(input: string, config?: HtmlSanitizeConfig): string;
72
+ /**
73
+ * Sanitize string input based on context
74
+ * @param input String to sanitize
75
+ * @param options Sanitization options
76
+ * @returns Sanitized string
77
+ */
78
+ sanitizeString(input: string, options?: SanitizeStringOptions): string;
79
+ /**
80
+ * Sanitize URL with robust validation and sanitization
81
+ * @param input URL to sanitize
82
+ * @param allowedProtocols Allowed URL protocols
83
+ * @returns Sanitized URL
84
+ */
85
+ sanitizeUrl(input: string, allowedProtocols?: string[]): string;
86
+ /**
87
+ * Sanitize file paths to prevent path traversal attacks
88
+ * @param input Path to sanitize
89
+ * @param options Options for path sanitization
90
+ * @returns Sanitized and normalized path
91
+ */
92
+ sanitizePath(input: string, options?: PathSanitizeOptions): string;
93
+ /**
94
+ * Sanitize a JSON string
95
+ * @param input JSON string to sanitize
96
+ * @param maxSize Maximum allowed size in bytes
97
+ * @returns Parsed and sanitized object
98
+ */
99
+ sanitizeJson<T = unknown>(input: string, maxSize?: number): T;
100
+ /**
101
+ * Ensure input is within a numeric range
102
+ * @param input Number to validate
103
+ * @param min Minimum allowed value
104
+ * @param max Maximum allowed value
105
+ * @returns Sanitized number within range
106
+ */
107
+ sanitizeNumber(input: number | string, min?: number, max?: number): number;
108
+ /**
109
+ * Sanitize input for logging to protect sensitive information
110
+ * @param input Input to sanitize
111
+ * @returns Sanitized input safe for logging
112
+ */
113
+ sanitizeForLogging(input: unknown): unknown;
114
+ /**
115
+ * Private helper to convert attribute format from record to sanitize-html format
116
+ */
117
+ private convertAttributesFormat;
118
+ /**
119
+ * Recursively redact sensitive fields in an object
120
+ */
121
+ private redactSensitiveFields;
122
+ }
123
+ export declare const sanitization: Sanitization;
124
+ export declare const sanitizeInput: {
125
+ /**
126
+ * Remove potentially dangerous characters from strings based on context
127
+ * @param input String to sanitize
128
+ * @param options Sanitization options for context-specific handling
129
+ * @returns Sanitized string
130
+ */
131
+ string: (input: string, options?: SanitizeStringOptions) => string;
132
+ /**
133
+ * Sanitize HTML to prevent XSS
134
+ * @param input HTML string to sanitize
135
+ * @param config Optional custom sanitization config
136
+ * @returns Sanitized HTML
137
+ */
138
+ html: (input: string, config?: HtmlSanitizeConfig) => string;
139
+ /**
140
+ * Sanitize URLs
141
+ * @param input URL to sanitize
142
+ * @param allowedProtocols Allowed URL protocols
143
+ * @returns Sanitized URL
144
+ */
145
+ url: (input: string, allowedProtocols?: string[]) => string;
146
+ /**
147
+ * Sanitize file paths to prevent path traversal attacks
148
+ * @param input Path to sanitize
149
+ * @param options Options for path sanitization
150
+ * @returns Sanitized and normalized path
151
+ */
152
+ path: (input: string, options?: PathSanitizeOptions) => string;
153
+ /**
154
+ * Sanitize a JSON string
155
+ * @param input JSON string to sanitize
156
+ * @param maxSize Maximum allowed size in bytes
157
+ * @returns Parsed and sanitized object
158
+ */
159
+ json: <T = unknown>(input: string, maxSize?: number) => T;
160
+ /**
161
+ * Ensure input is within a numeric range
162
+ * @param input Number to validate
163
+ * @param min Minimum allowed value
164
+ * @param max Maximum allowed value
165
+ * @returns Sanitized number within range
166
+ */
167
+ number: (input: number | string, min?: number, max?: number) => number;
168
+ };
169
+ /**
170
+ * Sanitize input for logging to protect sensitive information
171
+ * @param input Input to sanitize
172
+ * @returns Sanitized input safe for logging
173
+ */
174
+ export declare const sanitizeInputForLogging: (input: unknown) => unknown;
175
+ declare const _default: {
176
+ sanitization: Sanitization;
177
+ sanitizeInput: {
178
+ /**
179
+ * Remove potentially dangerous characters from strings based on context
180
+ * @param input String to sanitize
181
+ * @param options Sanitization options for context-specific handling
182
+ * @returns Sanitized string
183
+ */
184
+ string: (input: string, options?: SanitizeStringOptions) => string;
185
+ /**
186
+ * Sanitize HTML to prevent XSS
187
+ * @param input HTML string to sanitize
188
+ * @param config Optional custom sanitization config
189
+ * @returns Sanitized HTML
190
+ */
191
+ html: (input: string, config?: HtmlSanitizeConfig) => string;
192
+ /**
193
+ * Sanitize URLs
194
+ * @param input URL to sanitize
195
+ * @param allowedProtocols Allowed URL protocols
196
+ * @returns Sanitized URL
197
+ */
198
+ url: (input: string, allowedProtocols?: string[]) => string;
199
+ /**
200
+ * Sanitize file paths to prevent path traversal attacks
201
+ * @param input Path to sanitize
202
+ * @param options Options for path sanitization
203
+ * @returns Sanitized and normalized path
204
+ */
205
+ path: (input: string, options?: PathSanitizeOptions) => string;
206
+ /**
207
+ * Sanitize a JSON string
208
+ * @param input JSON string to sanitize
209
+ * @param maxSize Maximum allowed size in bytes
210
+ * @returns Parsed and sanitized object
211
+ */
212
+ json: <T = unknown>(input: string, maxSize?: number) => T;
213
+ /**
214
+ * Ensure input is within a numeric range
215
+ * @param input Number to validate
216
+ * @param min Minimum allowed value
217
+ * @param max Maximum allowed value
218
+ * @returns Sanitized number within range
219
+ */
220
+ number: (input: number | string, min?: number, max?: number) => number;
221
+ };
222
+ sanitizeInputForLogging: (input: unknown) => unknown;
223
+ };
224
+ export default _default;
@@ -0,0 +1,367 @@
1
+ import path from 'path';
2
+ import normalize from 'path-normalize';
3
+ import sanitizeHtml from 'sanitize-html';
4
+ import validator from 'validator';
5
+ import * as xssFilters from 'xss-filters';
6
+ import { BaseErrorCode, McpError } from '../types-global/errors.js';
7
+ import { logger } from './logger.js';
8
+ /**
9
+ * Sanitization class for handling various input sanitization tasks
10
+ */
11
+ export class Sanitization {
12
+ /**
13
+ * Private constructor to enforce singleton pattern
14
+ */
15
+ constructor() {
16
+ /** Default list of sensitive fields for sanitizing logs */
17
+ this.sensitiveFields = [
18
+ 'password', 'token', 'secret', 'key', 'apiKey', 'auth',
19
+ 'credential', 'jwt', 'ssn', 'credit', 'card', 'cvv', 'authorization'
20
+ ];
21
+ /** Default sanitize-html configuration */
22
+ this.defaultHtmlSanitizeConfig = {
23
+ allowedTags: [
24
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'ul', 'ol',
25
+ 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br',
26
+ 'div', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'pre'
27
+ ],
28
+ allowedAttributes: {
29
+ 'a': ['href', 'name', 'target'],
30
+ 'img': ['src', 'alt', 'title', 'width', 'height'],
31
+ '*': ['class', 'id', 'style']
32
+ },
33
+ preserveComments: false
34
+ };
35
+ logger.debug('Sanitization service initialized with modern libraries');
36
+ }
37
+ /**
38
+ * Get the singleton Sanitization instance
39
+ * @returns Sanitization instance
40
+ */
41
+ static getInstance() {
42
+ if (!Sanitization.instance) {
43
+ Sanitization.instance = new Sanitization();
44
+ }
45
+ return Sanitization.instance;
46
+ }
47
+ /**
48
+ * Set sensitive fields for log sanitization
49
+ * @param fields Array of field names to consider sensitive
50
+ */
51
+ setSensitiveFields(fields) {
52
+ this.sensitiveFields = [...this.sensitiveFields, ...fields];
53
+ logger.debug('Updated sensitive fields list', { count: this.sensitiveFields.length });
54
+ }
55
+ /**
56
+ * Get the current list of sensitive fields
57
+ * @returns Array of sensitive field names
58
+ */
59
+ getSensitiveFields() {
60
+ return [...this.sensitiveFields];
61
+ }
62
+ /**
63
+ * Sanitize HTML content using sanitize-html library
64
+ * @param input HTML string to sanitize
65
+ * @param config Optional custom sanitization config
66
+ * @returns Sanitized HTML
67
+ */
68
+ sanitizeHtml(input, config) {
69
+ if (!input)
70
+ return '';
71
+ // Create sanitize-html options from our config
72
+ const options = {
73
+ allowedTags: config?.allowedTags || this.defaultHtmlSanitizeConfig.allowedTags,
74
+ allowedAttributes: config?.allowedAttributes || this.defaultHtmlSanitizeConfig.allowedAttributes,
75
+ transformTags: config?.transformTags
76
+ };
77
+ // Handle comments - if preserveComments is true, add '!--' to allowedTags
78
+ if (config?.preserveComments || this.defaultHtmlSanitizeConfig.preserveComments) {
79
+ options.allowedTags = [...(options.allowedTags || []), '!--'];
80
+ }
81
+ return sanitizeHtml(input, options);
82
+ }
83
+ /**
84
+ * Sanitize string input based on context
85
+ * @param input String to sanitize
86
+ * @param options Sanitization options
87
+ * @returns Sanitized string
88
+ */
89
+ sanitizeString(input, options = {}) {
90
+ if (!input)
91
+ return '';
92
+ // Handle based on context
93
+ switch (options.context) {
94
+ case 'html':
95
+ // Use sanitize-html with custom options
96
+ return this.sanitizeHtml(input, {
97
+ allowedTags: options.allowedTags,
98
+ allowedAttributes: options.allowedAttributes ?
99
+ this.convertAttributesFormat(options.allowedAttributes) :
100
+ undefined
101
+ });
102
+ case 'attribute':
103
+ // Use xss-filters for HTML attributes
104
+ return xssFilters.inHTMLData(input);
105
+ case 'url':
106
+ // Validate and sanitize URL
107
+ if (!validator.isURL(input, {
108
+ protocols: ['http', 'https'],
109
+ require_protocol: true
110
+ })) {
111
+ return '';
112
+ }
113
+ return validator.trim(input);
114
+ case 'javascript':
115
+ // Reject any attempt to sanitize JavaScript
116
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'JavaScript sanitization not supported through string sanitizer', { input: input.substring(0, 50) });
117
+ case 'text':
118
+ default:
119
+ // Use XSS filters for basic text
120
+ return xssFilters.inHTMLData(input);
121
+ }
122
+ }
123
+ /**
124
+ * Sanitize URL with robust validation and sanitization
125
+ * @param input URL to sanitize
126
+ * @param allowedProtocols Allowed URL protocols
127
+ * @returns Sanitized URL
128
+ */
129
+ sanitizeUrl(input, allowedProtocols = ['http', 'https']) {
130
+ try {
131
+ // First validate the URL format
132
+ if (!validator.isURL(input, {
133
+ protocols: allowedProtocols,
134
+ require_protocol: true
135
+ })) {
136
+ throw new Error('Invalid URL format');
137
+ }
138
+ // Double-check no javascript: protocol sneaked in
139
+ if (input.toLowerCase().includes('javascript:')) {
140
+ throw new Error('JavaScript protocol not allowed');
141
+ }
142
+ // Sanitize and return
143
+ return validator.trim(xssFilters.uriInHTMLData(input));
144
+ }
145
+ catch (error) {
146
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid URL format', { input, error: error instanceof Error ? error.message : String(error) });
147
+ }
148
+ }
149
+ /**
150
+ * Sanitize file paths to prevent path traversal attacks
151
+ * @param input Path to sanitize
152
+ * @param options Options for path sanitization
153
+ * @returns Sanitized and normalized path
154
+ */
155
+ sanitizePath(input, options = {}) {
156
+ try {
157
+ if (!input) {
158
+ throw new Error('Empty path');
159
+ }
160
+ // Apply path normalization (resolves '..' and '.' segments properly)
161
+ let normalized = normalize(input);
162
+ // Convert backslashes to forward slashes if toPosix is true
163
+ if (options.toPosix) {
164
+ normalized = normalized.replace(/\\/g, '/');
165
+ }
166
+ // Handle absolute paths based on allowAbsolute option
167
+ if (!options.allowAbsolute && path.isAbsolute(normalized)) {
168
+ // Remove leading slash or drive letter to make it relative
169
+ normalized = normalized.replace(/^(?:[A-Za-z]:)?[/\\]/, '');
170
+ }
171
+ // If rootDir is specified, ensure the path doesn't escape it
172
+ if (options.rootDir) {
173
+ const rootDir = path.resolve(options.rootDir);
174
+ // Resolve the normalized path against the root dir
175
+ const fullPath = path.resolve(rootDir, normalized);
176
+ // More robust check for path traversal
177
+ if (!fullPath.startsWith(rootDir + path.sep) && fullPath !== rootDir) {
178
+ throw new Error('Path traversal detected');
179
+ }
180
+ // Return the path relative to the root
181
+ return path.relative(rootDir, fullPath);
182
+ }
183
+ // Final validation - ensure the path doesn't contain suspicious patterns
184
+ if (normalized.includes('\0') || normalized.match(/\\\\[.?]|\.\.\\/)) {
185
+ throw new Error('Invalid path characters detected');
186
+ }
187
+ return normalized;
188
+ }
189
+ catch (error) {
190
+ // Log the error for debugging
191
+ logger.warn('Path sanitization error', {
192
+ input,
193
+ error: error instanceof Error ? error.message : String(error)
194
+ });
195
+ // Return a safe default in case of errors
196
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid or unsafe path', { input });
197
+ }
198
+ }
199
+ /**
200
+ * Sanitize a JSON string
201
+ * @param input JSON string to sanitize
202
+ * @param maxSize Maximum allowed size in bytes
203
+ * @returns Parsed and sanitized object
204
+ */
205
+ sanitizeJson(input, maxSize) {
206
+ try {
207
+ // Check size limit if specified
208
+ if (maxSize !== undefined && input.length > maxSize) {
209
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `JSON exceeds maximum allowed size of ${maxSize} bytes`, { size: input.length, maxSize });
210
+ }
211
+ // Validate JSON format
212
+ if (!validator.isJSON(input)) {
213
+ throw new Error('Invalid JSON format');
214
+ }
215
+ return JSON.parse(input);
216
+ }
217
+ catch (error) {
218
+ if (error instanceof McpError) {
219
+ throw error;
220
+ }
221
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid JSON format', { input: input.length > 100 ? `${input.substring(0, 100)}...` : input });
222
+ }
223
+ }
224
+ /**
225
+ * Ensure input is within a numeric range
226
+ * @param input Number to validate
227
+ * @param min Minimum allowed value
228
+ * @param max Maximum allowed value
229
+ * @returns Sanitized number within range
230
+ */
231
+ sanitizeNumber(input, min, max) {
232
+ let value;
233
+ // Handle string input
234
+ if (typeof input === 'string') {
235
+ if (!validator.isNumeric(input)) {
236
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid number format', { input });
237
+ }
238
+ value = parseFloat(input);
239
+ }
240
+ else {
241
+ value = input;
242
+ }
243
+ if (isNaN(value)) {
244
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, 'Invalid number format', { input });
245
+ }
246
+ if (min !== undefined && value < min) {
247
+ value = min;
248
+ }
249
+ if (max !== undefined && value > max) {
250
+ value = max;
251
+ }
252
+ return value;
253
+ }
254
+ /**
255
+ * Sanitize input for logging to protect sensitive information
256
+ * @param input Input to sanitize
257
+ * @returns Sanitized input safe for logging
258
+ */
259
+ sanitizeForLogging(input) {
260
+ if (!input || typeof input !== 'object') {
261
+ return input;
262
+ }
263
+ // Create a deep copy to avoid modifying the original
264
+ const sanitized = Array.isArray(input)
265
+ ? [...input]
266
+ : { ...input };
267
+ // Recursively sanitize the object
268
+ this.redactSensitiveFields(sanitized);
269
+ return sanitized;
270
+ }
271
+ /**
272
+ * Private helper to convert attribute format from record to sanitize-html format
273
+ */
274
+ convertAttributesFormat(attrs) {
275
+ const result = {};
276
+ for (const [tag, attributes] of Object.entries(attrs)) {
277
+ result[tag] = attributes;
278
+ }
279
+ return result;
280
+ }
281
+ /**
282
+ * Recursively redact sensitive fields in an object
283
+ */
284
+ redactSensitiveFields(obj) {
285
+ if (!obj || typeof obj !== 'object') {
286
+ return;
287
+ }
288
+ // Handle arrays
289
+ if (Array.isArray(obj)) {
290
+ obj.forEach(item => this.redactSensitiveFields(item));
291
+ return;
292
+ }
293
+ // Handle regular objects
294
+ for (const [key, value] of Object.entries(obj)) {
295
+ // Check if this key matches any sensitive field pattern
296
+ const isSensitive = this.sensitiveFields.some(field => key.toLowerCase().includes(field.toLowerCase()));
297
+ if (isSensitive) {
298
+ // Mask sensitive value
299
+ obj[key] = '[REDACTED]';
300
+ }
301
+ else if (value && typeof value === 'object') {
302
+ // Recursively process nested objects
303
+ this.redactSensitiveFields(value);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ // Create and export singleton instance
309
+ export const sanitization = Sanitization.getInstance();
310
+ // Export the input sanitization object with convenience functions
311
+ export const sanitizeInput = {
312
+ /**
313
+ * Remove potentially dangerous characters from strings based on context
314
+ * @param input String to sanitize
315
+ * @param options Sanitization options for context-specific handling
316
+ * @returns Sanitized string
317
+ */
318
+ string: (input, options = {}) => sanitization.sanitizeString(input, options),
319
+ /**
320
+ * Sanitize HTML to prevent XSS
321
+ * @param input HTML string to sanitize
322
+ * @param config Optional custom sanitization config
323
+ * @returns Sanitized HTML
324
+ */
325
+ html: (input, config) => sanitization.sanitizeHtml(input, config),
326
+ /**
327
+ * Sanitize URLs
328
+ * @param input URL to sanitize
329
+ * @param allowedProtocols Allowed URL protocols
330
+ * @returns Sanitized URL
331
+ */
332
+ url: (input, allowedProtocols = ['http', 'https']) => sanitization.sanitizeUrl(input, allowedProtocols),
333
+ /**
334
+ * Sanitize file paths to prevent path traversal attacks
335
+ * @param input Path to sanitize
336
+ * @param options Options for path sanitization
337
+ * @returns Sanitized and normalized path
338
+ */
339
+ path: (input, options = {}) => sanitization.sanitizePath(input, options),
340
+ /**
341
+ * Sanitize a JSON string
342
+ * @param input JSON string to sanitize
343
+ * @param maxSize Maximum allowed size in bytes
344
+ * @returns Parsed and sanitized object
345
+ */
346
+ json: (input, maxSize) => sanitization.sanitizeJson(input, maxSize),
347
+ /**
348
+ * Ensure input is within a numeric range
349
+ * @param input Number to validate
350
+ * @param min Minimum allowed value
351
+ * @param max Maximum allowed value
352
+ * @returns Sanitized number within range
353
+ */
354
+ number: (input, min, max) => sanitization.sanitizeNumber(input, min, max)
355
+ };
356
+ /**
357
+ * Sanitize input for logging to protect sensitive information
358
+ * @param input Input to sanitize
359
+ * @returns Sanitized input safe for logging
360
+ */
361
+ export const sanitizeInputForLogging = (input) => sanitization.sanitizeForLogging(input);
362
+ // Export default
363
+ export default {
364
+ sanitization,
365
+ sanitizeInput,
366
+ sanitizeInputForLogging
367
+ };