ntfy-mcp-server 1.0.1 → 1.0.2

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.8.2-blue.svg)](https://www.typescriptlang.org/)
4
4
  [![Model Context Protocol](https://img.shields.io/badge/MCP-1.8.0-green.svg)](https://modelcontextprotocol.io/)
5
- [![Version](https://img.shields.io/badge/Version-1.0.1-blue.svg)](https://github.com/cyanheads/ntfy-mcp-server/releases)
5
+ [![Version](https://img.shields.io/badge/Version-1.0.2-blue.svg)](https://github.com/cyanheads/ntfy-mcp-server/releases)
6
6
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/cyanheads/ntfy-mcp-server)
8
8
  [![GitHub](https://img.shields.io/github/stars/cyanheads/ntfy-mcp-server?style=social)](https://github.com/cyanheads/ntfy-mcp-server)
@@ -23,14 +23,16 @@ 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
  const pkgPath = path.resolve(__dirname, '../../package.json');
32
34
  const safePath = sanitizeInput.path(pkgPath);
33
- console.error(`Looking for package.json at: ${safePath}`);
35
+ pkgLogger.debug(`Looking for package.json at: ${safePath}`);
34
36
  // Get file stats to check size before reading
35
37
  const stats = await fs.stat(safePath);
36
38
  // Check file size to prevent DoS attacks
@@ -126,27 +128,25 @@ export const createMcpServer = async () => {
126
128
  newState
127
129
  });
128
130
  });
129
- console.error("Initializing MCP server...");
130
131
  serverLogger.info("Initializing server...");
131
132
  const timers = [];
132
133
  return await ErrorHandler.tryCatch(async () => {
133
134
  // Load package info asynchronously
134
- const packageInfo = await loadPackageInfo();
135
+ const packageInfo = await loadPackageInfo(serverLogger);
135
136
  // Update logger with package info
136
- console.error("Loaded package info:", packageInfo.name, packageInfo.version);
137
137
  serverLogger.info("Loaded package info", {
138
138
  name: packageInfo.name,
139
139
  version: packageInfo.version
140
140
  });
141
141
  // Create the MCP server instance
142
- console.error("Creating MCP server instance...");
142
+ serverLogger.debug("Creating MCP server instance...");
143
143
  server = new McpServer({
144
144
  name: packageInfo.name,
145
145
  version: packageInfo.version
146
146
  });
147
- console.error("MCP server instance created");
147
+ serverLogger.debug("MCP server instance created");
148
148
  const registerComponent = async (type, name, registerFn) => {
149
- console.error(`Registering ${type}: ${name}`);
149
+ serverLogger.debug(`Registering ${type}: ${name}`);
150
150
  try {
151
151
  await ErrorHandler.tryCatch(async () => await registerFn(), {
152
152
  operation: `Register${type === 'tool' ? 'Tool' : 'Resource'}`,
@@ -160,16 +160,16 @@ export const createMcpServer = async () => {
160
160
  else {
161
161
  serverState.registeredResources.add(name);
162
162
  }
163
- console.error(`Successfully registered ${type}: ${name}`);
163
+ serverLogger.debug(`Successfully registered ${type}: ${name}`);
164
164
  return { success: true, type, name };
165
165
  }
166
166
  catch (error) {
167
- console.error(`Failed to register ${type}: ${name}`, error);
167
+ serverLogger.error(`Failed to register ${type}: ${name}`, { error });
168
168
  return { success: false, type, name, error };
169
169
  }
170
170
  };
171
171
  // Register components with proper error handling
172
- console.error("Registering components...");
172
+ serverLogger.debug("Registering components...");
173
173
  const registrationPromises = [
174
174
  registerComponent('tool', 'send_ntfy', () => registerNtfyTool(server)),
175
175
  registerComponent('resource', 'ntfy-resource', () => registerNtfyResource(server)),
@@ -192,30 +192,32 @@ export const createMcpServer = async () => {
192
192
  });
193
193
  // Process failed registrations
194
194
  if (failedRegistrations.length > 0) {
195
- console.error(`${failedRegistrations.length} registrations failed initially`, failedRegistrations.map(f => `${f.type}:${f.name}`));
196
195
  serverLogger.warn(`${failedRegistrations.length} registrations failed initially`, {
197
196
  failedComponents: failedRegistrations.map(f => `${f.type}:${f.name}`)
198
197
  });
199
198
  }
200
199
  // Add debug logs to diagnose the connection issue
201
- console.error("About to connect to stdio transport");
200
+ serverLogger.debug("About to connect to stdio transport");
202
201
  try {
203
202
  // Connect using stdio transport
204
203
  const transport = new StdioServerTransport();
205
- console.error("Created StdioServerTransport instance");
204
+ serverLogger.debug("Created StdioServerTransport instance");
206
205
  // Set event handlers - using type assertion to avoid TS errors
207
206
  server.onerror = (err) => {
208
- console.error(`Server error: ${err.message}`);
207
+ serverLogger.error(`Server error: ${err.message}`, { stack: err.stack });
209
208
  };
210
209
  // Skip setting onrequest since we don't have access to the type
211
210
  await server.connect(transport);
212
- console.error("Connected to transport successfully");
211
+ serverLogger.debug("Connected to transport successfully");
213
212
  }
214
213
  catch (error) {
215
- console.error("Error connecting to transport:", error);
214
+ serverLogger.error("Error connecting to transport", {
215
+ error: error instanceof Error ? error.message : String(error),
216
+ stack: error instanceof Error ? error.stack : undefined
217
+ });
216
218
  throw error;
217
219
  }
218
- console.error("MCP server initialized and connected");
220
+ serverLogger.info("MCP server initialized and connected");
219
221
  return server;
220
222
  }, {
221
223
  operation: 'CreateMcpServer',
@@ -228,7 +230,10 @@ export const createMcpServer = async () => {
228
230
  registeredResources: Array.from(serverState.registeredResources)
229
231
  })
230
232
  }).catch((error) => {
231
- console.error("Fatal error in MCP server creation:", error);
233
+ serverLogger.error("Fatal error in MCP server creation", {
234
+ error: error instanceof Error ? error.message : String(error),
235
+ stack: error instanceof Error ? error.stack : undefined
236
+ });
232
237
  // Attempt to close server
233
238
  if (server) {
234
239
  try {
@@ -236,7 +241,10 @@ export const createMcpServer = async () => {
236
241
  }
237
242
  catch (closeError) {
238
243
  // Already in error state, just log
239
- console.error("Error while closing server during error recovery:", closeError);
244
+ serverLogger.error("Error while closing server during error recovery", {
245
+ error: closeError instanceof Error ? closeError.message : String(closeError),
246
+ stack: closeError instanceof Error ? closeError.stack : undefined
247
+ });
240
248
  }
241
249
  }
242
250
  // 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
- return registerTool(server, { name: "send_ntfy" }, async (server, toolLogger) => {
31
- // Create a fresh schema with the latest config values
32
- // This ensures we have the most up-to-date environment variables
33
- const schemaWithLatestConfig = SendNtfyToolInputSchema();
34
- // Log default topic info at registration time for verification
35
- const ntfyConfig = config.ntfy;
36
- toolLogger.info('Registering ntfy tool handler with config', {
37
- defaultTopic: ntfyConfig.defaultTopic || '(not set)',
38
- baseUrl: ntfyConfig.baseUrl,
39
- apiKeyPresent: !!ntfyConfig.apiKey
40
- });
41
- // Register the tool using the simplified SDK pattern
42
- server.tool("send_ntfy", schemaWithLatestConfig.shape, async (params) => {
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.1",
3
+ "version": "1.0.2",
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",