@mcpcn/mcp-notification 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +167 -67
  2. package/dist/index.js +111 -473
  3. package/package.json +41 -39
package/README.md CHANGED
@@ -1,67 +1,167 @@
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
- ```
1
+ # MCP 通知提醒服务器
2
+
3
+ 一个基于 Model Context Protocol (MCP) 的通知提醒服务器,提供设置提醒、查询提醒列表、取消提醒三种工具,并通过后端接口对接统一的提醒调度与分发。
4
+
5
+ ## 功能特性
6
+
7
+ - 🔔 设置提醒:支持一次性、固定间隔循环、每日循环
8
+ - 📋 查询列表:获取设备的待触发提醒列表(仅 `scheduled`)
9
+ - 取消提醒:按 `id` 取消指定提醒
10
+ - MCP 协议集成:适配各类 MCP 客户端
11
+ - 🌐 可配置后端地址:通过环境变量 `REMINDER_API_BASE` 指定
12
+
13
+ ## 安装
14
+
15
+ ### 前置要求
16
+
17
+ - Node.js >= 18
18
+
19
+ ### 安装依赖
20
+
21
+ ```bash
22
+ npm install
23
+ ```
24
+
25
+ ### 构建项目
26
+
27
+ ```bash
28
+ npm run build
29
+ ```
30
+
31
+ ## 使用方法
32
+
33
+ ### 1. 直接运行
34
+
35
+ ```bash
36
+ npm run start
37
+ # 或
38
+ node dist/index.js
39
+ ```
40
+
41
+ ### 2. 作为 MCP 服务器
42
+
43
+ 在您的 MCP 客户端配置中添加:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "通知提醒": {
49
+ "command": "node",
50
+ "args": ["/path/to/通知提醒新/dist/index.js"],
51
+ "env": {
52
+ "REMINDER_API_BASE": "https://www.rapido.chat/api"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ 或使用已发布命令名(全局安装后):
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "通知提醒": {
65
+ "command": "notification-mcp",
66
+ "args": [],
67
+ "env": {
68
+ "REMINDER_API_BASE": "https://www.mcpcn.cc/api"
69
+ }
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## 工具说明
76
+
77
+ ### set_reminder
78
+
79
+ 设置通知提醒,支持以下模式(请求需携带会话头 `chatSessionId`,详见下文“会话标识”):
80
+
81
+ - 一次性(相对延时)
82
+ ```json
83
+ { "content": "开会", "repeat": "none", "delaySec": 300 }
84
+ ```
85
+
86
+ - 一次性(绝对时间)
87
+ ```json
88
+ { "content": "开会", "repeat": "none", "triggerAt": "2025-11-15T20:00:00+08:00" }
89
+ ```
90
+
91
+ - 间隔循环(每5分钟)
92
+ ```json
93
+ { "content": "喝水", "repeat": "interval", "intervalSec": 300 }
94
+ ```
95
+
96
+ - 每日循环(每天18:00,北京时间)
97
+ ```json
98
+ { "content": "下班打卡", "repeat": "daily", "timeOfDay": "18:00", "tzOffsetMin": 480 }
99
+ ```
100
+
101
+ 参数:
102
+
103
+ - `content` (string, 必需)
104
+ - `repeat` (string, 必需):`none|interval|daily`
105
+ - `delaySec` (number, 可选):一次性延时触发
106
+ - `triggerAt` (string, 可选):一次性绝对时间(RFC3339)
107
+ - `intervalSec` (number, 可选):间隔循环秒数
108
+ - `timeOfDay` (string, 可选):每日循环的时间(如 `18:00` 或 `18:00:00`)
109
+ - `tzOffsetMin` (number, 可选):时区偏移分钟(北京为 `480`)
110
+
111
+ ### list_reminders
112
+
113
+ 查询待触发提醒列表(请求需携带会话头 `chatSessionId`):
114
+
115
+ 参数:无
116
+
117
+ 返回:提醒条目数组(仅 `scheduled` 状态)
118
+
119
+ ### cancel_reminder
120
+
121
+ 取消指定提醒(请求需携带会话头 `chatSessionId`):
122
+
123
+ 参数:
124
+
125
+ - `id` (string, 必需)
126
+
127
+ ## 会话标识
128
+
129
+ MCP 客户端需在调用工具时携带 `meta.chatSessionId`,服务端会自动解析并将其作为 HTTP 请求头 `chatSessionId` 传给后端接口:
130
+
131
+ - 解析来源:`request.meta.chatSessionId` 或 `request.params.meta.chatSessionId`
132
+ - 请求头:`chatSessionId: <meta.chatSessionId>`
133
+
134
+ ## 后端接口
135
+
136
+ 默认后端基地址为 `https://www.mcpcn.cc/api`(可通过 `REMINDER_API_BASE` 修改)。接口为:
137
+
138
+ - 设置提醒:`https://www.mcpcn.cc/api/reminder/set`
139
+ - 列表查询:`https://www.mcpcn.cc/api/reminder/list`
140
+ - 取消提醒:`https://www.mcpcn.cc/api/reminder/cancel`
141
+
142
+ ## 项目结构
143
+
144
+ ```
145
+ 通知提醒新/
146
+ ├── src/
147
+ │ └── index.ts # MCP 服务器实现(工具与接口调用)
148
+ ├── dist/
149
+ │ └── index.js # 构建输出文件
150
+ ├── package.json # 项目配置(main/bin 脚本)
151
+ ├── tsconfig.json # TypeScript 配置
152
+ └── README.md # 使用说明
153
+ ```
154
+
155
+ ## 技术栈
156
+
157
+ - **TypeScript**
158
+ - **Node.js**
159
+ - **@modelcontextprotocol/sdk**
160
+
161
+ ## 许可证
162
+
163
+ MIT License
164
+
165
+ ## 贡献
166
+
167
+ 欢迎提交 Issue 和 Pull Request!
package/dist/index.js CHANGED
@@ -1,519 +1,157 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
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, '\\"');
4
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
5
+ const API_BASE = process.env.REMINDER_API_BASE || 'https://www.rapido.chat/api';
6
+ async function postJson(path, body, chatSessionId) {
7
+ const headers = { 'Content-Type': 'application/json' };
8
+ if (chatSessionId)
9
+ headers['chatSessionId'] = chatSessionId;
10
+ const resp = await fetch(`${API_BASE}${path}`, {
11
+ method: 'POST',
12
+ headers,
13
+ body: JSON.stringify(body),
14
+ });
15
+ if (!resp.ok) {
16
+ throw new Error(`HTTP 错误: ${resp.status} ${resp.statusText}`);
17
+ }
18
+ return (await resp.json());
16
19
  }
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);
20
+ async function getJson(path, chatSessionId) {
21
+ const headers = {};
22
+ if (chatSessionId)
23
+ headers['chatSessionId'] = chatSessionId;
24
+ const resp = await fetch(`${API_BASE}${path}`, { headers });
25
+ if (!resp.ok) {
26
+ throw new Error(`HTTP 错误: ${resp.status} ${resp.statusText}`);
27
+ }
28
+ return (await resp.json());
24
29
  }
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 {
30
+ class ReminderServer {
329
31
  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
32
+ this.server = new Server({ name: 'notification-mcp', version: '1.0.0' }, { capabilities: { tools: {} } });
33
+ this.setupHandlers();
340
34
  this.server.onerror = (error) => console.error('[MCP Error]', error);
341
35
  process.on('SIGINT', async () => {
342
36
  await this.server.close();
343
37
  process.exit(0);
344
38
  });
345
39
  }
346
- setupToolHandlers() {
347
- // List available tools
40
+ setupHandlers() {
348
41
  this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
349
42
  tools: [
350
43
  {
351
- name: 'send_notification',
352
- description: '发送系统通知或提醒',
44
+ name: 'set_reminder',
45
+ description: '设置通知提醒。支持一次性、按间隔循环、每日循环。',
353
46
  inputSchema: {
354
47
  type: 'object',
355
48
  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
- },
49
+ content: { type: 'string' },
50
+ repeat: { type: 'string', enum: ['none', 'interval', 'daily'] },
51
+ delaySec: { type: 'number' },
52
+ triggerAt: { type: 'string', description: 'RFC3339 时间,例如 2025-11-15T20:00:00+08:00' },
53
+ intervalSec: { type: 'number' },
54
+ timeOfDay: { type: 'string', description: '例如 18:00 或 18:00:00' },
55
+ tzOffsetMin: { type: 'number', description: '时区偏移分钟,例如北京为 480' },
392
56
  },
393
- required: ['title', 'message'],
57
+ required: ['content', 'repeat'],
394
58
  additionalProperties: false,
395
59
  },
396
60
  },
397
61
  {
398
- name: 'notification_task_management',
399
- description: '管理计划的通知或提醒任务',
62
+ name: 'list_reminders',
63
+ description: '获取设备的提醒列表(仅未触发的 scheduled)。',
400
64
  inputSchema: {
401
65
  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
- }
66
+ properties: {},
67
+ required: [],
68
+ additionalProperties: false,
69
+ },
70
+ },
71
+ {
72
+ name: 'cancel_reminder',
73
+ description: '取消指定提醒。',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: { id: { type: 'string' } },
77
+ required: ['id'],
78
+ additionalProperties: false,
79
+ },
416
80
  },
