ntfy-mcp-server 1.0.1 → 1.0.3
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/README.md +1 -1
- package/dist/mcp-server/server.js +30 -21
- package/dist/mcp-server/tools/ntfyTool/index.js +29 -15
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
4
|
[](https://modelcontextprotocol.io/)
|
|
5
|
-
[](https://github.com/cyanheads/ntfy-mcp-server/releases)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://github.com/cyanheads/ntfy-mcp-server)
|
|
8
8
|
[](https://github.com/cyanheads/ntfy-mcp-server)
|
|
@@ -23,14 +23,17 @@ const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
|
23
23
|
/**
|
|
24
24
|
* Load package information directly from package.json
|
|
25
25
|
*
|
|
26
|
+
* @param logger - The logger instance to use for logging
|
|
26
27
|
* @returns A promise resolving to an object with the package name and version
|
|
27
28
|
*/
|
|
28
|
-
const loadPackageInfo = async () => {
|
|
29
|
+
const loadPackageInfo = async (loggerInstance) => {
|
|
30
|
+
const pkgLogger = loggerInstance || logger.createChildLogger({ module: 'PackageInfo' });
|
|
29
31
|
return await ErrorHandler.tryCatch(async () => {
|
|
30
32
|
// Use the globally defined __dirname from the top of the file
|
|
31
|
-
|
|
33
|
+
// Go up three levels from the compiled file location (e.g., dist/mcp-server/server.js)
|
|
34
|
+
const pkgPath = path.resolve(__dirname, '../../../package.json');
|
|
32
35
|
const safePath = sanitizeInput.path(pkgPath);
|
|
33
|
-
|
|
36
|
+
pkgLogger.debug(`Looking for package.json at: ${safePath}`);
|
|
34
37
|
// Get file stats to check size before reading
|
|
35
38
|
const stats = await fs.stat(safePath);
|
|
36
39
|
// Check file size to prevent DoS attacks
|
|
@@ -126,27 +129,25 @@ export const createMcpServer = async () => {
|
|
|
126
129
|
newState
|
|
127
130
|
});
|
|
128
131
|
});
|
|
129
|
-
console.error("Initializing MCP server...");
|
|
130
132
|
serverLogger.info("Initializing server...");
|
|
131
133
|
const timers = [];
|
|
132
134
|
return await ErrorHandler.tryCatch(async () => {
|
|
133
135
|
// Load package info asynchronously
|
|
134
|
-
const packageInfo = await loadPackageInfo();
|
|
136
|
+
const packageInfo = await loadPackageInfo(serverLogger);
|
|
135
137
|
// Update logger with package info
|
|
136
|
-
console.error("Loaded package info:", packageInfo.name, packageInfo.version);
|
|
137
138
|
serverLogger.info("Loaded package info", {
|
|
138
139
|
name: packageInfo.name,
|
|
139
140
|
version: packageInfo.version
|
|
140
141
|
});
|
|
141
142
|
// Create the MCP server instance
|
|
142
|
-
|
|
143
|
+
serverLogger.debug("Creating MCP server instance...");
|
|
143
144
|
server = new McpServer({
|
|
144
145
|
name: packageInfo.name,
|
|
145
146
|
version: packageInfo.version
|
|
146
147
|
});
|
|
147
|
-
|
|
148
|
+
serverLogger.debug("MCP server instance created");
|
|
148
149
|
const registerComponent = async (type, name, registerFn) => {
|
|
149
|
-
|
|
150
|
+
serverLogger.debug(`Registering ${type}: ${name}`);
|
|
150
151
|
try {
|
|
151
152
|
await ErrorHandler.tryCatch(async () => await registerFn(), {
|
|
152
153
|
operation: `Register${type === 'tool' ? 'Tool' : 'Resource'}`,
|
|
@@ -160,16 +161,16 @@ export const createMcpServer = async () => {
|
|
|
160
161
|
else {
|
|
161
162
|
serverState.registeredResources.add(name);
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
+
serverLogger.debug(`Successfully registered ${type}: ${name}`);
|
|
164
165
|
return { success: true, type, name };
|
|
165
166
|
}
|
|
166
167
|
catch (error) {
|
|
167
|
-
|
|
168
|
+
serverLogger.error(`Failed to register ${type}: ${name}`, { error });
|
|
168
169
|
return { success: false, type, name, error };
|
|
169
170
|
}
|
|
170
171
|
};
|
|
171
172
|
// Register components with proper error handling
|
|
172
|
-
|
|
173
|
+
serverLogger.debug("Registering components...");
|
|
173
174
|
const registrationPromises = [
|
|
174
175
|
registerComponent('tool', 'send_ntfy', () => registerNtfyTool(server)),
|
|
175
176
|
registerComponent('resource', 'ntfy-resource', () => registerNtfyResource(server)),
|
|
@@ -192,30 +193,32 @@ export const createMcpServer = async () => {
|
|
|
192
193
|
});
|
|
193
194
|
// Process failed registrations
|
|
194
195
|
if (failedRegistrations.length > 0) {
|
|
195
|
-
console.error(`${failedRegistrations.length} registrations failed initially`, failedRegistrations.map(f => `${f.type}:${f.name}`));
|
|
196
196
|
serverLogger.warn(`${failedRegistrations.length} registrations failed initially`, {
|
|
197
197
|
failedComponents: failedRegistrations.map(f => `${f.type}:${f.name}`)
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
200
|
// Add debug logs to diagnose the connection issue
|
|
201
|
-
|
|
201
|
+
serverLogger.debug("About to connect to stdio transport");
|
|
202
202
|
try {
|
|
203
203
|
// Connect using stdio transport
|
|
204
204
|
const transport = new StdioServerTransport();
|
|
205
|
-
|
|
205
|
+
serverLogger.debug("Created StdioServerTransport instance");
|
|
206
206
|
// Set event handlers - using type assertion to avoid TS errors
|
|
207
207
|
server.onerror = (err) => {
|
|
208
|
-
|
|
208
|
+
serverLogger.error(`Server error: ${err.message}`, { stack: err.stack });
|
|
209
209
|
};
|
|
210
210
|
// Skip setting onrequest since we don't have access to the type
|
|
211
211
|
await server.connect(transport);
|
|
212
|
-
|
|
212
|
+
serverLogger.debug("Connected to transport successfully");
|
|
213
213
|
}
|
|
214
214
|
catch (error) {
|
|
215
|
-
|
|
215
|
+
serverLogger.error("Error connecting to transport", {
|
|
216
|
+
error: error instanceof Error ? error.message : String(error),
|
|
217
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
218
|
+
});
|
|
216
219
|
throw error;
|
|
217
220
|
}
|
|
218
|
-
|
|
221
|
+
serverLogger.info("MCP server initialized and connected");
|
|
219
222
|
return server;
|
|
220
223
|
}, {
|
|
221
224
|
operation: 'CreateMcpServer',
|
|
@@ -228,7 +231,10 @@ export const createMcpServer = async () => {
|
|
|
228
231
|
registeredResources: Array.from(serverState.registeredResources)
|
|
229
232
|
})
|
|
230
233
|
}).catch((error) => {
|
|
231
|
-
|
|
234
|
+
serverLogger.error("Fatal error in MCP server creation", {
|
|
235
|
+
error: error instanceof Error ? error.message : String(error),
|
|
236
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
237
|
+
});
|
|
232
238
|
// Attempt to close server
|
|
233
239
|
if (server) {
|
|
234
240
|
try {
|
|
@@ -236,7 +242,10 @@ export const createMcpServer = async () => {
|
|
|
236
242
|
}
|
|
237
243
|
catch (closeError) {
|
|
238
244
|
// Already in error state, just log
|
|
239
|
-
|
|
245
|
+
serverLogger.error("Error while closing server during error recovery", {
|
|
246
|
+
error: closeError instanceof Error ? closeError.message : String(closeError),
|
|
247
|
+
stack: closeError instanceof Error ? closeError.stack : undefined
|
|
248
|
+
});
|
|
240
249
|
}
|
|
241
250
|
}
|
|
242
251
|
// Re-throw to communicate error to caller
|
|
@@ -4,7 +4,6 @@ import { ErrorHandler } from "../../../utils/errorHandler.js";
|
|
|
4
4
|
import { logger } from "../../../utils/logger.js";
|
|
5
5
|
import { createRequestContext } from "../../../utils/requestContext.js";
|
|
6
6
|
import { sanitizeInputForLogging } from "../../../utils/sanitization.js";
|
|
7
|
-
import { registerTool } from "../../utils/registrationHelper.js";
|
|
8
7
|
import { processNtfyMessage } from "./ntfyMessage.js";
|
|
9
8
|
import { SendNtfyToolInputSchema } from "./types.js";
|
|
10
9
|
// Create module logger
|
|
@@ -26,20 +25,29 @@ export const registerNtfyTool = async (server) => {
|
|
|
26
25
|
operation: 'registerNtfyTool',
|
|
27
26
|
component: 'NtfyTool'
|
|
28
27
|
});
|
|
28
|
+
// Create a tool-specific logger
|
|
29
|
+
const toolLogger = logger.createChildLogger({
|
|
30
|
+
module: 'NtfyTool',
|
|
31
|
+
operation: 'registration'
|
|
32
|
+
});
|
|
29
33
|
moduleLogger.info('Starting ntfy tool registration');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
34
|
+
// Create a fresh schema with the latest config values
|
|
35
|
+
// This ensures we have the most up-to-date environment variables
|
|
36
|
+
const schemaWithLatestConfig = SendNtfyToolInputSchema();
|
|
37
|
+
// Log default topic info at registration time for verification
|
|
38
|
+
const ntfyConfig = config.ntfy;
|
|
39
|
+
toolLogger.info('Registering ntfy tool handler with config', {
|
|
40
|
+
defaultTopic: ntfyConfig.defaultTopic || '(not set)',
|
|
41
|
+
baseUrl: ntfyConfig.baseUrl,
|
|
42
|
+
apiKeyPresent: !!ntfyConfig.apiKey
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
// Prepare the description with the default topic information
|
|
46
|
+
const defaultTopicInfo = ntfyConfig.defaultTopic
|
|
47
|
+
? `Default topic: "${ntfyConfig.defaultTopic}"`
|
|
48
|
+
: "No default topic configured";
|
|
49
|
+
// Register the tool directly using the SDK pattern
|
|
50
|
+
server.tool("send_ntfy", `Send notifications to the user's devices using ntfy.sh service with support for titles, priorities, tags, attachments, and actions. Use this tool to externally notify the user of something important. ${defaultTopicInfo}.`, schemaWithLatestConfig.shape, async (params) => {
|
|
43
51
|
// Create request context for tracking this invocation
|
|
44
52
|
const toolRequestCtx = createRequestContext({
|
|
45
53
|
operation: 'handleNtfyTool',
|
|
@@ -106,5 +114,11 @@ export const registerNtfyTool = async (server) => {
|
|
|
106
114
|
});
|
|
107
115
|
});
|
|
108
116
|
toolLogger.info("Ntfy tool handler registered successfully");
|
|
109
|
-
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
toolLogger.error("Failed to register ntfy tool", {
|
|
120
|
+
error: error instanceof Error ? error.message : String(error)
|
|
121
|
+
});
|
|
122
|
+
throw error; // Re-throw to propagate the error
|
|
123
|
+
}
|
|
110
124
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntfy-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "An MCP (Model Context Protocol) server designed to interact with the ntfy push notification service. It enables LLMs and AI agents to send notifications to your devices with extensive customization options.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -36,14 +36,15 @@
|
|
|
36
36
|
"ntfy",
|
|
37
37
|
"notifications",
|
|
38
38
|
"push-notifications",
|
|
39
|
+
"pub-sub",
|
|
39
40
|
"MCP",
|
|
40
41
|
"model-context-protocol",
|
|
42
|
+
"mcp-server",
|
|
41
43
|
"LLM",
|
|
42
44
|
"AI-integration",
|
|
43
45
|
"server",
|
|
44
46
|
"typescript",
|
|
45
|
-
"claude"
|
|
46
|
-
"messaging"
|
|
47
|
+
"claude"
|
|
47
48
|
],
|
|
48
49
|
"author": "Casey Hand @cyanheads",
|
|
49
50
|
"license": "Apache-2.0"
|