@mcpcn/mcp-notification 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 +67 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +519 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# 通知MCP服务器
|
|
2
|
+
|
|
3
|
+
跨平台系统通知MCP服务器,支持macOS、Windows和Linux系统。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 📢 发送系统通知
|
|
8
|
+
- ⏰ 延迟发送
|
|
9
|
+
- 🔄 重复通知
|
|
10
|
+
- 🎵 声音支持
|
|
11
|
+
- 🖥️ 跨平台支持(macOS、Windows、Linux)
|
|
12
|
+
|
|
13
|
+
## 工具
|
|
14
|
+
|
|
15
|
+
### send_notification
|
|
16
|
+
发送系统通知
|
|
17
|
+
|
|
18
|
+
**参数:**
|
|
19
|
+
- `title` (必需): 通知标题
|
|
20
|
+
- `message` (必需): 通知内容
|
|
21
|
+
- `subtitle` (可选): 副标题
|
|
22
|
+
- `sound` (可选): 是否播放声音(默认true)
|
|
23
|
+
- `delay` (可选): 延迟发送(毫秒或"10s", "1m", "1h"格式)
|
|
24
|
+
- `repeat` (可选): 重复间隔(毫秒或时间字符串)
|
|
25
|
+
- `repeatCount` (可选): 重复次数
|
|
26
|
+
|
|
27
|
+
### notification_task_management
|
|
28
|
+
管理通知任务
|
|
29
|
+
|
|
30
|
+
**参数:**
|
|
31
|
+
- `action` (必需): 操作类型
|
|
32
|
+
- `stop_repeat_task`: 停止指定任务
|
|
33
|
+
- `stop_all_repeat_tasks`: 停止所有任务
|
|
34
|
+
- `get_active_repeat_tasks`: 获取活跃任务
|
|
35
|
+
- `get_repeat_task_info`: 获取任务信息
|
|
36
|
+
- `taskId` (部分操作需要): 任务ID
|
|
37
|
+
|
|
38
|
+
## 安装和使用
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 安装依赖
|
|
42
|
+
pnpm install
|
|
43
|
+
|
|
44
|
+
# 构建
|
|
45
|
+
pnpm build
|
|
46
|
+
|
|
47
|
+
# 运行
|
|
48
|
+
pnpm start
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 平台支持
|
|
52
|
+
|
|
53
|
+
- **macOS**: 使用AppleScript的`display notification`
|
|
54
|
+
- **Windows**: 使用PowerShell的BalloonTip
|
|
55
|
+
- **Linux**: 使用`notify-send`
|
|
56
|
+
|
|
57
|
+
## 示例
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"title": "提醒",
|
|
62
|
+
"message": "这是一条测试通知",
|
|
63
|
+
"subtitle": "测试",
|
|
64
|
+
"sound": true,
|
|
65
|
+
"delay": "5s"
|
|
66
|
+
}
|
|
67
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
/**
|
|
9
|
+
* Escapes special characters in strings for AppleScript
|
|
10
|
+
*/
|
|
11
|
+
function escapeString(str) {
|
|
12
|
+
// Escape for both AppleScript and shell
|
|
13
|
+
return str
|
|
14
|
+
.replace(/'/g, "'\\''")
|
|
15
|
+
.replace(/"/g, '\\"');
|
|
16
|
+
}
|
|
17
|
+
// 任务池:存储所有活跃的重复提醒任务
|
|
18
|
+
const repeatNotificationPool = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* 生成唯一任务ID
|
|
21
|
+
*/
|
|
22
|
+
function generateNotificationId() {
|
|
23
|
+
return 'notification_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now().toString(36);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validates notification parameters
|
|
27
|
+
*/
|
|
28
|
+
function validateParams(params) {
|
|
29
|
+
if (!params.title || typeof params.title !== 'string') {
|
|
30
|
+
throw new Error('Title is required and must be a string');
|
|
31
|
+
}
|
|
32
|
+
if (!params.message || typeof params.message !== 'string') {
|
|
33
|
+
throw new Error('Message is required and must be a string');
|
|
34
|
+
}
|
|
35
|
+
if (params.subtitle && typeof params.subtitle !== 'string') {
|
|
36
|
+
throw new Error('Subtitle must be a string');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Builds the AppleScript command for sending a notification
|
|
41
|
+
*/
|
|
42
|
+
function buildNotificationCommand(params) {
|
|
43
|
+
const { title, message, subtitle, sound = true } = params;
|
|
44
|
+
let script = `display notification "${escapeString(message)}" with title "${escapeString(title)}"`;
|
|
45
|
+
if (subtitle) {
|
|
46
|
+
script += ` subtitle "${escapeString(subtitle)}"`;
|
|
47
|
+
}
|
|
48
|
+
if (sound) {
|
|
49
|
+
script += ` sound name "default"`;
|
|
50
|
+
}
|
|
51
|
+
return `osascript -e '${script}'`;
|
|
52
|
+
}
|
|
53
|
+
// 检测操作系统
|
|
54
|
+
function getOS() {
|
|
55
|
+
const currentPlatform = process.platform;
|
|
56
|
+
if (currentPlatform === 'win32')
|
|
57
|
+
return 'windows';
|
|
58
|
+
if (currentPlatform === 'darwin')
|
|
59
|
+
return 'macos';
|
|
60
|
+
return 'linux';
|
|
61
|
+
}
|
|
62
|
+
// Windows 通知命令构建
|
|
63
|
+
function buildWindowsNotificationCommand(params) {
|
|
64
|
+
const { title, message, sound = true } = params;
|
|
65
|
+
// 使用 PowerShell 的 BalloonTip
|
|
66
|
+
let script = `
|
|
67
|
+
Add-Type -AssemblyName System.Windows.Forms;
|
|
68
|
+
$notification = New-Object System.Windows.Forms.NotifyIcon;
|
|
69
|
+
$notification.Icon = [System.Drawing.SystemIcons]::Information;
|
|
70
|
+
$notification.BalloonTipTitle = "${escapeString(title)}";
|
|
71
|
+
$notification.BalloonTipText = "${escapeString(message)}";
|
|
72
|
+
$notification.Visible = $true;
|
|
73
|
+
$notification.ShowBalloonTip(5000);
|
|
74
|
+
`;
|
|
75
|
+
if (sound) {
|
|
76
|
+
script += `
|
|
77
|
+
[System.Media.SystemSounds]::Asterisk.Play();
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
script += `
|
|
81
|
+
Start-Sleep -Seconds 1;
|
|
82
|
+
$notification.Dispose();
|
|
83
|
+
`;
|
|
84
|
+
return `powershell -Command "${script}"`;
|
|
85
|
+
}
|
|
86
|
+
// Linux 通知命令构建
|
|
87
|
+
function buildLinuxNotificationCommand(params) {
|
|
88
|
+
const { title, message, subtitle, sound = true } = params;
|
|
89
|
+
let command = `notify-send "${escapeString(title)}" "${escapeString(message)}"`;
|
|
90
|
+
if (subtitle) {
|
|
91
|
+
// 将 subtitle 添加到消息中,因为 notify-send 不直接支持副标题
|
|
92
|
+
command = `notify-send "${escapeString(title)}" "${escapeString(subtitle)}\n${escapeString(message)}"`;
|
|
93
|
+
}
|
|
94
|
+
// 添加声音支持
|
|
95
|
+
if (sound) {
|
|
96
|
+
command += ` --hint=string:sound-name:message-new-instant`;
|
|
97
|
+
}
|
|
98
|
+
return command;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 解析时间字符串为毫秒数
|
|
102
|
+
*/
|
|
103
|
+
function parseTimeDelay(delay) {
|
|
104
|
+
if (typeof delay === 'number') {
|
|
105
|
+
return delay;
|
|
106
|
+
}
|
|
107
|
+
const timeString = delay.toLowerCase().trim();
|
|
108
|
+
const match = timeString.match(/^(\d+(?:\.\d+)?)\s*([smh]?)$/);
|
|
109
|
+
if (!match) {
|
|
110
|
+
throw new Error('Invalid time format. Use numbers (milliseconds) or strings like "10s", "1m", "1h"');
|
|
111
|
+
}
|
|
112
|
+
const value = parseFloat(match[1]);
|
|
113
|
+
const unit = match[2] || 'ms'; // 默认单位为毫秒
|
|
114
|
+
switch (unit) {
|
|
115
|
+
case 's': return value * 1000; // 秒
|
|
116
|
+
case 'm': return value * 60 * 1000; // 分钟
|
|
117
|
+
case 'h': return value * 60 * 60 * 1000; // 小时
|
|
118
|
+
default: return value; // 毫秒
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Sends a notification using the appropriate platform command
|
|
123
|
+
*/
|
|
124
|
+
async function sendNotification(params) {
|
|
125
|
+
// 如果有 repeat 参数,设置重复提醒
|
|
126
|
+
if (params.repeat !== undefined) {
|
|
127
|
+
const repeatMs = parseTimeDelay(params.repeat);
|
|
128
|
+
if (repeatMs <= 0) {
|
|
129
|
+
throw new Error('Repeat interval must be a positive number');
|
|
130
|
+
}
|
|
131
|
+
const notificationId = generateNotificationId();
|
|
132
|
+
const { repeat, repeatCount, ...notificationParams } = params;
|
|
133
|
+
const maxCount = repeatCount || Infinity;
|
|
134
|
+
// 创建重复发送的函数
|
|
135
|
+
const scheduleNextNotification = (currentCount) => {
|
|
136
|
+
if (currentCount >= maxCount) {
|
|
137
|
+
// 任务完成,从任务池中移除
|
|
138
|
+
repeatNotificationPool.delete(notificationId);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const timeoutId = setTimeout(async () => {
|
|
142
|
+
try {
|
|
143
|
+
// 检查任务是否还在任务池中(可能已被取消)
|
|
144
|
+
const notification = repeatNotificationPool.get(notificationId);
|
|
145
|
+
if (!notification)
|
|
146
|
+
return;
|
|
147
|
+
await sendNotification(notificationParams);
|
|
148
|
+
// 更新任务信息
|
|
149
|
+
notification.currentCount++;
|
|
150
|
+
// 调度下一次通知
|
|
151
|
+
scheduleNextNotification(currentCount + 1);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('Repeated notification failed:', error);
|
|
155
|
+
// 即使失败也继续下一次
|
|
156
|
+
scheduleNextNotification(currentCount + 1);
|
|
157
|
+
}
|
|
158
|
+
}, repeatMs);
|
|
159
|
+
// 更新任务池中的任务信息
|
|
160
|
+
const notification = repeatNotificationPool.get(notificationId);
|
|
161
|
+
if (notification) {
|
|
162
|
+
// 清除旧的timeout
|
|
163
|
+
if (notification.timeoutId) {
|
|
164
|
+
clearTimeout(notification.timeoutId);
|
|
165
|
+
}
|
|
166
|
+
notification.timeoutId = timeoutId;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
// 创建任务并加入任务池
|
|
170
|
+
const notification = {
|
|
171
|
+
id: notificationId,
|
|
172
|
+
params,
|
|
173
|
+
timeoutId: null, // 稍后设置
|
|
174
|
+
currentCount: 0,
|
|
175
|
+
maxCount,
|
|
176
|
+
startTime: Date.now()
|
|
177
|
+
};
|
|
178
|
+
repeatNotificationPool.set(notificationId, notification);
|
|
179
|
+
// 如果有初始延迟,先等待延迟再开始重复
|
|
180
|
+
if (params.delay !== undefined) {
|
|
181
|
+
const delayMs = parseTimeDelay(params.delay);
|
|
182
|
+
notification.timeoutId = setTimeout(() => {
|
|
183
|
+
// 发送第一次通知并开始重复
|
|
184
|
+
sendNotification(notificationParams).then(() => {
|
|
185
|
+
notification.currentCount = 1;
|
|
186
|
+
scheduleNextNotification(1);
|
|
187
|
+
}).catch(error => {
|
|
188
|
+
console.error('Initial repeated notification failed:', error);
|
|
189
|
+
scheduleNextNotification(1);
|
|
190
|
+
});
|
|
191
|
+
}, delayMs);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// 没有初始延迟,立即开始第一次通知
|
|
195
|
+
try {
|
|
196
|
+
await sendNotification(notificationParams);
|
|
197
|
+
notification.currentCount = 1;
|
|
198
|
+
scheduleNextNotification(1);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
console.error('Initial repeated notification failed:', error);
|
|
202
|
+
scheduleNextNotification(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
notificationId,
|
|
207
|
+
message: `Repeat notification notification created with ID: ${notificationId}`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// 如果有 delay 参数但没有 repeat,使用 setTimeout 延迟发送
|
|
211
|
+
if (params.delay !== undefined) {
|
|
212
|
+
const delayMs = parseTimeDelay(params.delay);
|
|
213
|
+
if (delayMs <= 0) {
|
|
214
|
+
throw new Error('Delay must be a positive number');
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
// 设置延迟任务
|
|
218
|
+
setTimeout(async () => {
|
|
219
|
+
try {
|
|
220
|
+
// 创建不包含 delay 的参数对象,避免无限递归
|
|
221
|
+
const { delay, ...notificationParams } = params;
|
|
222
|
+
await sendNotification(notificationParams);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.error('Delayed notification failed:', error);
|
|
226
|
+
}
|
|
227
|
+
}, delayMs);
|
|
228
|
+
// 立即返回设置成功
|
|
229
|
+
return { message: 'Delayed notification scheduled successfully' };
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
throw new Error('Failed to schedule delayed notification');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// 立即发送通知的逻辑
|
|
236
|
+
try {
|
|
237
|
+
validateParams(params);
|
|
238
|
+
const os = getOS();
|
|
239
|
+
let command;
|
|
240
|
+
switch (os) {
|
|
241
|
+
case 'macos':
|
|
242
|
+
command = buildNotificationCommand(params);
|
|
243
|
+
break;
|
|
244
|
+
case 'windows':
|
|
245
|
+
command = buildWindowsNotificationCommand(params);
|
|
246
|
+
break;
|
|
247
|
+
case 'linux':
|
|
248
|
+
command = buildLinuxNotificationCommand(params);
|
|
249
|
+
break;
|
|
250
|
+
default:
|
|
251
|
+
throw new Error(`Unsupported platform: ${os}`);
|
|
252
|
+
}
|
|
253
|
+
await execAsync(command);
|
|
254
|
+
return { message: 'Notification sent successfully' };
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error instanceof Error) {
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
// Handle different types of system errors
|
|
261
|
+
const err = error;
|
|
262
|
+
if (err.message.includes('execution error')) {
|
|
263
|
+
throw new Error('Failed to execute notification command');
|
|
264
|
+
}
|
|
265
|
+
else if (err.message.includes('permission')) {
|
|
266
|
+
throw new Error('Permission denied when trying to send notification');
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
throw new Error(`Unexpected error: ${err.message}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 停止指定的重复提醒任务
|
|
275
|
+
*/
|
|
276
|
+
function stopRepeatNotification(notificationId) {
|
|
277
|
+
const notification = repeatNotificationPool.get(notificationId);
|
|
278
|
+
if (!notification) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
// 清除定时器
|
|
282
|
+
if (notification.timeoutId) {
|
|
283
|
+
clearTimeout(notification.timeoutId);
|
|
284
|
+
}
|
|
285
|
+
// 从任务池中移除
|
|
286
|
+
repeatNotificationPool.delete(notificationId);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 停止所有重复提醒任务
|
|
291
|
+
*/
|
|
292
|
+
function stopAllRepeatNotifications() {
|
|
293
|
+
const count = repeatNotificationPool.size;
|
|
294
|
+
// 清除所有定时器
|
|
295
|
+
for (const notification of repeatNotificationPool.values()) {
|
|
296
|
+
if (notification.timeoutId) {
|
|
297
|
+
clearTimeout(notification.timeoutId);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// 清空任务池
|
|
301
|
+
repeatNotificationPool.clear();
|
|
302
|
+
return count;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* 获取所有活跃的重复提醒任务信息
|
|
306
|
+
*/
|
|
307
|
+
function getActiveRepeatNotifications() {
|
|
308
|
+
return Array.from(repeatNotificationPool.values()).map(notification => ({
|
|
309
|
+
...notification,
|
|
310
|
+
// 不返回timeoutId,避免序列化问题
|
|
311
|
+
timeoutId: null
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 获取指定任务的信息
|
|
316
|
+
*/
|
|
317
|
+
function getRepeatNotificationInfo(notificationId) {
|
|
318
|
+
const notification = repeatNotificationPool.get(notificationId);
|
|
319
|
+
if (!notification) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
...notification,
|
|
324
|
+
// 不返回timeoutId,避免序列化问题
|
|
325
|
+
timeoutId: null
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
class NotificationServer {
|
|
329
|
+
constructor() {
|
|
330
|
+
this.server = new Server({
|
|
331
|
+
name: 'notification-mcp',
|
|
332
|
+
version: '1.0.0',
|
|
333
|
+
}, {
|
|
334
|
+
capabilities: {
|
|
335
|
+
tools: {},
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
this.setupToolHandlers();
|
|
339
|
+
// Error handling
|
|
340
|
+
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
341
|
+
process.on('SIGINT', async () => {
|
|
342
|
+
await this.server.close();
|
|
343
|
+
process.exit(0);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
setupToolHandlers() {
|
|
347
|
+
// List available tools
|
|
348
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
349
|
+
tools: [
|
|
350
|
+
{
|
|
351
|
+
name: 'send_notification',
|
|
352
|
+
description: '发送系统通知',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
title: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description: '通知标题',
|
|
359
|
+
},
|
|
360
|
+
message: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: '通知内容',
|
|
363
|
+
},
|
|
364
|
+
subtitle: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: '可选的副标题',
|
|
367
|
+
},
|
|
368
|
+
sound: {
|
|
369
|
+
type: 'boolean',
|
|
370
|
+
description: '是否播放默认通知声音',
|
|
371
|
+
default: true,
|
|
372
|
+
},
|
|
373
|
+
delay: {
|
|
374
|
+
oneOf: [
|
|
375
|
+
{ type: 'number' },
|
|
376
|
+
{ type: 'string' }
|
|
377
|
+
],
|
|
378
|
+
description: '延迟发送通知(毫秒或时间字符串如"10s", "1m", "1h")',
|
|
379
|
+
},
|
|
380
|
+
repeat: {
|
|
381
|
+
oneOf: [
|
|
382
|
+
{ type: 'number' },
|
|
383
|
+
{ type: 'string' }
|
|
384
|
+
],
|
|
385
|
+
description: '重复通知间隔(毫秒或时间字符串如"10s", "1m", "1h")',
|
|
386
|
+
},
|
|
387
|
+
repeatCount: {
|
|
388
|
+
type: 'number',
|
|
389
|
+
description: '重复次数(可选,如果设置了repeat但未设置此项则无限重复)',
|
|
390
|
+
minimum: 1,
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
required: ['title', 'message'],
|
|
394
|
+
additionalProperties: false,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: 'notification_task_management',
|
|
399
|
+
description: '管理计划的通知任务',
|
|
400
|
+
inputSchema: {
|
|
401
|
+
type: 'object',
|
|
402
|
+
properties: {
|
|
403
|
+
action: {
|
|
404
|
+
type: 'string',
|
|
405
|
+
enum: ['stop_repeat_task', 'stop_all_repeat_tasks', 'get_active_repeat_tasks', 'get_repeat_task_info'],
|
|
406
|
+
description: 'stop_repeat_task: 停止指定的重复通知任务. stop_all_repeat_tasks: 停止所有重复通知任务. get_active_repeat_tasks: 获取所有活跃的重复通知任务. get_repeat_task_info: 获取指定重复通知任务的信息.'
|
|
407
|
+
},
|
|
408
|
+
taskId: {
|
|
409
|
+
type: 'string',
|
|
410
|
+
description: '要管理的任务ID'
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
required: ['action'],
|
|
414
|
+
additionalProperties: false
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
}));
|
|
419
|
+
// Handle tool execution
|
|
420
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
421
|
+
try {
|
|
422
|
+
if (!request.params.arguments || typeof request.params.arguments !== 'object') {
|
|
423
|
+
throw new McpError(ErrorCode.InvalidParams, 'Invalid parameters');
|
|
424
|
+
}
|
|
425
|
+
switch (request.params.name) {
|
|
426
|
+
case 'send_notification': {
|
|
427
|
+
const { title, message, subtitle, sound, delay, repeat, repeatCount } = request.params.arguments;
|
|
428
|
+
if (typeof title !== 'string' || typeof message !== 'string') {
|
|
429
|
+
throw new McpError(ErrorCode.InvalidParams, 'Title and message must be strings');
|
|
430
|
+
}
|
|
431
|
+
const params = {
|
|
432
|
+
title,
|
|
433
|
+
message,
|
|
434
|
+
subtitle: typeof subtitle === 'string' ? subtitle : undefined,
|
|
435
|
+
sound: typeof sound === 'boolean' ? sound : undefined,
|
|
436
|
+
delay: (typeof delay === 'number' || typeof delay === 'string') ? delay : undefined,
|
|
437
|
+
repeat: (typeof repeat === 'number' || typeof repeat === 'string') ? repeat : undefined,
|
|
438
|
+
repeatCount: typeof repeatCount === 'number' ? repeatCount : undefined
|
|
439
|
+
};
|
|
440
|
+
const result = await sendNotification(params);
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: 'text',
|
|
445
|
+
text: result.notificationId ?
|
|
446
|
+
`${result.message}. Task ID: ${result.notificationId}` :
|
|
447
|
+
result.message,
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
case 'notification_task_management': {
|
|
453
|
+
const { action, taskId } = request.params.arguments;
|
|
454
|
+
switch (action) {
|
|
455
|
+
case 'stop_repeat_task': {
|
|
456
|
+
const success = stopRepeatNotification(taskId);
|
|
457
|
+
return {
|
|
458
|
+
content: [
|
|
459
|
+
{
|
|
460
|
+
type: 'text',
|
|
461
|
+
text: success ? `任务 ${taskId} 已成功停止` : `任务 ${taskId} 未找到`,
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
case 'stop_all_repeat_tasks': {
|
|
467
|
+
const count = stopAllRepeatNotifications();
|
|
468
|
+
return {
|
|
469
|
+
content: [
|
|
470
|
+
{
|
|
471
|
+
type: 'text',
|
|
472
|
+
text: `已停止 ${count} 个重复任务`,
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
case 'get_active_repeat_tasks': {
|
|
478
|
+
const tasks = getActiveRepeatNotifications();
|
|
479
|
+
return {
|
|
480
|
+
content: [
|
|
481
|
+
{
|
|
482
|
+
type: 'text',
|
|
483
|
+
text: JSON.stringify(tasks, null, 2),
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
case 'get_repeat_task_info': {
|
|
489
|
+
const info = getRepeatNotificationInfo(taskId);
|
|
490
|
+
return {
|
|
491
|
+
content: [
|
|
492
|
+
{
|
|
493
|
+
type: 'text',
|
|
494
|
+
text: info ? JSON.stringify(info, null, 2) : `任务 ${taskId} 未找到`,
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
default:
|
|
500
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown task management action: ${action}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
default:
|
|
504
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async run() {
|
|
513
|
+
const transport = new StdioServerTransport();
|
|
514
|
+
await this.server.connect(transport);
|
|
515
|
+
console.error('Notification MCP server running on stdio');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const server = new NotificationServer();
|
|
519
|
+
server.run().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcpcn/mcp-notification",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "系统通知MCP服务器",
|
|
5
|
+
"packageManager": "pnpm@8.12.1",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"notification-mcp": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"notification",
|
|
20
|
+
"系统通知",
|
|
21
|
+
"跨平台"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
25
|
+
"start": "node dist/index.js",
|
|
26
|
+
"dev": "tsc -w",
|
|
27
|
+
"clean": "rm -rf build",
|
|
28
|
+
"prepare": "pnpm clean && pnpm build"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.10.2",
|
|
34
|
+
"typescript": "^5.7.2"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
38
|
+
}
|
|
39
|
+
}
|