417
81
  ],
418
82
  }));
419
- // Handle tool execution
420
83
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
421
84
  try {
422
85
  if (!request.params.arguments || typeof request.params.arguments !== 'object') {
423
- throw new McpError(ErrorCode.InvalidParams, 'Invalid parameters');
86
+ throw new McpError(ErrorCode.InvalidParams, '无效的参数');
87
+ }
88
+ const name = request.params.name;
89
+ const args = request.params.arguments;
90
+ const chatSessionId = request?.meta?.chatSessionId ??
91
+ request?.params?.meta?.chatSessionId ??
92
+ request?.params?.arguments?.meta?.chatSessionId;
93
+ if (!chatSessionId) {
94
+ console.error('未在请求中检测到 chatSessionId(meta.chatSessionId)');
95
+ }
96
+ else {
97
+ console.error(`接收到 chatSessionId: ${chatSessionId}`);
424
98
  }
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
- };
99
+ if (name === 'set_reminder') {
100
+ const params = {
101
+ content: String(args.content || ''),
102
+ repeat: String(args.repeat || ''),
103
+ delaySec: args.delaySec,
104
+ triggerAt: args.triggerAt,
105
+ intervalSec: args.intervalSec,
106
+ timeOfDay: args.timeOfDay,
107
+ tzOffsetMin: args.tzOffsetMin,
108
+ };
109
+ const resp = await postJson('/reminder/set', params, chatSessionId);
110
+ if (resp.code !== 0) {
111
+ return { content: [{ type: 'text', text: `设置失败:${resp.msg}` }], isError: true };
451
112
  }
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
- }
113
+ return {
114
+ content: [
115
+ {
116
+ type: 'text',
117
+ text: JSON.stringify({ id: resp.data?.id, triggerAt: resp.data?.triggerAt, msg: resp.msg || '设置成功' }, null, 2),
118
+ },
119
+ ],
120
+ isError: false,
121
+ };
122
+ }
123
+ if (name === 'list_reminders') {
124
+ const resp = await getJson(`/reminder/list`, chatSessionId);
125
+ if (resp.code !== 0) {
126
+ return { content: [{ type: 'text', text: `获取失败:${resp.msg}` }], isError: true };
127
+ }
128
+ return {
129
+ content: [{ type: 'text', text: JSON.stringify(resp.data?.list ?? [], null, 2) }],
130
+ isError: false,
131
+ };
132
+ }
133
+ if (name === 'cancel_reminder') {
134
+ const params = { id: String(args.id || '') };
135
+ const resp = await postJson(`/reminder/cancel`, params, chatSessionId);
136
+ if (resp.code !== 0) {
137
+ return { content: [{ type: 'text', text: `取消失败:${resp.msg}` }], isError: true };
502
138
  }
503
- default:
504
- throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
139
+ return { content: [{ type: 'text', text: resp.msg || '取消成功' }], isError: false };
505
140
  }
