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,85 @@
1
+ import { NtfyMessage, NtfySubscriptionOptions } from './types.js';
2
+ /**
3
+ * Validates a topic name
4
+ * @param topic Topic name to validate
5
+ * @returns True if the topic name is valid, false otherwise
6
+ */
7
+ export declare function isValidTopic(topic: string): Promise<boolean>;
8
+ /**
9
+ * Validate a topic name synchronously
10
+ * This is a synchronous version for performance and cases where async isn't possible
11
+ * @param topic Topic to validate
12
+ * @returns True if topic is valid
13
+ */
14
+ export declare function validateTopicSync(topic: string): boolean;
15
+ /**
16
+ * Builds a ntfy subscription URL
17
+ * @param topic Topic to subscribe to (can be comma-separated for multiple topics)
18
+ * @param format Subscription format (json, sse, raw, ws)
19
+ * @param options Subscription options
20
+ * @returns Complete URL for the subscription
21
+ */
22
+ export declare function buildSubscriptionUrl(topic: string, format: string, options: NtfySubscriptionOptions): Promise<string>;
23
+ /**
24
+ * Builds a subscription URL synchronously
25
+ * @param topic Topic to subscribe to
26
+ * @param format Subscription format
27
+ * @param options Subscription options
28
+ * @returns Complete URL
29
+ */
30
+ export declare function buildSubscriptionUrlSync(topic: string, format: string, options: NtfySubscriptionOptions): string;
31
+ /**
32
+ * Creates authorization header for basic auth
33
+ * @param username Username
34
+ * @param password Password
35
+ * @returns Basic auth header value
36
+ */
37
+ export declare function createBasicAuthHeader(username: string, password: string): Promise<string>;
38
+ /**
39
+ * Creates basic auth header synchronously
40
+ * @param username Username
41
+ * @param password Password
42
+ * @returns Basic auth header value
43
+ */
44
+ export declare function createBasicAuthHeaderSync(username: string, password: string): string;
45
+ /**
46
+ * Parses a JSON message from ntfy
47
+ * @param data JSON string to parse
48
+ * @returns Parsed ntfy message
49
+ * @throws NtfyParseError if the message cannot be parsed
50
+ */
51
+ export declare function parseJsonMessage(data: string): Promise<NtfyMessage>;
52
+ /**
53
+ * Parse JSON message synchronously
54
+ * @param data JSON string to parse
55
+ * @returns Parsed ntfy message
56
+ * @throws NtfyParseError if parsing fails
57
+ */
58
+ export declare function parseJsonMessageSync(data: string): NtfyMessage;
59
+ /**
60
+ * Creates request headers for ntfy API calls
61
+ * @param options Subscription options
62
+ * @returns Headers object for fetch
63
+ */
64
+ export declare function createRequestHeaders(options: NtfySubscriptionOptions): Promise<HeadersInit>;
65
+ /**
66
+ * Create request headers synchronously
67
+ * @param options Subscription options
68
+ * @returns Headers object
69
+ */
70
+ export declare function createRequestHeadersSync(options: NtfySubscriptionOptions): HeadersInit;
71
+ /**
72
+ * Generates a timeout promise that rejects after the specified time
73
+ * @param ms Timeout in milliseconds
74
+ * @returns Promise that rejects after the specified time
75
+ */
76
+ export declare function createTimeout(ms: number): Promise<never>;
77
+ /**
78
+ * Creates an AbortController with a timeout
79
+ * @param timeoutMs Timeout in milliseconds
80
+ * @returns AbortController and a cleanup function
81
+ */
82
+ export declare function createAbortControllerWithTimeout(timeoutMs: number): {
83
+ controller: AbortController;
84
+ cleanup: () => void;
85
+ };
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Utility functions for the ntfy service
3
+ */
4
+ import { BaseErrorCode } from '../../types-global/errors.js';
5
+ import { ErrorHandler } from '../../utils/errorHandler.js';
6
+ import { logger } from '../../utils/logger.js';
7
+ import { sanitizeInput } from '../../utils/sanitization.js';
8
+ import { createRequestContext } from '../../utils/requestContext.js';
9
+ import { idGenerator } from '../../utils/idGenerator.js';
10
+ import { DEFAULT_NTFY_BASE_URL } from './constants.js';
11
+ import { NtfyParseError, ntfyErrorMapper } from './errors.js';
12
+ // Create a module-specific logger
13
+ const moduleLogger = logger.createChildLogger({
14
+ module: 'NtfyUtils',
15
+ serviceId: idGenerator.generateRandomString(8)
16
+ });
17
+ /**
18
+ * Validates a topic name
19
+ * @param topic Topic name to validate
20
+ * @returns True if the topic name is valid, false otherwise
21
+ */
22
+ export async function isValidTopic(topic) {
23
+ return ErrorHandler.tryCatch(async () => {
24
+ // Topic names are validated on the server side, but we can do basic validation here
25
+ if (!topic)
26
+ return false;
27
+ const sanitizedTopic = sanitizeInput.string(topic);
28
+ return sanitizedTopic.trim().length > 0 &&
29
+ !sanitizedTopic.includes('\n') &&
30
+ !sanitizedTopic.includes('\r');
31
+ }, {
32
+ operation: 'validateNtfyTopic',
33
+ context: { topic },
34
+ errorCode: BaseErrorCode.VALIDATION_ERROR,
35
+ rethrow: false,
36
+ // Return false on error rather than throwing
37
+ errorMapper: () => false
38
+ });
39
+ }
40
+ /**
41
+ * Validate a topic name synchronously
42
+ * This is a synchronous version for performance and cases where async isn't possible
43
+ * @param topic Topic to validate
44
+ * @returns True if topic is valid
45
+ */
46
+ export function validateTopicSync(topic) {
47
+ try {
48
+ if (!topic)
49
+ return false;
50
+ const sanitizedTopic = sanitizeInput.string(topic);
51
+ return sanitizedTopic.trim().length > 0 &&
52
+ !sanitizedTopic.includes('\n') &&
53
+ !sanitizedTopic.includes('\r');
54
+ }
55
+ catch (error) {
56
+ moduleLogger.warn('Error validating topic', { topic, error });
57
+ return false;
58
+ }
59
+ }
60
+ /**
61
+ * Builds a ntfy subscription URL
62
+ * @param topic Topic to subscribe to (can be comma-separated for multiple topics)
63
+ * @param format Subscription format (json, sse, raw, ws)
64
+ * @param options Subscription options
65
+ * @returns Complete URL for the subscription
66
+ */
67
+ export async function buildSubscriptionUrl(topic, format, options) {
68
+ return ErrorHandler.tryCatch(async () => {
69
+ const requestCtx = createRequestContext({
70
+ operation: 'buildSubscriptionUrl',
71
+ topic,
72
+ format
73
+ });
74
+ // Sanitize inputs
75
+ const sanitizedTopic = sanitizeInput.string(topic);
76
+ const sanitizedFormat = sanitizeInput.string(format);
77
+ moduleLogger.debug('Building subscription URL', {
78
+ topic: sanitizedTopic,
79
+ format: sanitizedFormat,
80
+ requestId: requestCtx.requestId
81
+ });
82
+ const baseUrl = sanitizeInput.url(options.baseUrl || DEFAULT_NTFY_BASE_URL);
83
+ const endpoint = `/${sanitizedTopic}/${sanitizedFormat}`;
84
+ // Build query parameters
85
+ const params = new URLSearchParams();
86
+ if (options.poll) {
87
+ params.append('poll', '1');
88
+ }
89
+ if (options.since) {
90
+ params.append('since', options.since.toString());
91
+ }
92
+ if (options.scheduled) {
93
+ params.append('scheduled', '1');
94
+ }
95
+ if (options.id) {
96
+ params.append('id', sanitizeInput.string(options.id));
97
+ }
98
+ if (options.message) {
99
+ params.append('message', sanitizeInput.string(options.message));
100
+ }
101
+ if (options.title) {
102
+ params.append('title', sanitizeInput.string(options.title));
103
+ }
104
+ if (options.priority) {
105
+ params.append('priority', sanitizeInput.string(options.priority.toString()));
106
+ }
107
+ if (options.tags) {
108
+ params.append('tags', sanitizeInput.string(options.tags));
109
+ }
110
+ if (options.auth) {
111
+ params.append('auth', sanitizeInput.string(options.auth));
112
+ }
113
+ const queryString = params.toString();
114
+ const fullUrl = `${baseUrl}${endpoint}${queryString ? `?${queryString}` : ''}`;
115
+ moduleLogger.debug('Built subscription URL', {
116
+ url: fullUrl,
117
+ requestId: requestCtx.requestId
118
+ });
119
+ return fullUrl;
120
+ }, {
121
+ operation: 'buildSubscriptionUrl',
122
+ context: { topic, format },
123
+ input: options,
124
+ errorCode: BaseErrorCode.VALIDATION_ERROR,
125
+ errorMapper: ntfyErrorMapper,
126
+ rethrow: true
127
+ });
128
+ }
129
+ /**
130
+ * Builds a subscription URL synchronously
131
+ * @param topic Topic to subscribe to
132
+ * @param format Subscription format
133
+ * @param options Subscription options
134
+ * @returns Complete URL
135
+ */
136
+ export function buildSubscriptionUrlSync(topic, format, options) {
137
+ try {
138
+ // Sanitize inputs
139
+ const sanitizedTopic = sanitizeInput.string(topic);
140
+ const sanitizedFormat = sanitizeInput.string(format);
141
+ const baseUrl = sanitizeInput.url(options.baseUrl || DEFAULT_NTFY_BASE_URL);
142
+ const endpoint = `/${sanitizedTopic}/${sanitizedFormat}`;
143
+ // Build query parameters
144
+ const params = new URLSearchParams();
145
+ if (options.poll) {
146
+ params.append('poll', '1');
147
+ }
148
+ if (options.since) {
149
+ params.append('since', options.since.toString());
150
+ }
151
+ if (options.scheduled) {
152
+ params.append('scheduled', '1');
153
+ }
154
+ if (options.id) {
155
+ params.append('id', sanitizeInput.string(options.id));
156
+ }
157
+ if (options.message) {
158
+ params.append('message', sanitizeInput.string(options.message));
159
+ }
160
+ if (options.title) {
161
+ params.append('title', sanitizeInput.string(options.title));
162
+ }
163
+ if (options.priority) {
164
+ params.append('priority', sanitizeInput.string(options.priority.toString()));
165
+ }
166
+ if (options.tags) {
167
+ params.append('tags', sanitizeInput.string(options.tags));
168
+ }
169
+ if (options.auth) {
170
+ params.append('auth', sanitizeInput.string(options.auth));
171
+ }
172
+ const queryString = params.toString();
173
+ return `${baseUrl}${endpoint}${queryString ? `?${queryString}` : ''}`;
174
+ }
175
+ catch (error) {
176
+ moduleLogger.error('Error building subscription URL', { topic, format, error });
177
+ throw error;
178
+ }
179
+ }
180
+ /**
181
+ * Creates authorization header for basic auth
182
+ * @param username Username
183
+ * @param password Password
184
+ * @returns Basic auth header value
185
+ */
186
+ export async function createBasicAuthHeader(username, password) {
187
+ return ErrorHandler.tryCatch(async () => {
188
+ const requestCtx = createRequestContext({
189
+ operation: 'createBasicAuthHeader',
190
+ hasCredentials: !!username && !!password
191
+ });
192
+ if (!username || !password) {
193
+ moduleLogger.warn('Missing username or password for basic auth', {
194
+ requestId: requestCtx.requestId,
195
+ hasUsername: !!username
196
+ });
197
+ return '';
198
+ }
199
+ // Sanitize credentials
200
+ const sanitizedUsername = sanitizeInput.string(username);
201
+ // Don't log or sanitize password directly to avoid potential leaks
202
+ // Use btoa for base64 encoding (available in Node.js and browsers)
203
+ return `Basic ${btoa(`${sanitizedUsername}:${password}`)}`;
204
+ }, {
205
+ operation: 'createBasicAuthHeader',
206
+ errorCode: BaseErrorCode.VALIDATION_ERROR,
207
+ // Don't include username/password in logs
208
+ rethrow: false,
209
+ // Return empty string on error rather than throwing
210
+ errorMapper: () => ''
211
+ });
212
+ }
213
+ /**
214
+ * Creates basic auth header synchronously
215
+ * @param username Username
216
+ * @param password Password
217
+ * @returns Basic auth header value
218
+ */
219
+ export function createBasicAuthHeaderSync(username, password) {
220
+ try {
221
+ if (!username || !password) {
222
+ return '';
223
+ }
224
+ // Sanitize credentials
225
+ const sanitizedUsername = sanitizeInput.string(username);
226
+ // Use btoa for base64 encoding
227
+ return `Basic ${btoa(`${sanitizedUsername}:${password}`)}`;
228
+ }
229
+ catch (error) {
230
+ moduleLogger.warn('Error creating basic auth header', { error });
231
+ return '';
232
+ }
233
+ }
234
+ /**
235
+ * Parses a JSON message from ntfy
236
+ * @param data JSON string to parse
237
+ * @returns Parsed ntfy message
238
+ * @throws NtfyParseError if the message cannot be parsed
239
+ */
240
+ export async function parseJsonMessage(data) {
241
+ return ErrorHandler.tryCatch(async () => {
242
+ if (!data || typeof data !== 'string') {
243
+ throw new Error('Invalid input: data must be a non-empty string');
244
+ }
245
+ const message = JSON.parse(data);
246
+ // Basic validation to ensure it's a proper ntfy message
247
+ if (!message.id || !message.time || !message.event || !message.topic) {
248
+ throw new Error('Invalid message format');
249
+ }
250
+ return message;
251
+ }, {
252
+ operation: 'parseJsonMessage',
253
+ context: { dataLength: data?.length ?? 0 },
254
+ input: { data: data?.length > 100 ? `${data.substring(0, 100)}...` : data },
255
+ errorCode: BaseErrorCode.VALIDATION_ERROR,
256
+ errorMapper: (error) => {
257
+ // Transform the error to our NtfyParseError
258
+ return new NtfyParseError(`Failed to parse message: ${error instanceof Error ? error.message : 'Unknown error'}`, data);
259
+ },
260
+ rethrow: true
261
+ });
262
+ }
263
+ /**
264
+ * Parse JSON message synchronously
265
+ * @param data JSON string to parse
266
+ * @returns Parsed ntfy message
267
+ * @throws NtfyParseError if parsing fails
268
+ */
269
+ export function parseJsonMessageSync(data) {
270
+ try {
271
+ if (!data || typeof data !== 'string') {
272
+ throw new Error('Invalid input: data must be a non-empty string');
273
+ }
274
+ const message = JSON.parse(data);
275
+ // Basic validation to ensure it's a proper ntfy message
276
+ if (!message.id || !message.time || !message.event || !message.topic) {
277
+ throw new Error('Invalid message format');
278
+ }
279
+ return message;
280
+ }
281
+ catch (error) {
282
+ throw new NtfyParseError(`Failed to parse message: ${error instanceof Error ? error.message : 'Unknown error'}`, data);
283
+ }
284
+ }
285
+ /**
286
+ * Creates request headers for ntfy API calls
287
+ * @param options Subscription options
288
+ * @returns Headers object for fetch
289
+ */
290
+ export async function createRequestHeaders(options) {
291
+ return ErrorHandler.tryCatch(async () => {
292
+ const requestCtx = createRequestContext({
293
+ operation: 'createRequestHeaders'
294
+ });
295
+ moduleLogger.debug('Creating request headers', {
296
+ requestId: requestCtx.requestId,
297
+ hasAuth: !!options.auth || !!(options.username && options.password),
298
+ hasCustomHeaders: !!options.headers && Object.keys(options.headers).length > 0
299
+ });
300
+ const headers = {
301
+ 'Accept': 'application/json',
302
+ 'User-Agent': 'ntfy-mcp-server/1.0.0',
303
+ };
304
+ // Add custom headers if provided (after sanitization)
305
+ if (options.headers) {
306
+ Object.entries(options.headers).forEach(([key, value]) => {
307
+ headers[sanitizeInput.string(key)] = sanitizeInput.string(value);
308
+ });
309
+ }
310
+ // Add authorization header if credentials are provided
311
+ if (options.username && options.password) {
312
+ const authHeader = await createBasicAuthHeader(options.username, options.password);
313
+ headers['Authorization'] = authHeader;
314
+ }
315
+ else if (options.auth && !options.auth.includes('=')) {
316
+ // Check if the auth token is an ntfy API key (starts with tk_)
317
+ if (options.auth.startsWith('tk_')) {
318
+ // Format as Bearer token for ntfy API key
319
+ headers['Authorization'] = `Bearer ${sanitizeInput.string(options.auth)}`;
320
+ }
321
+ else {
322
+ headers['Authorization'] = sanitizeInput.string(options.auth);
323
+ }
324
+ }
325
+ return headers;
326
+ }, {
327
+ operation: 'createRequestHeaders',
328
+ rethrow: false,
329
+ // Return minimal headers on error rather than breaking calls
330
+ errorMapper: () => ({
331
+ 'Accept': 'application/json',
332
+ 'User-Agent': 'ntfy-mcp-server/1.0.0',
333
+ })
334
+ });
335
+ }
336
+ /**
337
+ * Create request headers synchronously
338
+ * @param options Subscription options
339
+ * @returns Headers object
340
+ */
341
+ export function createRequestHeadersSync(options) {
342
+ try {
343
+ const headers = {
344
+ 'Accept': 'application/json',
345
+ 'User-Agent': 'ntfy-mcp-server/1.0.0',
346
+ };
347
+ // Add custom headers if provided (after sanitization)
348
+ if (options.headers) {
349
+ Object.entries(options.headers).forEach(([key, value]) => {
350
+ headers[sanitizeInput.string(key)] = sanitizeInput.string(value);
351
+ });
352
+ }
353
+ // Add authorization header if credentials are provided
354
+ if (options.username && options.password) {
355
+ headers['Authorization'] = createBasicAuthHeaderSync(options.username, options.password);
356
+ }
357
+ else if (options.auth && !options.auth.includes('=')) {
358
+ // Check if the auth token is an ntfy API key (starts with tk_)
359
+ if (options.auth.startsWith('tk_')) {
360
+ // Format as Bearer token for ntfy API key
361
+ headers['Authorization'] = `Bearer ${sanitizeInput.string(options.auth)}`;
362
+ }
363
+ else {
364
+ headers['Authorization'] = sanitizeInput.string(options.auth);
365
+ }
366
+ }
367
+ return headers;
368
+ }
369
+ catch (error) {
370
+ moduleLogger.error('Error creating request headers', { error });
371
+ // Return minimal headers on error
372
+ return {
373
+ 'Accept': 'application/json',
374
+ 'User-Agent': 'ntfy-mcp-server/1.0.0',
375
+ };
376
+ }
377
+ }
378
+ /**
379
+ * Generates a timeout promise that rejects after the specified time
380
+ * @param ms Timeout in milliseconds
381
+ * @returns Promise that rejects after the specified time
382
+ */
383
+ export function createTimeout(ms) {
384
+ const timeoutId = createRequestContext({ operation: 'createTimeout', timeoutMs: ms }).requestId;
385
+ moduleLogger.debug('Creating timeout promise', { timeoutMs: ms, timeoutId });
386
+ return new Promise((_, reject) => {
387
+ setTimeout(() => {
388
+ moduleLogger.debug('Timeout reached', { timeoutMs: ms, timeoutId });
389
+ reject(new Error(`Operation timed out after ${ms}ms`));
390
+ }, ms);
391
+ });
392
+ }
393
+ /**
394
+ * Creates an AbortController with a timeout
395
+ * @param timeoutMs Timeout in milliseconds
396
+ * @returns AbortController and a cleanup function
397
+ */
398
+ export function createAbortControllerWithTimeout(timeoutMs) {
399
+ const controlId = createRequestContext({ operation: 'createAbortController', timeoutMs }).requestId;
400
+ moduleLogger.debug('Creating AbortController with timeout', { timeoutMs, controlId });
401
+ const controller = new AbortController();
402
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
403
+ return {
404
+ controller,
405
+ cleanup: () => {
406
+ clearTimeout(timeoutId);
407
+ moduleLogger.debug('Cleaned up AbortController timeout', { controlId });
408
+ },
409
+ };
410
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { McpToolResponse } from "./mcp.js";
3
+ export declare const BaseErrorCode: {
4
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
5
+ readonly FORBIDDEN: "FORBIDDEN";
6
+ readonly NOT_FOUND: "NOT_FOUND";
7
+ readonly CONFLICT: "CONFLICT";
8
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
9
+ readonly RATE_LIMITED: "RATE_LIMITED";
10
+ readonly TIMEOUT: "TIMEOUT";
11
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
12
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
13
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
14
+ };
15
+ export type BaseErrorCode = typeof BaseErrorCode[keyof typeof BaseErrorCode];
16
+ export declare class McpError extends Error {
17
+ code: BaseErrorCode;
18
+ details?: Record<string, unknown> | undefined;
19
+ constructor(code: BaseErrorCode, message: string, details?: Record<string, unknown> | undefined);
20
+ toResponse(): McpToolResponse;
21
+ }
22
+ export declare const ErrorSchema: z.ZodObject<{
23
+ code: z.ZodString;
24
+ message: z.ZodString;
25
+ details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ message: string;
28
+ code: string;
29
+ details?: Record<string, unknown> | undefined;
30
+ }, {
31
+ message: string;
32
+ code: string;
33
+ details?: Record<string, unknown> | undefined;
34
+ }>;
35
+ export type ErrorResponse = z.infer<typeof ErrorSchema>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ // Base error codes that all tools can use
3
+ export const BaseErrorCode = {
4
+ UNAUTHORIZED: 'UNAUTHORIZED',
5
+ FORBIDDEN: 'FORBIDDEN',
6
+ NOT_FOUND: 'NOT_FOUND',
7
+ CONFLICT: 'CONFLICT',
8
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
9
+ RATE_LIMITED: 'RATE_LIMITED',
10
+ TIMEOUT: 'TIMEOUT',
11
+ SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
12
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
13
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR'
14
+ };
15
+ // Base MCP error class
16
+ export class McpError extends Error {
17
+ constructor(code, message, details) {
18
+ super(message);
19
+ this.code = code;
20
+ this.details = details;
21
+ this.name = 'McpError';
22
+ }
23
+ toResponse() {
24
+ const content = {
25
+ type: "text",
26
+ text: `Error [${this.code}]: ${this.message}${this.details ? `\nDetails: ${JSON.stringify(this.details, null, 2)}` : ''}`
27
+ };
28
+ return {
29
+ content: [content],
30
+ isError: true
31
+ };
32
+ }
33
+ }
34
+ // Error schema for validation
35
+ export const ErrorSchema = z.object({
36
+ code: z.string(),
37
+ message: z.string(),
38
+ details: z.record(z.unknown()).optional()
39
+ });
@@ -0,0 +1,30 @@
1
+ export interface McpContent {
2
+ type: "text";
3
+ text: string;
4
+ }
5
+ export interface McpToolResponse {
6
+ content: McpContent[];
7
+ isError?: boolean;
8
+ }
9
+ export interface ResourceContent {
10
+ uri: string;
11
+ text: string;
12
+ mimeType?: string;
13
+ }
14
+ export interface ResourceResponse {
15
+ contents: ResourceContent[];
16
+ }
17
+ export interface PromptMessageContent {
18
+ type: "text";
19
+ text: string;
20
+ }
21
+ export interface PromptMessage {
22
+ role: "user" | "assistant";
23
+ content: PromptMessageContent;
24
+ }
25
+ export interface PromptResponse {
26
+ messages: PromptMessage[];
27
+ }
28
+ export declare const createToolResponse: (text: string, isError?: boolean) => McpToolResponse;
29
+ export declare const createResourceResponse: (uri: string, text: string, mimeType?: string) => ResourceResponse;
30
+ export declare const createPromptResponse: (text: string, role?: "user" | "assistant") => PromptResponse;
@@ -0,0 +1,25 @@
1
+ // Type definitions for the MCP (Message Control Protocol) protocol
2
+ // Helper functions
3
+ export const createToolResponse = (text, isError) => ({
4
+ content: [{
5
+ type: "text",
6
+ text
7
+ }],
8
+ isError
9
+ });
10
+ export const createResourceResponse = (uri, text, mimeType) => ({
11
+ contents: [{
12
+ uri,
13
+ text,
14
+ mimeType
15
+ }]
16
+ });
17
+ export const createPromptResponse = (text, role = "assistant") => ({
18
+ messages: [{
19
+ role,
20
+ content: {
21
+ type: "text",
22
+ text
23
+ }
24
+ }]
25
+ });
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ import { RateLimitConfig } from "../utils/rateLimiter.js";
3
+ import { OperationContext } from "../utils/security.js";
4
+ /**
5
+ * Metadata for a tool example
6
+ */
7
+ export interface ToolExample {
8
+ /** Example input parameters */
9
+ input: Record<string, unknown>;
10
+ /** Expected output string */
11
+ output: string;
12
+ /** Description of the example */
13
+ description: string;
14
+ }
15
+ /**
16
+ * Configuration for a tool
17
+ */
18
+ export interface ToolMetadata {
19
+ /** Examples showing how to use the tool */
20
+ examples: ToolExample[];
21
+ /** Optional permission required for this tool */
22
+ requiredPermission?: string;
23
+ /** Optional schema for the return value */
24
+ returnSchema?: z.ZodType<unknown>;
25
+ /** Rate limit configuration for the tool */
26
+ rateLimit?: RateLimitConfig;
27
+ /** Whether this tool can be used without authentication */
28
+ allowUnauthenticated?: boolean;
29
+ }
30
+ /**
31
+ * Create a tool example
32
+ *
33
+ * @param input Example input parameters
34
+ * @param output Expected output (as a formatted string)
35
+ * @param description Description of what the example demonstrates
36
+ * @returns A tool example object
37
+ */
38
+ export declare function createToolExample(input: Record<string, unknown>, output: string, description: string): ToolExample;
39
+ /**
40
+ * Create tool metadata
41
+ *
42
+ * @param metadata Tool metadata options
43
+ * @returns Tool metadata configuration
44
+ */
45
+ export declare function createToolMetadata(metadata: ToolMetadata): ToolMetadata;
46
+ /**
47
+ * Register a tool with the MCP server
48
+ *
49
+ * This is a compatibility wrapper for the McpServer.tool() method.
50
+ * In the current implementation, the tool registration is handled by the McpServer class,
51
+ * so this function primarily exists to provide a consistent API.
52
+ *
53
+ * @param server MCP server instance
54
+ * @param name Tool name
55
+ * @param description Tool description
56
+ * @param inputSchema Schema for validating input
57
+ * @param handler Handler function for the tool
58
+ * @param metadata Optional tool metadata
59
+ */
60
+ export declare function registerTool(server: any, // Using any to avoid type conflicts
61
+ name: string, description: string, inputSchema: Record<string, z.ZodType<any>>, handler: (input: unknown, context: OperationContext) => Promise<unknown>, metadata?: ToolMetadata): Promise<void>;