@mcpcn/mcp-notification 1.0.4 → 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.
- package/README.md +167 -67
- package/dist/index.js +113 -641
- package/package.json +41 -39
package/README.md
CHANGED
|
@@ -1,67 +1,167 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## 功能特性
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
###
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
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,685 +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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
catch {
|
|
32
|
-
return { stopAll: false, canceledIds: [] };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function writeCancelState(state) {
|
|
36
|
-
ensureStateDir();
|
|
37
|
-
fs.writeFileSync(GLOBAL_CANCEL_FILE, JSON.stringify(state), 'utf8');
|
|
38
|
-
}
|
|
39
|
-
function isGlobalStopAll() {
|
|
40
|
-
return Boolean(readCancelState().stopAll);
|
|
41
|
-
}
|
|
42
|
-
function isGloballyCanceled(id) {
|
|
43
|
-
const state = readCancelState();
|
|
44
|
-
return Boolean(state.stopAll) || (state.canceledIds || []).includes(id);
|
|
45
|
-
}
|
|
46
|
-
function setGlobalStopAll(flag) {
|
|
47
|
-
const state = readCancelState();
|
|
48
|
-
state.stopAll = flag;
|
|
49
|
-
writeCancelState(state);
|
|
50
|
-
}
|
|
51
|
-
function cancelTaskGlobally(id) {
|
|
52
|
-
const state = readCancelState();
|
|
53
|
-
const set = new Set(state.canceledIds || []);
|
|
54
|
-
set.add(id);
|
|
55
|
-
state.canceledIds = Array.from(set);
|
|
56
|
-
writeCancelState(state);
|
|
57
|
-
}
|
|
58
|
-
function clearGlobalCancel() {
|
|
59
|
-
writeCancelState({ stopAll: false, canceledIds: [] });
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Escapes special characters in strings for AppleScript
|
|
63
|
-
*/
|
|
64
|
-
function escapeString(str) {
|
|
65
|
-
// Escape for both AppleScript and shell
|
|
66
|
-
return str
|
|
67
|
-
.replace(/'/g, "'\\''")
|
|
68
|
-
.replace(/"/g, '\\"');
|
|
69
|
-
}
|
|
70
|
-
// 任务池:存储所有活跃的重复提醒任务
|
|
71
|
-
const repeatNotificationPool = new Map();
|
|
72
|
-
/**
|
|
73
|
-
* 生成唯一任务ID
|
|
74
|
-
*/
|
|
75
|
-
function generateNotificationId() {
|
|
76
|
-
return 'notification_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now().toString(36);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Validates notification parameters
|
|
80
|
-
*/
|
|
81
|
-
function validateParams(params) {
|
|
82
|
-
if (!params.title || typeof params.title !== 'string') {
|
|
83
|
-
throw new Error('Title is required and must be a string');
|
|
84
|
-
}
|
|
85
|
-
if (!params.message || typeof params.message !== 'string') {
|
|
86
|
-
throw new Error('Message is required and must be a string');
|
|
87
|
-
}
|
|
88
|
-
if (params.subtitle && typeof params.subtitle !== 'string') {
|
|
89
|
-
throw new Error('Subtitle must be a string');
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Builds the AppleScript command for sending a notification
|
|
94
|
-
*/
|
|
95
|
-
function buildNotificationCommand(params) {
|
|
96
|
-
const { title, message, subtitle, sound = true } = params;
|
|
97
|
-
let script = `display notification "${escapeString(message)}" with title "${escapeString(title)}"`;
|
|
98
|
-
if (subtitle) {
|
|
99
|
-
script += ` subtitle "${escapeString(subtitle)}"`;
|
|
100
|
-
}
|
|
101
|
-
if (sound) {
|
|
102
|
-
script += ` sound name "default"`;
|
|
103
|
-
}
|
|
104
|
-
return `osascript -e '${script}'`;
|
|
105
|
-
}
|
|
106
|
-
// 检测操作系统
|
|
107
|
-
function getOS() {
|
|
108
|
-
const currentPlatform = process.platform;
|
|
109
|
-
if (currentPlatform === 'win32')
|
|
110
|
-
return 'windows';
|
|
111
|
-
if (currentPlatform === 'darwin')
|
|
112
|
-
return 'macos';
|
|
113
|
-
return 'linux';
|
|
114
|
-
}
|
|
115
|
-
// Windows 通知命令构建
|
|
116
|
-
function buildWindowsNotificationCommand(params) {
|
|
117
|
-
const { title, message, sound = true } = params;
|
|
118
|
-
// 使用 PowerShell 的 BalloonTip
|
|
119
|
-
let script = `
|
|
120
|
-
Add-Type -AssemblyName System.Windows.Forms;
|
|
121
|
-
$notification = New-Object System.Windows.Forms.NotifyIcon;
|
|
122
|
-
$notification.Icon = [System.Drawing.SystemIcons]::Information;
|
|
123
|
-
$notification.BalloonTipTitle = "${escapeString(title)}";
|
|
124
|
-
$notification.BalloonTipText = "${escapeString(message)}";
|
|
125
|
-
$notification.Visible = $true;
|
|
126
|
-
$notification.ShowBalloonTip(5000);
|
|
127
|
-
`;
|
|
128
|
-
if (sound) {
|
|
129
|
-
script += `
|
|
130
|
-
[System.Media.SystemSounds]::Asterisk.Play();
|
|
131
|
-
`;
|
|
132
|
-
}
|
|
133
|
-
script += `
|
|
134
|
-
Start-Sleep -Seconds 1;
|
|
135
|
-
$notification.Dispose();
|
|
136
|
-
`;
|
|
137
|
-
return `powershell -Command "${script}"`;
|
|
138
|
-
}
|
|
139
|
-
// Linux 通知命令构建
|
|
140
|
-
function buildLinuxNotificationCommand(params) {
|
|
141
|
-
const { title, message, subtitle, sound = true } = params;
|
|
142
|
-
let command = `notify-send "${escapeString(title)}" "${escapeString(message)}"`;
|
|
143
|
-
if (subtitle) {
|
|
144
|
-
// 将 subtitle 添加到消息中,因为 notify-send 不直接支持副标题
|
|
145
|
-
command = `notify-send "${escapeString(title)}" "${escapeString(subtitle)}\n${escapeString(message)}"`;
|
|
146
|
-
}
|
|
147
|
-
// 添加声音支持
|
|
148
|
-
if (sound) {
|
|
149
|
-
command += ` --hint=string:sound-name:message-new-instant`;
|
|
150
|
-
}
|
|
151
|
-
return command;
|
|
152
|
-
}
|
|
153
|
-
// 直接执行:macOS 通知
|
|
154
|
-
async function executeMacNotification(params) {
|
|
155
|
-
const command = buildNotificationCommand(params);
|
|
156
|
-
await execAsync(command);
|
|
157
|
-
}
|
|
158
|
-
// 直接执行:Windows 通知
|
|
159
|
-
async function executeWindowsNotification(params) {
|
|
160
|
-
const { title, message, sound = true } = params;
|
|
161
|
-
let script = `
|
|
162
|
-
Add-Type -AssemblyName System.Windows.Forms;
|
|
163
|
-
Add-Type -AssemblyName System.Drawing;
|
|
164
|
-
$notification = New-Object System.Windows.Forms.NotifyIcon;
|
|
165
|
-
$notification.Icon = [System.Drawing.SystemIcons]::Information;
|
|
166
|
-
$notification.BalloonTipTitle = "${escapeString(title)}";
|
|
167
|
-
$notification.BalloonTipText = "${escapeString(message)}";
|
|
168
|
-
$notification.Visible = $true;
|
|
169
|
-
$notification.ShowBalloonTip(5000);
|
|
170
|
-
`;
|
|
171
|
-
if (sound) {
|
|
172
|
-
script += `
|
|
173
|
-
[System.Media.SystemSounds]::Asterisk.Play();
|
|
174
|
-
`;
|
|
175
|
-
}
|
|
176
|
-
script += `
|
|
177
|
-
$sw = [Diagnostics.Stopwatch]::StartNew();
|
|
178
|
-
while ($sw.ElapsedMilliseconds -lt 6000) {
|
|
179
|
-
[System.Windows.Forms.Application]::DoEvents();
|
|
180
|
-
Start-Sleep -Milliseconds 100;
|
|
181
|
-
}
|
|
182
|
-
$notification.Dispose();
|
|
183
|
-
`;
|
|
184
|
-
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
185
|
-
const command = `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
186
|
-
await execAsync(command);
|
|
187
|
-
}
|
|
188
|
-
// 直接执行:Linux 通知
|
|
189
|
-
async function executeLinuxNotification(params) {
|
|
190
|
-
const command = buildLinuxNotificationCommand(params);
|
|
191
|
-
await execAsync(command);
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* 解析时间字符串为毫秒数
|
|
195
|
-
*/
|
|
196
|
-
function parseTimeDelay(delay) {
|
|
197
|
-
if (typeof delay === 'number') {
|
|
198
|
-
return delay;
|
|
199
|
-
}
|
|
200
|
-
const timeString = delay.toLowerCase().trim();
|
|
201
|
-
const match = timeString.match(/^(\d+(?:\.\d+)?)\s*([smh]?)$/);
|
|
202
|
-
if (!match) {
|
|
203
|
-
throw new Error('Invalid time format. Use numbers (milliseconds) or strings like "10s", "1m", "1h"');
|
|
204
|
-
}
|
|
205
|
-
const value = parseFloat(match[1]);
|
|
206
|
-
const unit = match[2] || 'ms'; // 默认单位为毫秒
|
|
207
|
-
switch (unit) {
|
|
208
|
-
case 's': return value * 1000; // 秒
|
|
209
|
-
case 'm': return value * 60 * 1000; // 分钟
|
|
210
|
-
case 'h': return value * 60 * 60 * 1000; // 小时
|
|
211
|
-
default: return value; // 毫秒
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Sends a notification using the appropriate platform command
|
|
216
|
-
*/
|
|
217
|
-
async function sendNotification(params) {
|
|
218
|
-
// 如果有 repeat 参数,设置重复提醒
|
|
219
|
-
if (params.repeat !== undefined) {
|
|
220
|
-
const repeatMs = parseTimeDelay(params.repeat);
|
|
221
|
-
if (repeatMs <= 0) {
|
|
222
|
-
throw new Error('Repeat interval must be a positive number');
|
|
223
|
-
}
|
|
224
|
-
const notificationId = generateNotificationId();
|
|
225
|
-
const { repeat, repeatCount, ...notificationParams } = params;
|
|
226
|
-
const maxCount = repeatCount || Infinity;
|
|
227
|
-
// 创建重复发送的函数
|
|
228
|
-
const scheduleNextNotification = (currentCount) => {
|
|
229
|
-
if (currentCount >= maxCount) {
|
|
230
|
-
// 任务完成,从任务池中移除
|
|
231
|
-
repeatNotificationPool.delete(notificationId);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const timeoutId = setTimeout(async () => {
|
|
235
|
-
try {
|
|
236
|
-
// 全局取消检查(支持重启后仍能停止)
|
|
237
|
-
if (isGloballyCanceled(notificationId)) {
|
|
238
|
-
// 从任务池中移除并停止
|
|
239
|
-
repeatNotificationPool.delete(notificationId);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
// 检查任务是否还在任务池中(可能已被取消)
|
|
243
|
-
const notification = repeatNotificationPool.get(notificationId);
|
|
244
|
-
if (!notification)
|
|
245
|
-
return;
|
|
246
|
-
await sendNotification(notificationParams);
|
|
247
|
-
// 更新任务信息
|
|
248
|
-
notification.currentCount++;
|
|
249
|
-
// 调度下一次通知(再次检查 stopAll)
|
|
250
|
-
if (!isGlobalStopAll()) {
|
|
251
|
-
scheduleNextNotification(currentCount + 1);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
console.error('Repeated notification failed:', error);
|
|
256
|
-
// 即使失败也继续下一次
|
|
257
|
-
if (!isGlobalStopAll()) {
|
|
258
|
-
scheduleNextNotification(currentCount + 1);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}, repeatMs);
|
|
262
|
-
// 更新任务池中的任务信息
|
|
263
|
-
const notification = repeatNotificationPool.get(notificationId);
|
|
264
|
-
if (notification) {
|
|
265
|
-
// 清除旧的timeout
|
|
266
|
-
if (notification.timeoutId) {
|
|
267
|
-
clearTimeout(notification.timeoutId);
|
|
268
|
-
}
|
|
269
|
-
notification.timeoutId = timeoutId;
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
// 创建任务并加入任务池
|
|
273
|
-
const notification = {
|
|
274
|
-
id: notificationId,
|
|
275
|
-
params,
|
|
276
|
-
timeoutId: null, // 稍后设置
|
|
277
|
-
currentCount: 0,
|
|
278
|
-
maxCount,
|
|
279
|
-
startTime: Date.now()
|
|
280
|
-
};
|
|
281
|
-
repeatNotificationPool.set(notificationId, notification);
|
|
282
|
-
// 如果有初始延迟,先等待延迟再开始重复
|
|
283
|
-
if (params.delay !== undefined) {
|
|
284
|
-
const delayMs = parseTimeDelay(params.delay);
|
|
285
|
-
notification.timeoutId = setTimeout(() => {
|
|
286
|
-
// 发送第一次通知并开始重复
|
|
287
|
-
if (isGloballyCanceled(notificationId)) {
|
|
288
|
-
repeatNotificationPool.delete(notificationId);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
sendNotification(notificationParams).then(() => {
|
|
292
|
-
notification.currentCount = 1;
|
|
293
|
-
scheduleNextNotification(1);
|
|
294
|
-
}).catch(error => {
|
|
295
|
-
console.error('Initial repeated notification failed:', error);
|
|
296
|
-
scheduleNextNotification(1);
|
|
297
|
-
});
|
|
298
|
-
}, delayMs);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
// 没有初始延迟,立即开始第一次通知
|
|
302
|
-
try {
|
|
303
|
-
if (isGloballyCanceled(notificationId)) {
|
|
304
|
-
repeatNotificationPool.delete(notificationId);
|
|
305
|
-
return { message: 'Notification canceled before start' };
|
|
306
|
-
}
|
|
307
|
-
await sendNotification(notificationParams);
|
|
308
|
-
notification.currentCount = 1;
|
|
309
|
-
scheduleNextNotification(1);
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
console.error('Initial repeated notification failed:', error);
|
|
313
|
-
scheduleNextNotification(1);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return {
|
|
317
|
-
notificationId,
|
|
318
|
-
message: `Repeat notification notification created with ID: ${notificationId}`
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
// 如果有 delay 参数但没有 repeat,使用 setTimeout 延迟发送
|
|
322
|
-
if (params.delay !== undefined) {
|
|
323
|
-
const delayMs = parseTimeDelay(params.delay);
|
|
324
|
-
if (delayMs <= 0) {
|
|
325
|
-
throw new Error('Delay must be a positive number');
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
// 生成任务ID,并加入任务池,便于后续管理/取消
|
|
329
|
-
const notificationId = generateNotificationId();
|
|
330
|
-
const record = {
|
|
331
|
-
id: notificationId,
|
|
332
|
-
params,
|
|
333
|
-
timeoutId: null,
|
|
334
|
-
currentCount: 0,
|
|
335
|
-
maxCount: 1,
|
|
336
|
-
startTime: Date.now(),
|
|
337
|
-
};
|
|
338
|
-
repeatNotificationPool.set(notificationId, record);
|
|
339
|
-
const timeoutId = setTimeout(async () => {
|
|
340
|
-
try {
|
|
341
|
-
if (isGloballyCanceled(notificationId)) {
|
|
342
|
-
repeatNotificationPool.delete(notificationId);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
// 如果任务已被取消并从池中移除,则不再执行
|
|
346
|
-
const exists = repeatNotificationPool.get(notificationId);
|
|
347
|
-
if (!exists)
|
|
348
|
-
return;
|
|
349
|
-
// 创建不包含 delay 的参数对象,避免无限递归
|
|
350
|
-
const { delay, ...notificationParams } = params;
|
|
351
|
-
await sendNotification(notificationParams);
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
console.error('Delayed notification failed:', error);
|
|
355
|
-
}
|
|
356
|
-
finally {
|
|
357
|
-
// 单次延迟任务执行完成后从任务池移除
|
|
358
|
-
repeatNotificationPool.delete(notificationId);
|
|
359
|
-
}
|
|
360
|
-
}, delayMs);
|
|
361
|
-
// 回填 timeoutId
|
|
362
|
-
record.timeoutId = timeoutId;
|
|
363
|
-
// 返回任务ID,便于调用方管理
|
|
364
|
-
return { notificationId, message: 'Delayed notification scheduled successfully' };
|
|
365
|
-
}
|
|
366
|
-
catch (error) {
|
|
367
|
-
throw new Error('Failed to schedule delayed notification');
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
// 立即发送通知的逻辑
|
|
371
|
-
try {
|
|
372
|
-
validateParams(params);
|
|
373
|
-
const os = getOS();
|
|
374
|
-
switch (os) {
|
|
375
|
-
case 'macos':
|
|
376
|
-
await executeMacNotification(params);
|
|
377
|
-
break;
|
|
378
|
-
case 'windows':
|
|
379
|
-
await executeWindowsNotification(params);
|
|
380
|
-
break;
|
|
381
|
-
case 'linux':
|
|
382
|
-
await executeLinuxNotification(params);
|
|
383
|
-
break;
|
|
384
|
-
default:
|
|
385
|
-
throw new Error(`Unsupported platform: ${os}`);
|
|
386
|
-
}
|
|
387
|
-
return { message: 'Notification sent successfully' };
|
|
388
|
-
}
|
|
389
|
-
catch (error) {
|
|
390
|
-
if (error instanceof Error) {
|
|
391
|
-
throw error;
|
|
392
|
-
}
|
|
393
|
-
// Handle different types of system errors
|
|
394
|
-
const err = error;
|
|
395
|
-
if (err.message.includes('execution error')) {
|
|
396
|
-
throw new Error('Failed to execute notification command');
|
|
397
|
-
}
|
|
398
|
-
else if (err.message.includes('permission')) {
|
|
399
|
-
throw new Error('Permission denied when trying to send notification');
|
|
400
|
-
}
|
|
401
|
-
else {
|
|
402
|
-
throw new Error(`Unexpected error: ${err.message}`);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* 停止指定的重复提醒任务
|
|
408
|
-
*/
|
|
409
|
-
function stopRepeatNotification(notificationId) {
|
|
410
|
-
const notification = repeatNotificationPool.get(notificationId);
|
|
411
|
-
if (!notification) {
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
414
|
-
// 清除定时器
|
|
415
|
-
if (notification.timeoutId) {
|
|
416
|
-
clearTimeout(notification.timeoutId);
|
|
417
|
-
}
|
|
418
|
-
// 从任务池中移除
|
|
419
|
-
repeatNotificationPool.delete(notificationId);
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* 停止所有重复提醒任务
|
|
424
|
-
*/
|
|
425
|
-
function stopAllRepeatNotifications() {
|
|
426
|
-
const count = repeatNotificationPool.size;
|
|
427
|
-
// 清除所有定时器
|
|
428
|
-
for (const notification of repeatNotificationPool.values()) {
|
|
429
|
-
if (notification.timeoutId) {
|
|
430
|
-
clearTimeout(notification.timeoutId);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
// 清空任务池
|
|
434
|
-
repeatNotificationPool.clear();
|
|
435
|
-
return count;
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* 获取所有活跃的重复提醒任务信息
|
|
439
|
-
*/
|
|
440
|
-
function getActiveRepeatNotifications() {
|
|
441
|
-
return Array.from(repeatNotificationPool.values()).map(notification => ({
|
|
442
|
-
...notification,
|
|
443
|
-
// 不返回timeoutId,避免序列化问题
|
|
444
|
-
timeoutId: null
|
|
445
|
-
}));
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* 获取指定任务的信息
|
|
449
|
-
*/
|
|
450
|
-
function getRepeatNotificationInfo(notificationId) {
|
|
451
|
-
const notification = repeatNotificationPool.get(notificationId);
|
|
452
|
-
if (!notification) {
|
|
453
|
-
return null;
|
|
454
|
-
}
|
|
455
|
-
return {
|
|
456
|
-
...notification,
|
|
457
|
-
// 不返回timeoutId,避免序列化问题
|
|
458
|
-
timeoutId: null
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
class NotificationServer {
|
|
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());
|
|
19
|
+
}
|
|
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());
|
|
29
|
+
}
|
|
30
|
+
class ReminderServer {
|
|
462
31
|
constructor() {
|
|
463
|
-
this.server = new Server({
|
|
464
|
-
|
|
465
|
-
version: '1.0.0',
|
|
466
|
-
}, {
|
|
467
|
-
capabilities: {
|
|
468
|
-
tools: {},
|
|
469
|
-
},
|
|
470
|
-
});
|
|
471
|
-
this.setupToolHandlers();
|
|
472
|
-
// Error handling
|
|
32
|
+
this.server = new Server({ name: 'notification-mcp', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
33
|
+
this.setupHandlers();
|
|
473
34
|
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
474
35
|
process.on('SIGINT', async () => {
|
|
475
36
|
await this.server.close();
|
|
476
37
|
process.exit(0);
|
|
477
38
|
});
|
|
478
39
|
}
|
|
479
|
-
|
|
480
|
-
// List available tools
|
|
40
|
+
setupHandlers() {
|
|
481
41
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
482
42
|
tools: [
|
|
483
43
|
{
|
|
484
|
-
name: '
|
|
485
|
-
description: '
|
|
44
|
+
name: 'set_reminder',
|
|
45
|
+
description: '设置通知提醒。支持一次性、按间隔循环、每日循环。',
|
|
486
46
|
inputSchema: {
|
|
487
47
|
type: 'object',
|
|
488
48
|
properties: {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
},
|
|
497
|
-
subtitle: {
|
|
498
|
-
type: 'string',
|
|
499
|
-
description: '可选的副标题',
|
|
500
|
-
},
|
|
501
|
-
sound: {
|
|
502
|
-
type: 'boolean',
|
|
503
|
-
description: '是否播放默认提示音',
|
|
504
|
-
default: true,
|
|
505
|
-
},
|
|
506
|
-
delay: {
|
|
507
|
-
oneOf: [
|
|
508
|
-
{ type: 'number' },
|
|
509
|
-
{ type: 'string' }
|
|
510
|
-
],
|
|
511
|
-
description: '延迟发送通知或提醒(毫秒或时间字符串如"10s", "1m", "1h")',
|
|
512
|
-
},
|
|
513
|
-
repeat: {
|
|
514
|
-
oneOf: [
|
|
515
|
-
{ type: 'number' },
|
|
516
|
-
{ type: 'string' }
|
|
517
|
-
],
|
|
518
|
-
description: '重复通知或提醒的间隔(毫秒或时间字符串如"10s", "1m", "1h")',
|
|
519
|
-
},
|
|
520
|
-
repeatCount: {
|
|
521
|
-
type: 'number',
|
|
522
|
-
description: '重复次数(可选,如果设置了repeat但未设置此项则无限重复)',
|
|
523
|
-
minimum: 1,
|
|
524
|
-
},
|
|
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' },
|
|
525
56
|
},
|
|
526
|
-
required: ['
|
|
57
|
+
required: ['content', 'repeat'],
|
|
527
58
|
additionalProperties: false,
|
|
528
59
|
},
|
|
529
60
|
},
|
|
530
61
|
{
|
|
531
|
-
name: '
|
|
532
|
-
description: '
|
|
62
|
+
name: 'list_reminders',
|
|
63
|
+
description: '获取设备的提醒列表(仅未触发的 scheduled)。',
|
|
533
64
|
inputSchema: {
|
|
534
65
|
type: 'object',
|
|
535
|
-
properties: {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
},
|
|
546
|
-
required: ['
|
|
547
|
-
additionalProperties: false
|
|
548
|
-
}
|
|
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
|
+
},
|
|
549
80
|
},
|
|
550
81
|
],
|
|
551
82
|
}));
|
|
552
|
-
// Handle tool execution
|
|
553
83
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
554
84
|
try {
|
|
555
85
|
if (!request.params.arguments || typeof request.params.arguments !== 'object') {
|
|
556
|
-
throw new McpError(ErrorCode.InvalidParams, '
|
|
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)');
|
|
557
95
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return {
|
|
575
|
-
content: [
|
|
576
|
-
{
|
|
577
|
-
type: 'text',
|
|
578
|
-
text: result.notificationId ?
|
|
579
|
-
`${result.message}. Task ID: ${result.notificationId}` :
|
|
580
|
-
result.message,
|
|
581
|
-
},
|
|
582
|
-
],
|
|
583
|
-
isError: false,
|
|
584
|
-
};
|
|
96
|
+
else {
|
|
97
|
+
console.error(`接收到 chatSessionId: ${chatSessionId}`);
|
|
98
|
+
}
|
|
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 };
|
|
585
112
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
case 'stop_repeat_task_globally': {
|
|
614
|
-
if (typeof taskId !== 'string') {
|
|
615
|
-
throw new McpError(ErrorCode.InvalidParams, 'taskId is required for stop_repeat_task_globally');
|
|
616
|
-
}
|
|
617
|
-
cancelTaskGlobally(taskId);
|
|
618
|
-
// 也尝试本进程内停止
|
|
619
|
-
const success = stopRepeatNotification(taskId);
|
|
620
|
-
return {
|
|
621
|
-
content: [{ type: 'text', text: `已全局标记停止任务 ${taskId}` + (success ? '(并停止本进程任务)' : '') }],
|
|
622
|
-
isError: false,
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
case 'stop_all_repeat_tasks_globally': {
|
|
626
|
-
setGlobalStopAll(true);
|
|
627
|
-
const count = stopAllRepeatNotifications();
|
|
628
|
-
return {
|
|
629
|
-
content: [{ type: 'text', text: `已全局标记停止所有重复任务,并停止本进程内 ${count} 个任务` }],
|
|
630
|
-
isError: false,
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
case 'clear_global_state': {
|
|
634
|
-
clearGlobalCancel();
|
|
635
|
-
return { content: [{ type: 'text', text: '已清除全局停止状态' }], isError: false };
|
|
636
|
-
}
|
|
637
|
-
case 'get_active_repeat_tasks': {
|
|
638
|
-
const tasks = getActiveRepeatNotifications();
|
|
639
|
-
return {
|
|
640
|
-
content: [
|
|
641
|
-
{
|
|
642
|
-
type: 'text',
|
|
643
|
-
text: JSON.stringify(tasks, null, 2),
|
|
644
|
-
},
|
|
645
|
-
],
|
|
646
|
-
isError: false,
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
case 'get_repeat_task_info': {
|
|
650
|
-
const info = getRepeatNotificationInfo(taskId);
|
|
651
|
-
return {
|
|
652
|
-
content: [
|
|
653
|
-
{
|
|
654
|
-
type: 'text',
|
|
655
|
-
text: info ? JSON.stringify(info, null, 2) : `任务 ${taskId} 未找到`,
|
|
656
|
-
},
|
|
657
|
-
],
|
|
658
|
-
isError: false,
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
default:
|
|
662
|
-
throw new McpError(ErrorCode.MethodNotFound, `Unknown task management action: ${action}`);
|
|
663
|
-
}
|
|
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 };
|
|
664
138
|
}
|
|
665
|
-
|
|
666
|
-
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
139
|
+
return { content: [{ type: 'text', text: resp.msg || '取消成功' }], isError: false };
|
|
667
140
|
}
|
|
141
|
+
throw new McpError(ErrorCode.MethodNotFound, `未知工具: ${name}`);
|
|
668
142
|
}
|
|
669
143
|
catch (error) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
isError: true,
|
|
674
|
-
};
|
|
144
|
+
if (error instanceof McpError)
|
|
145
|
+
throw error;
|
|
146
|
+
throw new McpError(ErrorCode.InternalError, `执行失败: ${error.message}`);
|
|
675
147
|
}
|
|
676
148
|
});
|
|
677
149
|
}
|
|
678
150
|
async run() {
|
|
679
151
|
const transport = new StdioServerTransport();
|
|
680
152
|
await this.server.connect(transport);
|
|
681
|
-
console.error('
|
|
153
|
+
console.error('Reminder MCP server running on stdio');
|
|
682
154
|
}
|
|
683
155
|
}
|
|
684
|
-
const server = new
|
|
156
|
+
const server = new ReminderServer();
|
|
685
157
|
server.run().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mcpcn/mcp-notification",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
}
|