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.
- package/LICENSE +201 -0
- package/README.md +423 -0
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.js +111 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +108 -0
- package/dist/mcp-server/resources/ntfyResource/getNtfyTopic.d.ts +2 -0
- package/dist/mcp-server/resources/ntfyResource/getNtfyTopic.js +111 -0
- package/dist/mcp-server/resources/ntfyResource/index.d.ts +12 -0
- package/dist/mcp-server/resources/ntfyResource/index.js +72 -0
- package/dist/mcp-server/resources/ntfyResource/types.d.ts +27 -0
- package/dist/mcp-server/resources/ntfyResource/types.js +8 -0
- package/dist/mcp-server/server.d.ts +40 -0
- package/dist/mcp-server/server.js +245 -0
- package/dist/mcp-server/tools/ntfyTool/index.d.ts +11 -0
- package/dist/mcp-server/tools/ntfyTool/index.js +110 -0
- package/dist/mcp-server/tools/ntfyTool/ntfyMessage.d.ts +9 -0
- package/dist/mcp-server/tools/ntfyTool/ntfyMessage.js +289 -0
- package/dist/mcp-server/tools/ntfyTool/types.d.ts +252 -0
- package/dist/mcp-server/tools/ntfyTool/types.js +144 -0
- package/dist/mcp-server/utils/registrationHelper.d.ts +48 -0
- package/dist/mcp-server/utils/registrationHelper.js +63 -0
- package/dist/services/ntfy/constants.d.ts +37 -0
- package/dist/services/ntfy/constants.js +37 -0
- package/dist/services/ntfy/errors.d.ts +79 -0
- package/dist/services/ntfy/errors.js +134 -0
- package/dist/services/ntfy/index.d.ts +33 -0
- package/dist/services/ntfy/index.js +56 -0
- package/dist/services/ntfy/publisher.d.ts +66 -0
- package/dist/services/ntfy/publisher.js +229 -0
- package/dist/services/ntfy/subscriber.d.ts +81 -0
- package/dist/services/ntfy/subscriber.js +502 -0
- package/dist/services/ntfy/types.d.ts +161 -0
- package/dist/services/ntfy/types.js +4 -0
- package/dist/services/ntfy/utils.d.ts +85 -0
- package/dist/services/ntfy/utils.js +410 -0
- package/dist/types-global/errors.d.ts +35 -0
- package/dist/types-global/errors.js +39 -0
- package/dist/types-global/mcp.d.ts +30 -0
- package/dist/types-global/mcp.js +25 -0
- package/dist/types-global/tool.d.ts +61 -0
- package/dist/types-global/tool.js +99 -0
- package/dist/utils/errorHandler.d.ts +98 -0
- package/dist/utils/errorHandler.js +271 -0
- package/dist/utils/idGenerator.d.ts +94 -0
- package/dist/utils/idGenerator.js +149 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +16 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.js +92 -0
- package/dist/utils/rateLimiter.d.ts +115 -0
- package/dist/utils/rateLimiter.js +180 -0
- package/dist/utils/requestContext.d.ts +68 -0
- package/dist/utils/requestContext.js +91 -0
- package/dist/utils/sanitization.d.ts +224 -0
- package/dist/utils/sanitization.js +367 -0
- package/dist/utils/security.d.ts +26 -0
- package/dist/utils/security.js +27 -0
- package/package.json +47 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Valid priority levels for ntfy messages
|
|
4
|
+
*/
|
|
5
|
+
export declare const NTFY_PRIORITIES: readonly [1, 2, 3, 4, 5];
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Zod schema for the send_ntfy tool with current environment values
|
|
8
|
+
* This function should be called at registration time to ensure it has the
|
|
9
|
+
* latest configuration values from the environment
|
|
10
|
+
*
|
|
11
|
+
* @returns A Zod schema for the ntfy tool
|
|
12
|
+
*/
|
|
13
|
+
export declare function createSendNtfyToolSchema(): z.ZodObject<{
|
|
14
|
+
topic: z.ZodEffects<z.ZodString, string, string>;
|
|
15
|
+
message: z.ZodString;
|
|
16
|
+
title: z.ZodOptional<z.ZodString>;
|
|
17
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
18
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
click: z.ZodOptional<z.ZodString>;
|
|
20
|
+
actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21
|
+
id: z.ZodString;
|
|
22
|
+
label: z.ZodString;
|
|
23
|
+
action: z.ZodString;
|
|
24
|
+
url: z.ZodOptional<z.ZodString>;
|
|
25
|
+
method: z.ZodOptional<z.ZodString>;
|
|
26
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
27
|
+
body: z.ZodOptional<z.ZodString>;
|
|
28
|
+
clear: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
action: string;
|
|
33
|
+
method?: string | undefined;
|
|
34
|
+
url?: string | undefined;
|
|
35
|
+
headers?: Record<string, string> | undefined;
|
|
36
|
+
body?: string | undefined;
|
|
37
|
+
clear?: boolean | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
id: string;
|
|
40
|
+
label: string;
|
|
41
|
+
action: string;
|
|
42
|
+
method?: string | undefined;
|
|
43
|
+
url?: string | undefined;
|
|
44
|
+
headers?: Record<string, string> | undefined;
|
|
45
|
+
body?: string | undefined;
|
|
46
|
+
clear?: boolean | undefined;
|
|
47
|
+
}>, "many">>;
|
|
48
|
+
attachment: z.ZodOptional<z.ZodObject<{
|
|
49
|
+
url: z.ZodString;
|
|
50
|
+
name: z.ZodOptional<z.ZodString>;
|
|
51
|
+
}, "strip", z.ZodTypeAny, {
|
|
52
|
+
url: string;
|
|
53
|
+
name?: string | undefined;
|
|
54
|
+
}, {
|
|
55
|
+
url: string;
|
|
56
|
+
name?: string | undefined;
|
|
57
|
+
}>>;
|
|
58
|
+
email: z.ZodOptional<z.ZodString>;
|
|
59
|
+
delay: z.ZodOptional<z.ZodString>;
|
|
60
|
+
cache: z.ZodOptional<z.ZodString>;
|
|
61
|
+
firebase: z.ZodOptional<z.ZodString>;
|
|
62
|
+
id: z.ZodOptional<z.ZodString>;
|
|
63
|
+
expires: z.ZodOptional<z.ZodString>;
|
|
64
|
+
markdown: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
66
|
+
}, "strip", z.ZodTypeAny, {
|
|
67
|
+
message: string;
|
|
68
|
+
topic: string;
|
|
69
|
+
title?: string | undefined;
|
|
70
|
+
id?: string | undefined;
|
|
71
|
+
priority?: number | undefined;
|
|
72
|
+
tags?: string[] | undefined;
|
|
73
|
+
baseUrl?: string | undefined;
|
|
74
|
+
click?: string | undefined;
|
|
75
|
+
actions?: {
|
|
76
|
+
id: string;
|
|
77
|
+
label: string;
|
|
78
|
+
action: string;
|
|
79
|
+
method?: string | undefined;
|
|
80
|
+
url?: string | undefined;
|
|
81
|
+
headers?: Record<string, string> | undefined;
|
|
82
|
+
body?: string | undefined;
|
|
83
|
+
clear?: boolean | undefined;
|
|
84
|
+
}[] | undefined;
|
|
85
|
+
attachment?: {
|
|
86
|
+
url: string;
|
|
87
|
+
name?: string | undefined;
|
|
88
|
+
} | undefined;
|
|
89
|
+
email?: string | undefined;
|
|
90
|
+
delay?: string | undefined;
|
|
91
|
+
cache?: string | undefined;
|
|
92
|
+
firebase?: string | undefined;
|
|
93
|
+
expires?: string | undefined;
|
|
94
|
+
markdown?: boolean | undefined;
|
|
95
|
+
}, {
|
|
96
|
+
message: string;
|
|
97
|
+
topic: string;
|
|
98
|
+
title?: string | undefined;
|
|
99
|
+
id?: string | undefined;
|
|
100
|
+
priority?: number | undefined;
|
|
101
|
+
tags?: string[] | undefined;
|
|
102
|
+
baseUrl?: string | undefined;
|
|
103
|
+
click?: string | undefined;
|
|
104
|
+
actions?: {
|
|
105
|
+
id: string;
|
|
106
|
+
label: string;
|
|
107
|
+
action: string;
|
|
108
|
+
method?: string | undefined;
|
|
109
|
+
url?: string | undefined;
|
|
110
|
+
headers?: Record<string, string> | undefined;
|
|
111
|
+
body?: string | undefined;
|
|
112
|
+
clear?: boolean | undefined;
|
|
113
|
+
}[] | undefined;
|
|
114
|
+
attachment?: {
|
|
115
|
+
url: string;
|
|
116
|
+
name?: string | undefined;
|
|
117
|
+
} | undefined;
|
|
118
|
+
email?: string | undefined;
|
|
119
|
+
delay?: string | undefined;
|
|
120
|
+
cache?: string | undefined;
|
|
121
|
+
firebase?: string | undefined;
|
|
122
|
+
expires?: string | undefined;
|
|
123
|
+
markdown?: boolean | undefined;
|
|
124
|
+
}>;
|
|
125
|
+
export declare const SendNtfyToolInputSchema: () => z.ZodObject<{
|
|
126
|
+
topic: z.ZodEffects<z.ZodString, string, string>;
|
|
127
|
+
message: z.ZodString;
|
|
128
|
+
title: z.ZodOptional<z.ZodString>;
|
|
129
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
130
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
131
|
+
click: z.ZodOptional<z.ZodString>;
|
|
132
|
+
actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
133
|
+
id: z.ZodString;
|
|
134
|
+
label: z.ZodString;
|
|
135
|
+
action: z.ZodString;
|
|
136
|
+
url: z.ZodOptional<z.ZodString>;
|
|
137
|
+
method: z.ZodOptional<z.ZodString>;
|
|
138
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
139
|
+
body: z.ZodOptional<z.ZodString>;
|
|
140
|
+
clear: z.ZodOptional<z.ZodBoolean>;
|
|
141
|
+
}, "strip", z.ZodTypeAny, {
|
|
142
|
+
id: string;
|
|
143
|
+
label: string;
|
|
144
|
+
action: string;
|
|
145
|
+
method?: string | undefined;
|
|
146
|
+
url?: string | undefined;
|
|
147
|
+
headers?: Record<string, string> | undefined;
|
|
148
|
+
body?: string | undefined;
|
|
149
|
+
clear?: boolean | undefined;
|
|
150
|
+
}, {
|
|
151
|
+
id: string;
|
|
152
|
+
label: string;
|
|
153
|
+
action: string;
|
|
154
|
+
method?: string | undefined;
|
|
155
|
+
url?: string | undefined;
|
|
156
|
+
headers?: Record<string, string> | undefined;
|
|
157
|
+
body?: string | undefined;
|
|
158
|
+
clear?: boolean | undefined;
|
|
159
|
+
}>, "many">>;
|
|
160
|
+
attachment: z.ZodOptional<z.ZodObject<{
|
|
161
|
+
url: z.ZodString;
|
|
162
|
+
name: z.ZodOptional<z.ZodString>;
|
|
163
|
+
}, "strip", z.ZodTypeAny, {
|
|
164
|
+
url: string;
|
|
165
|
+
name?: string | undefined;
|
|
166
|
+
}, {
|
|
167
|
+
url: string;
|
|
168
|
+
name?: string | undefined;
|
|
169
|
+
}>>;
|
|
170
|
+
email: z.ZodOptional<z.ZodString>;
|
|
171
|
+
delay: z.ZodOptional<z.ZodString>;
|
|
172
|
+
cache: z.ZodOptional<z.ZodString>;
|
|
173
|
+
firebase: z.ZodOptional<z.ZodString>;
|
|
174
|
+
id: z.ZodOptional<z.ZodString>;
|
|
175
|
+
expires: z.ZodOptional<z.ZodString>;
|
|
176
|
+
markdown: z.ZodOptional<z.ZodBoolean>;
|
|
177
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
178
|
+
}, "strip", z.ZodTypeAny, {
|
|
179
|
+
message: string;
|
|
180
|
+
topic: string;
|
|
181
|
+
title?: string | undefined;
|
|
182
|
+
id?: string | undefined;
|
|
183
|
+
priority?: number | undefined;
|
|
184
|
+
tags?: string[] | undefined;
|
|
185
|
+
baseUrl?: string | undefined;
|
|
186
|
+
click?: string | undefined;
|
|
187
|
+
actions?: {
|
|
188
|
+
id: string;
|
|
189
|
+
label: string;
|
|
190
|
+
action: string;
|
|
191
|
+
method?: string | undefined;
|
|
192
|
+
url?: string | undefined;
|
|
193
|
+
headers?: Record<string, string> | undefined;
|
|
194
|
+
body?: string | undefined;
|
|
195
|
+
clear?: boolean | undefined;
|
|
196
|
+
}[] | undefined;
|
|
197
|
+
attachment?: {
|
|
198
|
+
url: string;
|
|
199
|
+
name?: string | undefined;
|
|
200
|
+
} | undefined;
|
|
201
|
+
email?: string | undefined;
|
|
202
|
+
delay?: string | undefined;
|
|
203
|
+
cache?: string | undefined;
|
|
204
|
+
firebase?: string | undefined;
|
|
205
|
+
expires?: string | undefined;
|
|
206
|
+
markdown?: boolean | undefined;
|
|
207
|
+
}, {
|
|
208
|
+
message: string;
|
|
209
|
+
topic: string;
|
|
210
|
+
title?: string | undefined;
|
|
211
|
+
id?: string | undefined;
|
|
212
|
+
priority?: number | undefined;
|
|
213
|
+
tags?: string[] | undefined;
|
|
214
|
+
baseUrl?: string | undefined;
|
|
215
|
+
click?: string | undefined;
|
|
216
|
+
actions?: {
|
|
217
|
+
id: string;
|
|
218
|
+
label: string;
|
|
219
|
+
action: string;
|
|
220
|
+
method?: string | undefined;
|
|
221
|
+
url?: string | undefined;
|
|
222
|
+
headers?: Record<string, string> | undefined;
|
|
223
|
+
body?: string | undefined;
|
|
224
|
+
clear?: boolean | undefined;
|
|
225
|
+
}[] | undefined;
|
|
226
|
+
attachment?: {
|
|
227
|
+
url: string;
|
|
228
|
+
name?: string | undefined;
|
|
229
|
+
} | undefined;
|
|
230
|
+
email?: string | undefined;
|
|
231
|
+
delay?: string | undefined;
|
|
232
|
+
cache?: string | undefined;
|
|
233
|
+
firebase?: string | undefined;
|
|
234
|
+
expires?: string | undefined;
|
|
235
|
+
markdown?: boolean | undefined;
|
|
236
|
+
}>;
|
|
237
|
+
export type SendNtfyToolInput = z.infer<ReturnType<typeof createSendNtfyToolSchema>>;
|
|
238
|
+
/**
|
|
239
|
+
* Response structure for the send_ntfy tool
|
|
240
|
+
*/
|
|
241
|
+
export interface SendNtfyToolResponse {
|
|
242
|
+
success: boolean;
|
|
243
|
+
id: string;
|
|
244
|
+
topic: string;
|
|
245
|
+
time: number;
|
|
246
|
+
expires?: number;
|
|
247
|
+
message: string;
|
|
248
|
+
title?: string;
|
|
249
|
+
url?: string;
|
|
250
|
+
/** Number of retries needed (if any) */
|
|
251
|
+
retries?: number;
|
|
252
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { config } from '../../../config/index.js';
|
|
3
|
+
import { logger } from '../../../utils/logger.js';
|
|
4
|
+
import { createRequestContext } from '../../../utils/requestContext.js';
|
|
5
|
+
// Create a module-specific logger
|
|
6
|
+
const schemaLogger = logger.createChildLogger({
|
|
7
|
+
module: 'NtfyToolSchema'
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Valid priority levels for ntfy messages
|
|
11
|
+
*/
|
|
12
|
+
export const NTFY_PRIORITIES = [1, 2, 3, 4, 5];
|
|
13
|
+
/**
|
|
14
|
+
* Validates a ntfy topic string format
|
|
15
|
+
*
|
|
16
|
+
* @param topic - The topic string to validate
|
|
17
|
+
* @returns boolean indicating if topic is valid
|
|
18
|
+
*/
|
|
19
|
+
function isValidTopic(topic) {
|
|
20
|
+
if (!topic)
|
|
21
|
+
return false;
|
|
22
|
+
return topic.trim() !== '' && !/[\r\n]/.test(topic);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Zod schema for the send_ntfy tool with current environment values
|
|
26
|
+
* This function should be called at registration time to ensure it has the
|
|
27
|
+
* latest configuration values from the environment
|
|
28
|
+
*
|
|
29
|
+
* @returns A Zod schema for the ntfy tool
|
|
30
|
+
*/
|
|
31
|
+
export function createSendNtfyToolSchema() {
|
|
32
|
+
// Create request context for tracking
|
|
33
|
+
const requestCtx = createRequestContext({
|
|
34
|
+
operation: 'createSendNtfyToolSchema'
|
|
35
|
+
});
|
|
36
|
+
schemaLogger.debug('Creating send_ntfy tool schema');
|
|
37
|
+
// Get the latest configuration
|
|
38
|
+
const ntfyConfig = config.ntfy;
|
|
39
|
+
// Process configuration values
|
|
40
|
+
const baseUrl = ntfyConfig.baseUrl || 'https://ntfy.sh';
|
|
41
|
+
const defaultTopic = ntfyConfig.defaultTopic || '';
|
|
42
|
+
const maxMessageSize = ntfyConfig.maxMessageSize || 4096;
|
|
43
|
+
// Log the loaded config values for debugging
|
|
44
|
+
schemaLogger.debug('Loaded ntfy configuration', {
|
|
45
|
+
defaultTopic: defaultTopic || '(not set)',
|
|
46
|
+
baseUrl,
|
|
47
|
+
maxMessageSize
|
|
48
|
+
});
|
|
49
|
+
// Generate better description text based on current config
|
|
50
|
+
const topicDesc = defaultTopic
|
|
51
|
+
? `The ntfy topic to send the notification to (required). Default topic configured: "${defaultTopic}". Use ntfy://default resource to identify the configured topic.`
|
|
52
|
+
: `The ntfy topic to send the notification to (required). No default topic configured. Use ntfy://default resource to check if a topic has been configured.`;
|
|
53
|
+
schemaLogger.debug('Schema configuration loaded', {
|
|
54
|
+
hasBaseUrl: !!baseUrl,
|
|
55
|
+
hasDefaultTopic: !!defaultTopic,
|
|
56
|
+
hasApiKey: !!ntfyConfig.apiKey,
|
|
57
|
+
maxMessageSize
|
|
58
|
+
});
|
|
59
|
+
// Create schema with the latest config values
|
|
60
|
+
const schema = z.object({
|
|
61
|
+
// Required parameters
|
|
62
|
+
topic: z.string()
|
|
63
|
+
.min(1, "Topic must not be empty")
|
|
64
|
+
.refine(isValidTopic, "Topic must not contain newlines")
|
|
65
|
+
.describe(topicDesc),
|
|
66
|
+
message: z.string()
|
|
67
|
+
.min(1, "Message must not be empty")
|
|
68
|
+
.max(maxMessageSize, `Message size cannot exceed ${maxMessageSize} bytes`)
|
|
69
|
+
.describe(`The message to send (notification body, max ${maxMessageSize} bytes)`),
|
|
70
|
+
// Optional parameters with improved descriptions
|
|
71
|
+
title: z.string()
|
|
72
|
+
.max(250, "Title should be under 250 characters")
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('Message title (optional)'),
|
|
75
|
+
tags: z.array(z.string())
|
|
76
|
+
.max(5, "Maximum of 5 tags allowed")
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Tags that show as emojis (e.g., ["warning", "skull", "robot"])'),
|
|
79
|
+
priority: z.number()
|
|
80
|
+
.int()
|
|
81
|
+
.min(1)
|
|
82
|
+
.max(5)
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Message priority: 1=min, 2=low, 3=default, 4=high, 5=max'),
|
|
85
|
+
click: z.string()
|
|
86
|
+
.url("Must be a valid URL")
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('URL to open when notification is clicked'),
|
|
89
|
+
actions: z.array(z.object({
|
|
90
|
+
id: z.string().describe('Action identifier'),
|
|
91
|
+
label: z.string().describe('Label for the action button'),
|
|
92
|
+
action: z.string().describe('Action type (e.g., view, broadcast, http)'),
|
|
93
|
+
url: z.string().url("Must be a valid URL").optional().describe('URL or data for the action'),
|
|
94
|
+
method: z.string().optional().describe('HTTP method for http actions'),
|
|
95
|
+
headers: z.record(z.string()).optional().describe('Additional headers for http actions'),
|
|
96
|
+
body: z.string().optional().describe('Body for http actions'),
|
|
97
|
+
clear: z.boolean().optional().describe('Clear notification after action (Default: false)')
|
|
98
|
+
}))
|
|
99
|
+
.max(3, "Maximum of 3 actions supported")
|
|
100
|
+
.optional()
|
|
101
|
+
.describe('Action buttons in the notification (max 3)'),
|
|
102
|
+
attachment: z.object({
|
|
103
|
+
url: z.string().url("Must be a valid URL").describe('URL of the attachment'),
|
|
104
|
+
name: z.string().optional().describe('Name of the attachment'),
|
|
105
|
+
})
|
|
106
|
+
.optional()
|
|
107
|
+
.describe('Attachment for the notification'),
|
|
108
|
+
email: z.string()
|
|
109
|
+
.email("Must be a valid email address")
|
|
110
|
+
.optional()
|
|
111
|
+
.describe('Email address to send the notification to'),
|
|
112
|
+
delay: z.string()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe('Delay the message (e.g., 30m, 1h, tomorrow)'),
|
|
115
|
+
cache: z.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('Cache duration (e.g., 10m, 1h, 1d)'),
|
|
118
|
+
firebase: z.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Firebase Cloud Messaging (FCM) topic to forward to'),
|
|
121
|
+
id: z.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('Unique ID for the message'),
|
|
124
|
+
expires: z.string()
|
|
125
|
+
.optional()
|
|
126
|
+
.describe('Message expiration (e.g., 10m, 1h, 1d)'),
|
|
127
|
+
markdown: z.boolean()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe('Format message as markdown'),
|
|
130
|
+
// Server options
|
|
131
|
+
baseUrl: z.string()
|
|
132
|
+
.url("Must be a valid URL")
|
|
133
|
+
.optional()
|
|
134
|
+
.describe(`Base URL for the ntfy server (default: ${baseUrl})`)
|
|
135
|
+
// Authentication is handled automatically using API key from .env when available
|
|
136
|
+
});
|
|
137
|
+
schemaLogger.info('Send_ntfy tool schema created successfully', {
|
|
138
|
+
fieldCount: Object.keys(schema.shape).length
|
|
139
|
+
});
|
|
140
|
+
return schema;
|
|
141
|
+
}
|
|
142
|
+
// Create a dynamic version of the schema for type inference
|
|
143
|
+
// We want to ensure this is always recreated at runtime with the latest env values
|
|
144
|
+
export const SendNtfyToolInputSchema = () => createSendNtfyToolSchema();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ChildLogger } from "../../utils/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Base interface for registration options
|
|
5
|
+
*/
|
|
6
|
+
export interface RegistrationOptions {
|
|
7
|
+
/** Name of the component being registered */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Logger context for creating a child logger */
|
|
10
|
+
loggerContext?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Full options with component type
|
|
14
|
+
*/
|
|
15
|
+
interface InternalRegistrationOptions extends RegistrationOptions {
|
|
16
|
+
/** Type of component (tool, resource, etc.) */
|
|
17
|
+
type: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Helper for consistent registration pattern with proper error handling
|
|
21
|
+
* @param server MCP server instance
|
|
22
|
+
* @param options Registration options
|
|
23
|
+
* @param registerFn Function that performs the actual registration
|
|
24
|
+
* @returns Promise resolving when registration is complete
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerComponent(server: McpServer, options: InternalRegistrationOptions, registerFn: (server: McpServer, childLogger: ChildLogger) => Promise<void>): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Register a tool with the MCP server using a consistent pattern
|
|
29
|
+
* @param server MCP server instance
|
|
30
|
+
* @param options Tool registration options
|
|
31
|
+
* @param handlerFn Function that sets up the tool handler
|
|
32
|
+
* @returns Promise resolving when registration is complete
|
|
33
|
+
*/
|
|
34
|
+
export declare function registerTool(server: McpServer, options: RegistrationOptions, handlerFn: (server: McpServer, logger: ChildLogger) => Promise<void>): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Register a resource with the MCP server using a consistent pattern
|
|
37
|
+
* @param server MCP server instance
|
|
38
|
+
* @param options Resource registration options
|
|
39
|
+
* @param handlerFn Function that sets up the resource handler
|
|
40
|
+
* @returns Promise resolving when registration is complete
|
|
41
|
+
*/
|
|
42
|
+
export declare function registerResource(server: McpServer, options: RegistrationOptions, handlerFn: (server: McpServer, logger: ChildLogger) => Promise<void>): Promise<void>;
|
|
43
|
+
declare const _default: {
|
|
44
|
+
registerComponent: typeof registerComponent;
|
|
45
|
+
registerTool: typeof registerTool;
|
|
46
|
+
registerResource: typeof registerResource;
|
|
47
|
+
};
|
|
48
|
+
export default _default;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { BaseErrorCode, McpError } from "../../types-global/errors.js";
|
|
2
|
+
import { ErrorHandler } from "../../utils/errorHandler.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* Helper for consistent registration pattern with proper error handling
|
|
6
|
+
* @param server MCP server instance
|
|
7
|
+
* @param options Registration options
|
|
8
|
+
* @param registerFn Function that performs the actual registration
|
|
9
|
+
* @returns Promise resolving when registration is complete
|
|
10
|
+
*/
|
|
11
|
+
export async function registerComponent(server, options, registerFn) {
|
|
12
|
+
// Create a component-specific logger
|
|
13
|
+
const componentLogger = logger.createChildLogger({
|
|
14
|
+
module: `${options.type}Registration`,
|
|
15
|
+
componentName: options.name,
|
|
16
|
+
...options.loggerContext
|
|
17
|
+
});
|
|
18
|
+
componentLogger.info(`Registering ${options.type}: ${options.name}`);
|
|
19
|
+
// Use ErrorHandler.tryCatch for consistent error handling
|
|
20
|
+
return await ErrorHandler.tryCatch(async () => {
|
|
21
|
+
// Call the registration function
|
|
22
|
+
await registerFn(server, componentLogger);
|
|
23
|
+
componentLogger.info(`${options.type} registered successfully: ${options.name}`);
|
|
24
|
+
}, {
|
|
25
|
+
operation: `registering ${options.type}`,
|
|
26
|
+
// Provide context for better error tracking
|
|
27
|
+
context: {
|
|
28
|
+
componentType: options.type,
|
|
29
|
+
componentName: options.name
|
|
30
|
+
},
|
|
31
|
+
// Use a specific error code for registration failures
|
|
32
|
+
errorCode: BaseErrorCode.INTERNAL_ERROR,
|
|
33
|
+
// Custom error mapper for clearer error messages
|
|
34
|
+
errorMapper: (error) => new McpError(error instanceof McpError ? error.code : BaseErrorCode.INTERNAL_ERROR, `Failed to register ${options.type} '${options.name}': ${error instanceof Error ? error.message : 'Unknown error'}`, { componentType: options.type, componentName: options.name }),
|
|
35
|
+
// Registration errors are considered critical
|
|
36
|
+
critical: true
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Register a tool with the MCP server using a consistent pattern
|
|
41
|
+
* @param server MCP server instance
|
|
42
|
+
* @param options Tool registration options
|
|
43
|
+
* @param handlerFn Function that sets up the tool handler
|
|
44
|
+
* @returns Promise resolving when registration is complete
|
|
45
|
+
*/
|
|
46
|
+
export async function registerTool(server, options, handlerFn) {
|
|
47
|
+
return registerComponent(server, { ...options, type: 'tool' }, handlerFn);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register a resource with the MCP server using a consistent pattern
|
|
51
|
+
* @param server MCP server instance
|
|
52
|
+
* @param options Resource registration options
|
|
53
|
+
* @param handlerFn Function that sets up the resource handler
|
|
54
|
+
* @returns Promise resolving when registration is complete
|
|
55
|
+
*/
|
|
56
|
+
export async function registerResource(server, options, handlerFn) {
|
|
57
|
+
return registerComponent(server, { ...options, type: 'resource' }, handlerFn);
|
|
58
|
+
}
|
|
59
|
+
export default {
|
|
60
|
+
registerComponent,
|
|
61
|
+
registerTool,
|
|
62
|
+
registerResource
|
|
63
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for the ntfy service
|
|
3
|
+
*/
|
|
4
|
+
/** Default ntfy server URL */
|
|
5
|
+
export declare const DEFAULT_NTFY_BASE_URL = "https://ntfy.sh";
|
|
6
|
+
/** HTTP subscription format endpoints */
|
|
7
|
+
export declare const SUBSCRIPTION_ENDPOINTS: {
|
|
8
|
+
json: string;
|
|
9
|
+
sse: string;
|
|
10
|
+
raw: string;
|
|
11
|
+
ws: string;
|
|
12
|
+
};
|
|
13
|
+
/** Default subscription options */
|
|
14
|
+
export declare const DEFAULT_SUBSCRIPTION_OPTIONS: {
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
poll: boolean;
|
|
17
|
+
scheduled: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** Default HTTP request timeout in milliseconds */
|
|
20
|
+
export declare const DEFAULT_REQUEST_TIMEOUT = 30000;
|
|
21
|
+
/** Keepalive timeout in milliseconds (how long to wait before considering connection dead) */
|
|
22
|
+
export declare const KEEPALIVE_TIMEOUT = 120000;
|
|
23
|
+
/** Reconnect delay in milliseconds (delay before attempting to reconnect after failure) */
|
|
24
|
+
export declare const RECONNECT_DELAY = 5000;
|
|
25
|
+
/** Maximum reconnect attempts before giving up */
|
|
26
|
+
export declare const MAX_RECONNECT_ATTEMPTS = 5;
|
|
27
|
+
/** User agent string for requests */
|
|
28
|
+
export declare const USER_AGENT = "ntfy-mcp-server/1.0.0";
|
|
29
|
+
/** Error messages */
|
|
30
|
+
export declare const ERROR_MESSAGES: {
|
|
31
|
+
INVALID_TOPIC: string;
|
|
32
|
+
CONNECTION_FAILED: string;
|
|
33
|
+
SUBSCRIPTION_CLOSED: string;
|
|
34
|
+
PARSE_ERROR: string;
|
|
35
|
+
NETWORK_ERROR: string;
|
|
36
|
+
AUTHENTICATION_FAILED: string;
|
|
37
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for the ntfy service
|
|
3
|
+
*/
|
|
4
|
+
/** Default ntfy server URL */
|
|
5
|
+
export const DEFAULT_NTFY_BASE_URL = 'https://ntfy.sh';
|
|
6
|
+
/** HTTP subscription format endpoints */
|
|
7
|
+
export const SUBSCRIPTION_ENDPOINTS = {
|
|
8
|
+
json: '/json',
|
|
9
|
+
sse: '/sse',
|
|
10
|
+
raw: '/raw',
|
|
11
|
+
ws: '/ws',
|
|
12
|
+
};
|
|
13
|
+
/** Default subscription options */
|
|
14
|
+
export const DEFAULT_SUBSCRIPTION_OPTIONS = {
|
|
15
|
+
baseUrl: DEFAULT_NTFY_BASE_URL,
|
|
16
|
+
poll: false,
|
|
17
|
+
scheduled: false,
|
|
18
|
+
};
|
|
19
|
+
/** Default HTTP request timeout in milliseconds */
|
|
20
|
+
export const DEFAULT_REQUEST_TIMEOUT = 30000;
|
|
21
|
+
/** Keepalive timeout in milliseconds (how long to wait before considering connection dead) */
|
|
22
|
+
export const KEEPALIVE_TIMEOUT = 120000;
|
|
23
|
+
/** Reconnect delay in milliseconds (delay before attempting to reconnect after failure) */
|
|
24
|
+
export const RECONNECT_DELAY = 5000;
|
|
25
|
+
/** Maximum reconnect attempts before giving up */
|
|
26
|
+
export const MAX_RECONNECT_ATTEMPTS = 5;
|
|
27
|
+
/** User agent string for requests */
|
|
28
|
+
export const USER_AGENT = 'ntfy-mcp-server/1.0.0';
|
|
29
|
+
/** Error messages */
|
|
30
|
+
export const ERROR_MESSAGES = {
|
|
31
|
+
INVALID_TOPIC: 'Invalid topic name',
|
|
32
|
+
CONNECTION_FAILED: 'Failed to connect to ntfy server',
|
|
33
|
+
SUBSCRIPTION_CLOSED: 'Subscription closed',
|
|
34
|
+
PARSE_ERROR: 'Failed to parse message',
|
|
35
|
+
NETWORK_ERROR: 'Network error occurred',
|
|
36
|
+
AUTHENTICATION_FAILED: 'Authentication failed',
|
|
37
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for the ntfy service
|
|
3
|
+
*/
|
|
4
|
+
import { McpError } from '../../types-global/errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base error class for ntfy service errors
|
|
7
|
+
*/
|
|
8
|
+
export declare class NtfyError extends McpError {
|
|
9
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when connection to ntfy server fails
|
|
13
|
+
*/
|
|
14
|
+
export declare class NtfyConnectionError extends NtfyError {
|
|
15
|
+
readonly url?: string | undefined;
|
|
16
|
+
constructor(message: string, url?: string | undefined);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Error thrown when authentication fails
|
|
20
|
+
*/
|
|
21
|
+
export declare class NtfyAuthenticationError extends NtfyError {
|
|
22
|
+
constructor(message: string);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Error thrown when a message cannot be parsed
|
|
26
|
+
*/
|
|
27
|
+
export declare class NtfyParseError extends NtfyError {
|
|
28
|
+
readonly rawData?: string | undefined;
|
|
29
|
+
constructor(message: string, rawData?: string | undefined);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown when a subscription is closed unexpectedly
|
|
33
|
+
*/
|
|
34
|
+
export declare class NtfySubscriptionClosedError extends NtfyError {
|
|
35
|
+
readonly reason?: string | undefined;
|
|
36
|
+
constructor(message: string, reason?: string | undefined);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when an invalid topic name is provided
|
|
40
|
+
*/
|
|
41
|
+
export declare class NtfyInvalidTopicError extends NtfyError {
|
|
42
|
+
readonly topic?: string | undefined;
|
|
43
|
+
constructor(message: string, topic?: string | undefined);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error thrown when a timeout occurs
|
|
47
|
+
*/
|
|
48
|
+
export declare class NtfyTimeoutError extends NtfyError {
|
|
49
|
+
readonly timeoutMs?: number | undefined;
|
|
50
|
+
constructor(message: string, timeoutMs?: number | undefined);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Error mapping for ntfy errors
|
|
54
|
+
*/
|
|
55
|
+
export declare const NTFY_ERROR_MAPPINGS: ({
|
|
56
|
+
pattern: RegExp;
|
|
57
|
+
errorCode: "UNAUTHORIZED";
|
|
58
|
+
factory: (error: unknown) => NtfyAuthenticationError;
|
|
59
|
+
} | {
|
|
60
|
+
pattern: RegExp;
|
|
61
|
+
errorCode: "VALIDATION_ERROR";
|
|
62
|
+
factory: (error: unknown, context?: Record<string, unknown>) => NtfyParseError;
|
|
63
|
+
} | {
|
|
64
|
+
pattern: RegExp;
|
|
65
|
+
errorCode: "VALIDATION_ERROR";
|
|
66
|
+
factory: (error: unknown, context?: Record<string, unknown>) => NtfyInvalidTopicError;
|
|
67
|
+
} | {
|
|
68
|
+
pattern: RegExp;
|
|
69
|
+
errorCode: "TIMEOUT";
|
|
70
|
+
factory: (error: unknown, context?: Record<string, unknown>) => NtfyTimeoutError;
|
|
71
|
+
} | {
|
|
72
|
+
pattern: RegExp;
|
|
73
|
+
errorCode: "SERVICE_UNAVAILABLE";
|
|
74
|
+
factory: (error: unknown, context?: Record<string, unknown>) => NtfyConnectionError;
|
|
75
|
+
})[];
|
|
76
|
+
/**
|
|
77
|
+
* Create an error mapper function for ntfy errors
|
|
78
|
+
*/
|
|
79
|
+
export declare const ntfyErrorMapper: (error: unknown, context?: Record<string, unknown>) => McpError;
|