141
+ throw new McpError(ErrorCode.MethodNotFound, `未知工具: ${name}`);
506
142
  }
507
143
  catch (error) {
508
- throw error;
144
+ if (error instanceof McpError)
145
+ throw error;
146
+ throw new McpError(ErrorCode.InternalError, `执行失败: ${error.message}`);
509
147
  }
510
148
  });
511
149
  }
512
150
  async run() {
513
151
  const transport = new StdioServerTransport();
514
152
  await this.server.connect(transport);
515
- console.error('Notification MCP server running on stdio');
153
+ console.error('Reminder MCP server running on stdio');
516
154
  }
517
155
  }
518
- const server = new NotificationServer();
156
+ const server = new ReminderServer();
519
157
  server.run().catch(console.error);
package/package.json CHANGED
@@ -1,39 +1,41 @@
1
- {
2
- "name": "@mcpcn/mcp-notification",
3
- "version": "1.0.3",
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
- }
1
+ {
2
+ "name": "@mcpcn/mcp-notification",
3
+ "version": "1.0.5",
4
+ "description": "系统通知MCP服务器",
5
+ "packageManager": "yarn@1.22.22",
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
+ "schedule",
22
+ "interval",
23
+ "daily"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc && node -e \"try{require('fs').chmodSync('dist/index.js',0o755)}catch(e){}\"",
27
+ "start": "node dist/index.js",
28
+ "dev": "tsc -w",
29
+ "clean": "node -e \"try{require('fs').rmSync('dist',{recursive:true,force:true})}catch(e){}\"",
30
+ "prepare": "npm run clean && npm run build"
31
+ },
32
+ "type": "module",
33
+ "license": "MIT",
34
+ "devDependencies": {
35
+ "@types/node": "^22.10.2",
36
+ "typescript": "^5.7.2"
37
+ },
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.0.4"
40
+ }
41
+ }