UNPKG

ntfy-mcp-server

Version:

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.

125 lines (124 loc) 6.22 kB
import { config } from "../../../config/index.js"; import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; import { ErrorHandler } from "../../../utils/errorHandler.js"; import { logger } from "../../../utils/logger.js"; import { createRequestContext } from "../../../utils/requestContext.js"; import { sanitizeInputForLogging } from "../../../utils/sanitization.js"; import { processNtfyMessage } from "./ntfyMessage.js"; import { SendNtfyToolInputSchema } from "./types.js"; // Create module logger const moduleLogger = logger.createChildLogger({ module: 'NtfyToolRegistration' }); /** * Register the send_ntfy tool with the MCP server * * This function registers a tool for sending notifications via ntfy.sh with * comprehensive parameter support for all ntfy features. * * @param server - The MCP server instance to register the tool with * @returns Promise resolving when registration is complete */ export const registerNtfyTool = async (server) => { // Create a request context for tracking this registration operation const requestCtx = createRequestContext({ operation: 'registerNtfyTool', component: 'NtfyTool' }); // Create a tool-specific logger const toolLogger = logger.createChildLogger({ module: 'NtfyTool', operation: 'registration' }); moduleLogger.info('Starting ntfy tool registration'); // Create a fresh schema with the latest config values // This ensures we have the most up-to-date environment variables const schemaWithLatestConfig = SendNtfyToolInputSchema(); // Log default topic info at registration time for verification const ntfyConfig = config.ntfy; toolLogger.info('Registering ntfy tool handler with config', { defaultTopic: ntfyConfig.defaultTopic || '(not set)', baseUrl: ntfyConfig.baseUrl, apiKeyPresent: !!ntfyConfig.apiKey }); try { // Prepare the description with the default topic information const defaultTopicInfo = ntfyConfig.defaultTopic ? `Default topic: "${ntfyConfig.defaultTopic}"` : "No default topic configured"; // Register the tool directly using the SDK pattern 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) => { // Create request context for tracking this invocation const toolRequestCtx = createRequestContext({ operation: 'handleNtfyTool', topic: params?.topic }); toolLogger.debug('Received tool invocation', { requestId: toolRequestCtx.requestId, topic: params?.topic }); // Use ErrorHandler for consistent error handling return await ErrorHandler.tryCatch(async () => { // Process the notification const response = await processNtfyMessage(params); toolLogger.info('Successfully processed ntfy message', { messageId: response.id, topic: response.topic, retries: response.retries }); // Return in the standard MCP format return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] }; }, { operation: 'sending ntfy notification', context: { requestId: toolRequestCtx.requestId, topic: params?.topic }, input: sanitizeInputForLogging(params), // Map errors appropriately errorMapper: (error) => { // Log the error toolLogger.error('Error processing ntfy tool request', { error: error instanceof Error ? error.message : 'Unknown error', errorType: error instanceof Error ? error.name : 'Unknown', topic: params?.topic }); // Pass through McpErrors, map others properly if (error instanceof McpError) { return error; } // Try to classify unknown errors if (error instanceof Error) { const errorMsg = error.message.toLowerCase(); if (errorMsg.includes('validation') || errorMsg.includes('invalid')) { return new McpError(BaseErrorCode.VALIDATION_ERROR, `Validation error: ${error.message}`); } else if (errorMsg.includes('not found') || errorMsg.includes('missing')) { return new McpError(BaseErrorCode.NOT_FOUND, `Resource not found: ${error.message}`); } else if (errorMsg.includes('timeout')) { return new McpError(BaseErrorCode.TIMEOUT, `Request timed out: ${error.message}`); } else if (errorMsg.includes('rate limit')) { return new McpError(BaseErrorCode.RATE_LIMITED, `Rate limit exceeded: ${error.message}`); } } // Default to service unavailable for network/connection issues return new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Failed to send notification: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); }); toolLogger.info("Ntfy tool handler registered successfully"); } catch (error) { toolLogger.error("Failed to register ntfy tool", { error: error instanceof Error ? error.message : String(error) }); throw error; // Re-throw to propagate the error } };