Send Message Notification
notifySend notifications across desktop, email, and API to inform users of important updates, task completions, or alerts.
Instructions
Send notifications and messages through multiple channels (desktop, email, API). Use this tool to notify users about any important information, progress updates, task completions, alerts, or any other communication needs.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | The title of the notification | |
| message | No | The main content of the notification message |
Implementation Reference
- src/index.ts:82-221 (handler)The main handler function for the 'notify' tool. It sends notifications through multiple channels (ntfy, email, API, desktop sound, desktop notification) based on configuration, and returns results for each channel.
async ({ title, message }) => { const notifyTitle = title || 'Message MCP' const notifyMessage = message || 'Task completed, please review.' const allNotifyPromise: { [key: string]: Promise<unknown> } = {} // NTFY notification if (config.ntfyTopic) { const safeTopic = encodeURIComponent(config.ntfyTopic) allNotifyPromise.ntfy = upfetch(`https://ntfy.sh/${safeTopic}`, { method: 'POST', body: notifyMessage, headers: { Title: rfc2047.encode(notifyTitle), Priority: 'urgent', }, }) } // Email notification if (config.smtpHost && config.smtpUser && config.smtpPass) { const transporter = nodemailer.createTransport({ host: config.smtpHost, port: config.smtpPort, secure: config.smtpSecure, auth: { user: config.smtpUser, pass: config.smtpPass, }, pool: true, maxConnections: 5, }) const mailOptions = { from: config.smtpUser, to: config.smtpUser, subject: notifyTitle, text: notifyMessage, } allNotifyPromise.nodemailer = transporter.sendMail(mailOptions) } // API notification if (config.apiUrl) { allNotifyPromise.api = upfetch(config.apiUrl, { method: config.apiMethod, headers: config.apiHeaders, body: { title: notifyTitle, message: notifyMessage, }, }) } // Desktop play sound notification if (!config.disableDesktop) { // Sound notification const player = play({}) const internalSoundPath = join(__dirname, 'assets', 'notify.mp3') const soundPath = config.soundPath || internalSoundPath allNotifyPromise.sound = new Promise((resolve, reject) => { player.play(soundPath, (error) => { if (error) { reject(error) } }) setTimeout(() => { resolve({ message: 'Sound notification played successfully!', }) }, 1500) }) // Desktop notification allNotifyPromise.desktop = new Promise((resolve, reject) => { notifier.notify( { title: notifyTitle, message: notifyMessage, sound: false, }, (error) => { if (error) { reject(error) } }, ) setTimeout(() => { resolve({ message: 'Desktop notification sent successfully!', }) }, 1500) }) } // Wait for all notifications to complete if (Object.keys(allNotifyPromise).length === 0) { return { content: [ { type: 'text' as const, text: 'No notification channels configured.', }, ], } } // Collect results from all notifications const entries = Object.entries(allNotifyPromise) const results = await Promise.allSettled(entries.map(([, p]) => p)) const content: { type: 'text'; text: string }[] = [] results.forEach((result, i) => { const [name] = entries[i] let message = '' if (result.status === 'fulfilled') { message = typeof result.value === 'object' ? `successfully! ${JSON.stringify(result.value)}` : 'successfully!' } else { message = result.reason instanceof Error ? `failed! ${result.reason.message}` : 'failed!' } content.push({ type: 'text' as const, text: `${name} ${message}`, }) }) return { content, } }, - src/index.ts:74-80 (schema)Input schema for the 'notify' tool using Zod. Defines two optional string inputs: 'title' (notification title) and 'message' (main content).
inputSchema: { title: z.string().optional().describe('The title of the notification'), message: z .string() .optional() .describe('The main content of the notification message'), }, - src/index.ts:68-222 (registration)Registration of the 'notify' tool via server.registerTool(). The tool is named 'notify' and is registered with its metadata, schema, and handler on the McpServer instance.
server.registerTool( 'notify', { title: 'Send Message Notification', description: 'Send notifications and messages through multiple channels (desktop, email, API). Use this tool to notify users about any important information, progress updates, task completions, alerts, or any other communication needs.', inputSchema: { title: z.string().optional().describe('The title of the notification'), message: z .string() .optional() .describe('The main content of the notification message'), }, }, async ({ title, message }) => { const notifyTitle = title || 'Message MCP' const notifyMessage = message || 'Task completed, please review.' const allNotifyPromise: { [key: string]: Promise<unknown> } = {} // NTFY notification if (config.ntfyTopic) { const safeTopic = encodeURIComponent(config.ntfyTopic) allNotifyPromise.ntfy = upfetch(`https://ntfy.sh/${safeTopic}`, { method: 'POST', body: notifyMessage, headers: { Title: rfc2047.encode(notifyTitle), Priority: 'urgent', }, }) } // Email notification if (config.smtpHost && config.smtpUser && config.smtpPass) { const transporter = nodemailer.createTransport({ host: config.smtpHost, port: config.smtpPort, secure: config.smtpSecure, auth: { user: config.smtpUser, pass: config.smtpPass, }, pool: true, maxConnections: 5, }) const mailOptions = { from: config.smtpUser, to: config.smtpUser, subject: notifyTitle, text: notifyMessage, } allNotifyPromise.nodemailer = transporter.sendMail(mailOptions) } // API notification if (config.apiUrl) { allNotifyPromise.api = upfetch(config.apiUrl, { method: config.apiMethod, headers: config.apiHeaders, body: { title: notifyTitle, message: notifyMessage, }, }) } // Desktop play sound notification if (!config.disableDesktop) { // Sound notification const player = play({}) const internalSoundPath = join(__dirname, 'assets', 'notify.mp3') const soundPath = config.soundPath || internalSoundPath allNotifyPromise.sound = new Promise((resolve, reject) => { player.play(soundPath, (error) => { if (error) { reject(error) } }) setTimeout(() => { resolve({ message: 'Sound notification played successfully!', }) }, 1500) }) // Desktop notification allNotifyPromise.desktop = new Promise((resolve, reject) => { notifier.notify( { title: notifyTitle, message: notifyMessage, sound: false, }, (error) => { if (error) { reject(error) } }, ) setTimeout(() => { resolve({ message: 'Desktop notification sent successfully!', }) }, 1500) }) } // Wait for all notifications to complete if (Object.keys(allNotifyPromise).length === 0) { return { content: [ { type: 'text' as const, text: 'No notification channels configured.', }, ], } } // Collect results from all notifications const entries = Object.entries(allNotifyPromise) const results = await Promise.allSettled(entries.map(([, p]) => p)) const content: { type: 'text'; text: string }[] = [] results.forEach((result, i) => { const [name] = entries[i] let message = '' if (result.status === 'fulfilled') { message = typeof result.value === 'object' ? `successfully! ${JSON.stringify(result.value)}` : 'successfully!' } else { message = result.reason instanceof Error ? `failed! ${result.reason.message}` : 'failed!' } content.push({ type: 'text' as const, text: `${name} ${message}`, }) }) return { content, } }, ) - src/utils.ts:1-9 (helper)Helper function getBoolean() used to parse environment variables for the config, e.g., disableDesktop and smtpSecure.
export function getBoolean(value?: string | boolean): boolean { if (typeof value === 'boolean') { return value } if (typeof value === 'string') { return value.toLowerCase() === 'true' || value === '1' } return false } - src/utils.ts:22-34 (helper)Helper function getHeaders() used to parse API_HEADERS env var into a HeadersInit object for the API notification channel.
export function getHeaders(headers?: string): HeadersInit { if (!headers) return {} try { const parsed = JSON.parse(headers) return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {} } catch (error) { console.error('Invalid headers format, using empty headers:', error) return {} } }