🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@topvisor/mcp-notifications

Package Overview
Dependencies
Maintainers
2
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@topvisor/mcp-notifications - npm Package Compare versions

Comparing version
1.0.3
to
1.0.4
+41
src/send-via-powershell.mjs
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import notifier from 'node-notifier'
import {normalizeIconPath} from './utils.mjs'
const payloadBase64 = process.argv[2] || ''
if (!payloadBase64) process.exit(1)
let job
try {
job = JSON.parse(Buffer.from(payloadBase64, 'base64').toString('utf8'))
} catch {
process.exit(1)
}
const icon = prepareIconPowerShell(job.icon)
notifier.notify(
{
title: job.title,
message: job.message,
wait: job.wait,
sound: Boolean(job.sound),
icon,
appID: job.appID,
},
(error) => process.exit(error ? 1 : 0),
)
function prepareIconPowerShell(iconPath) {
if (!iconPath) return undefined
const resolved = normalizeIconPath(iconPath)
if (!fs.existsSync(resolved)) return undefined
if (!resolved.startsWith('\\\\wsl.localhost\\')) return resolved
const ext = path.extname(resolved) || '.png'
const target = path.join(os.tmpdir(), `mcp-notifications-icon${ext}`)
fs.copyFileSync(resolved, target)
return target
}
import path from 'node:path'
// Преобразует пути WSL (/mnt/c/...) в Windows-формат (C:\...).
export const normalizeIconPath = (iconPath) => {
const resolved = path.resolve(iconPath)
const mntMatch = resolved.match(/^\/mnt\/([a-zA-Z])\/(.*)$/)
if (mntMatch) {
const drive = mntMatch[1].toUpperCase()
const rest = mntMatch[2].replace(/\//g, '\\')
return `${drive}:\\${rest}`
}
return resolved
}
// Определяет запуск в WSL, чтобы выбрать отправку через PowerShell-обёртку.
export const isWsl = () =>
process.platform === 'linux' &&
(Boolean(process.env.WSL_DISTRO_NAME) || process.env.WSL_INTEROP !== undefined)
+6
-1
{
"name": "@topvisor/mcp-notifications",
"version": "1.0.3",
"version": "1.0.4",
"mcpName": "io.github.topvisor/mcp-notifications",

@@ -35,2 +35,3 @@ "type": "module",

"scripts": {
"test": "vitest run",
"start": "node ./src/server.mjs",

@@ -47,3 +48,7 @@ "start:bin": "node ./bin/mcp-notifications.mjs"

"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^20.19.41",
"vitest": "^4.1.6"
}
}

@@ -148,2 +148,11 @@ # mcp-notifications

## Backend selection
You can select notification transport via env `MCP_NOTIFICATIONS_BACKEND`:
- `auto` (default): uses `powershell` in WSL if available, then `wsl-notify-send`, otherwise `node-notifier`
- `powershell`: force `powershell.exe` Windows toast transport (recommended for WSL)
- `wsl-notify-send`: force `wsl-notify-send` command
- `node-notifier`: force `node-notifier` package
## Chat Prompts To Test In Codex

@@ -150,0 +159,0 @@

+56
-47

@@ -1,61 +0,70 @@

import notifier from 'node-notifier';
import path from 'path';
import { fileURLToPath } from 'url';
import {spawn, spawnSync} from 'node:child_process'
import path from 'node:path'
import {fileURLToPath} from 'node:url'
import notifier from 'node-notifier'
import {isWsl, normalizeIconPath} from './utils.mjs'
const jobs = [];
let isProcessing = false;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DEFAULT_ICON_PATH = path.resolve(__dirname, '../assets/topvisor-robot.png');
const jobs = []
let isProcessing = false
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const DEFAULT_ICON_PATH = path.resolve(__dirname, '../assets/topvisor-robot.png')
const WINDOWS_WRAPPER_PATH = path.resolve(__dirname, './send-via-powershell.mjs')
/**
* Добавить уведомление в очередь фоновой отправки.
* Tool-вызов не ждёт завершения системного API уведомлений.
*/
export const enqueueNotification = ({ title, message, playSound, icon, appId }) => {
export const enqueueNotification = ({title, message, sound, icon, appID}) => {
jobs.push({
title,
message,
playSound,
icon: normalizeIcon(icon),
appId,
});
processQueue();
};
sound,
icon: normalizeIconPath(icon || DEFAULT_ICON_PATH),
wait: false,
appID,
})
processQueue()
}
const processQueue = () => {
if (isProcessing) return;
if (isProcessing) return
const job = jobs.shift()
if (!job) return
const job = jobs.shift();
if (!job) return;
isProcessing = true
sendNotification(job, (error) => {
isProcessing = false
if (error) {
console.error('[notify] Ошибка отправки уведомления:', error.message)
}
setImmediate(processQueue)
})
}
isProcessing = true;
const sendNotification = (job, done) => {
if (isWsl()) {
sendViaPowershellWrapper(job, done)
return
}
notifier.notify(
{
title: job.title,
message: job.message,
wait: false,
sound: job.playSound,
icon: job.icon,
appID: job.appId,
},
(error) => {
isProcessing = false;
notifier.notify(job, (error) => done(error ?? null))
}
if (error) {
console.error('[notify] Ошибка отправки уведомления:', error.message);
}
const sendViaPowershellWrapper = (job, done) => {
const wrapperWindowsPath = toWindowsPath(WINDOWS_WRAPPER_PATH)
if (!wrapperWindowsPath) {
done(new Error('Cannot resolve Windows path for notifier wrapper'))
return
}
setImmediate(processQueue);
},
);
};
const payload = Buffer.from(JSON.stringify(job), 'utf8').toString('base64')
const command = `node "${wrapperWindowsPath}" "${payload}"`
const child = spawn('powershell.exe', ['-NoProfile', '-Command', command], {stdio: 'ignore'})
child.on('error', (error) => done(error))
child.on('exit', (code) => done(code === 0 ? null : new Error(`powershell exited with code ${code}`)))
}
const normalizeIcon = (icon) => {
if (!icon) {
return DEFAULT_ICON_PATH;
const toWindowsPath = (linuxPath) => {
const converted = spawnSync('wslpath', ['-w', linuxPath], {encoding: 'utf8'})
if (converted.status !== 0) {
return null
}
return path.resolve(icon);
};
return converted.stdout.trim() || null
}

@@ -21,4 +21,4 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

async ({ title, message, play_sound, icon, app_id }) => {
const playSound = play_sound ?? false;
const appId = app_id ?? process.env.MCP_NOTIFICATIONS_APP_ID;
const sound = play_sound ?? false;
const appID = app_id ?? process.env.MCP_NOTIFICATIONS_APP_ID;

@@ -28,5 +28,5 @@ enqueueNotification({

message,
playSound,
sound,
icon,
appId,
appID,
});

@@ -33,0 +33,0 @@