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 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.3-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,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
- const pkgPath = path.resolve(__dirname, '../../package.json');
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
- console.error(`Looking for package.json at: ${safePath}`);
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
- console.error("Creating MCP server instance...");
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
- console.error("MCP server instance created");
148
+ serverLogger.debug("MCP server instance created");
148
149
  const registerComponent = async (type, name, registerFn) => {
149
- console.error(`Registering ${type}: ${name}`);
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
- console.error(`Successfully registered ${type}: ${name}`);
164
+ serverLogger.debug(`Successfully registered ${type}: ${name}`);
164
165
  return { success: true, type, name };
165
166
  }
166
167
  catch (error) {
167
- console.error(`Failed to register ${type}: ${name}`, error);
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
- console.error("Registering components...");
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
- console.error("About to connect to stdio transport");
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
- console.error("Created StdioServerTransport instance");
205
+ serverLogger.debug("Created StdioServerTransport instance");
206
206
  // Set event handlers - using type assertion to avoid TS errors
207
207
  server.onerror = (err) => {
208
- console.error(`Server error: ${err.message}`);
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
- console.error("Connected to transport successfully");
212
+ serverLogger.debug("Connected to transport successfully");
213
213
  }
214
214
  catch (error) {
215
- console.error("Error connecting to transport:", error);
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
- console.error("MCP server initialized and connected");
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
- console.error("Fatal error in MCP server creation:", error);
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
- console.error("Error while closing server during error recovery:", closeError);
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
- 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.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"