@mcpcn/mcp-notification 1.1.3 → 1.1.6
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/dist/index.js +140 -12
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -95,21 +95,45 @@ class ReminderServer {
|
|
|
95
95
|
tools: [
|
|
96
96
|
{
|
|
97
97
|
name: 'set_reminder',
|
|
98
|
-
description: '
|
|
98
|
+
description: '设置通知提醒,支持一次性、按间隔循环、每日循环;一次性提醒需提供triggerAt或delaySec,或提供timeOfDay用于推算,优先提供delaySec。',
|
|
99
99
|
inputSchema: {
|
|
100
100
|
type: 'object',
|
|
101
101
|
properties: {
|
|
102
|
-
content: { type: 'string' },
|
|
103
|
-
repeat: { type: 'string', enum: ['none', 'interval', 'daily'] },
|
|
104
|
-
delaySec: { type: 'number' },
|
|
105
|
-
triggerAt: { type: 'string', description: 'RFC3339
|
|
106
|
-
intervalSec: { type: 'number' },
|
|
107
|
-
timeOfDay: { type: 'string', description: '
|
|
108
|
-
tzOffsetMin: { type: 'number', description: '时区偏移分钟,例如北京为 480' },
|
|
109
|
-
chatSessionId: { type: 'string' },
|
|
102
|
+
content: { type: 'string', minLength: 1, description: '提醒内容,例如 “开会”。' },
|
|
103
|
+
repeat: { type: 'string', enum: ['none', 'interval', 'daily'], default: 'none', description: '提醒类型:none 一次性、interval 按间隔、daily 每日。' },
|
|
104
|
+
delaySec: { type: 'number', minimum: 1, description: '一次性提醒相对延迟秒数,例如 300 表示5分钟后触发。与triggerAt二选一。' },
|
|
105
|
+
triggerAt: { type: 'string', description: '一次性提醒绝对时间,RFC3339 格式,例如 2025-11-21T08:00:00+08:00。' },
|
|
106
|
+
intervalSec: { type: 'number', minimum: 1, description: '按间隔循环的间隔秒数,例如 3600 表示每小时提醒一次。仅repeat=interval时必需。' },
|
|
107
|
+
timeOfDay: { type: 'string', pattern: '^\\d{1,2}:\\d{2}(:\\d{2})?$', description: '一天中的时间,格式 HH:mm 或 HH:mm:ss,例如 08:00 或 08:00:00。repeat=daily时必需;repeat=none且未提供triggerAt/delaySec时用于推算。' },
|
|
108
|
+
tzOffsetMin: { type: 'number', description: '时区偏移分钟,例如北京为 480;不提供时默认使用本机时区。' },
|
|
109
|
+
chatSessionId: { type: 'string', minLength: 1, description: '设备会话标识,由宿主环境传入。' },
|
|
110
110
|
},
|
|
111
111
|
required: ['content', 'repeat'],
|
|
112
112
|
additionalProperties: false,
|
|
113
|
+
oneOf: [
|
|
114
|
+
{
|
|
115
|
+
properties: { repeat: { const: 'none' } },
|
|
116
|
+
anyOf: [
|
|
117
|
+
{ required: ['triggerAt'] },
|
|
118
|
+
{ required: ['delaySec'] },
|
|
119
|
+
{ required: ['timeOfDay'] },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
properties: { repeat: { const: 'interval' } },
|
|
124
|
+
required: ['intervalSec'],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
properties: { repeat: { const: 'daily' } },
|
|
128
|
+
required: ['timeOfDay'],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
examples: [
|
|
132
|
+
{ content: '开会', repeat: 'none', triggerAt: '2025-11-21T08:00:00+08:00' },
|
|
133
|
+
{ content: '喝水', repeat: 'none', delaySec: 300 },
|
|
134
|
+
{ content: '站立休息', repeat: 'interval', intervalSec: 1800 },
|
|
135
|
+
{ content: '打卡', repeat: 'daily', timeOfDay: '09:00', tzOffsetMin: 480 },
|
|
136
|
+
],
|
|
113
137
|
},
|
|
114
138
|
},
|
|
115
139
|
{
|
|
@@ -117,7 +141,7 @@ class ReminderServer {
|
|
|
117
141
|
description: '获取设备的提醒列表(仅未触发的 scheduled)。',
|
|
118
142
|
inputSchema: {
|
|
119
143
|
type: 'object',
|
|
120
|
-
properties: { chatSessionId: { type: 'string' } },
|
|
144
|
+
properties: { chatSessionId: { type: 'string', minLength: 1, description: '设备会话标识。' } },
|
|
121
145
|
required: [],
|
|
122
146
|
additionalProperties: false,
|
|
123
147
|
},
|
|
@@ -127,7 +151,7 @@ class ReminderServer {
|
|
|
127
151
|
description: '取消指定提醒。',
|
|
128
152
|
inputSchema: {
|
|
129
153
|
type: 'object',
|
|
130
|
-
properties: { id: { type: 'string' }, chatSessionId: { type: 'string' } },
|
|
154
|
+
properties: { id: { type: 'string', minLength: 1, description: '提醒ID。' }, chatSessionId: { type: 'string', minLength: 1, description: '设备会话标识。' } },
|
|
131
155
|
required: ['id'],
|
|
132
156
|
additionalProperties: false,
|
|
133
157
|
},
|
|
@@ -169,6 +193,73 @@ class ReminderServer {
|
|
|
169
193
|
timeOfDay: args.timeOfDay,
|
|
170
194
|
tzOffsetMin: args.tzOffsetMin,
|
|
171
195
|
};
|
|
196
|
+
const needSingleTrigger = params.repeat === 'none';
|
|
197
|
+
const hasTrigger = typeof params.triggerAt === 'string' && params.triggerAt.trim().length > 0;
|
|
198
|
+
const hasDelay = typeof params.delaySec === 'number' && Number.isFinite(params.delaySec);
|
|
199
|
+
const sendOffsetMin = typeof params.tzOffsetMin === 'number' ? params.tzOffsetMin : -new Date().getTimezoneOffset();
|
|
200
|
+
params.tzOffsetMin = sendOffsetMin;
|
|
201
|
+
if (needSingleTrigger && !hasTrigger && !hasDelay) {
|
|
202
|
+
if (typeof params.timeOfDay === 'string' && params.timeOfDay.trim()) {
|
|
203
|
+
const m = params.timeOfDay.trim().match(/^\s*(\d{1,2}):(\d{2})(?::(\d{2}))?\s*$/);
|
|
204
|
+
if (!m) {
|
|
205
|
+
return { content: [{ type: 'text', text: '设置失败:timeOfDay格式应为HH:mm或HH:mm:ss' }], isError: true };
|
|
206
|
+
}
|
|
207
|
+
const hh = parseInt(m[1], 10);
|
|
208
|
+
const mi = parseInt(m[2], 10);
|
|
209
|
+
const ss = m[3] ? parseInt(m[3], 10) : 0;
|
|
210
|
+
if (hh < 0 || hh > 23 || mi < 0 || mi > 59 || ss < 0 || ss > 59) {
|
|
211
|
+
return { content: [{ type: 'text', text: '设置失败:timeOfDay格式应为HH:mm或HH:mm:ss' }], isError: true };
|
|
212
|
+
}
|
|
213
|
+
const offsetMin = typeof params.tzOffsetMin === 'number' ? params.tzOffsetMin : -new Date().getTimezoneOffset();
|
|
214
|
+
const nowUtc = Date.now();
|
|
215
|
+
const zoneNowMs = nowUtc + offsetMin * 60000;
|
|
216
|
+
const zoneNow = new Date(zoneNowMs);
|
|
217
|
+
let y = zoneNow.getUTCFullYear();
|
|
218
|
+
let mth = zoneNow.getUTCMonth();
|
|
219
|
+
let d = zoneNow.getUTCDate();
|
|
220
|
+
let targetUtcMs = Date.UTC(y, mth, d, hh, mi, ss) - offsetMin * 60000;
|
|
221
|
+
if (targetUtcMs <= nowUtc) {
|
|
222
|
+
const tomorrowZone = new Date(zoneNowMs + 86400000);
|
|
223
|
+
y = tomorrowZone.getUTCFullYear();
|
|
224
|
+
mth = tomorrowZone.getUTCMonth();
|
|
225
|
+
d = tomorrowZone.getUTCDate();
|
|
226
|
+
targetUtcMs = Date.UTC(y, mth, d, hh, mi, ss) - offsetMin * 60000;
|
|
227
|
+
}
|
|
228
|
+
const targetZone = new Date(targetUtcMs + offsetMin * 60000);
|
|
229
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
230
|
+
const yStr = String(targetZone.getUTCFullYear());
|
|
231
|
+
const mStr = pad2(targetZone.getUTCMonth() + 1);
|
|
232
|
+
const dStr = pad2(targetZone.getUTCDate());
|
|
233
|
+
const sign = offsetMin >= 0 ? '+' : '-';
|
|
234
|
+
const abs = Math.abs(offsetMin);
|
|
235
|
+
const oh = pad2(Math.floor(abs / 60));
|
|
236
|
+
const om = pad2(abs % 60);
|
|
237
|
+
params.triggerAt = `${yStr}-${mStr}-${dStr}T${pad2(hh)}:${pad2(mi)}:${pad2(ss)}${sign}${oh}:${om}`;
|
|
238
|
+
console.error(`自动计算triggerAt: ${params.triggerAt}`);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
return { content: [{ type: 'text', text: '设置失败:必须提供triggerAt或delaySec,或提供timeOfDay用于推算' }], isError: true };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (needSingleTrigger && hasDelay && !hasTrigger) {
|
|
245
|
+
const offsetMin = typeof params.tzOffsetMin === 'number' ? params.tzOffsetMin : -new Date().getTimezoneOffset();
|
|
246
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
247
|
+
const nowUtc = Date.now();
|
|
248
|
+
const targetUtcMs = nowUtc + params.delaySec * 1000;
|
|
249
|
+
const targetZone = new Date(targetUtcMs + offsetMin * 60000);
|
|
250
|
+
const yStr = String(targetZone.getUTCFullYear());
|
|
251
|
+
const mStr = pad2(targetZone.getUTCMonth() + 1);
|
|
252
|
+
const dStr = pad2(targetZone.getUTCDate());
|
|
253
|
+
const hhStr = pad2(targetZone.getUTCMinutes() >= 0 ? targetZone.getUTCHours() : targetZone.getUTCHours());
|
|
254
|
+
const miStr = pad2(targetZone.getUTCMinutes());
|
|
255
|
+
const ssStr = pad2(targetZone.getUTCSeconds());
|
|
256
|
+
const sign = offsetMin >= 0 ? '+' : '-';
|
|
257
|
+
const abs = Math.abs(offsetMin);
|
|
258
|
+
const oh = pad2(Math.floor(abs / 60));
|
|
259
|
+
const om = pad2(abs % 60);
|
|
260
|
+
params.triggerAt = `${yStr}-${mStr}-${dStr}T${hhStr}:${miStr}:${ssStr}${sign}${oh}:${om}`;
|
|
261
|
+
console.error(`delaySec计算triggerAt: ${params.triggerAt}`);
|
|
262
|
+
}
|
|
172
263
|
const resp = await postJson('/reminder/set', params, chatSessionId);
|
|
173
264
|
if (resp.code !== 0) {
|
|
174
265
|
return { content: [{ type: 'text', text: `设置失败:${resp.msg}` }], isError: true };
|
|
@@ -212,8 +303,45 @@ class ReminderServer {
|
|
|
212
303
|
if (resp.code !== 0) {
|
|
213
304
|
return { content: [{ type: 'text', text: `获取失败:${resp.msg}` }], isError: true };
|
|
214
305
|
}
|
|
306
|
+
const offsetMin = typeof args?.tzOffsetMin === 'number' ? args.tzOffsetMin : -new Date().getTimezoneOffset();
|
|
307
|
+
const formatOffset = (min) => {
|
|
308
|
+
const sign = min >= 0 ? '+' : '-';
|
|
309
|
+
const abs = Math.abs(min);
|
|
310
|
+
const hh = String(Math.floor(abs / 60)).padStart(2, '0');
|
|
311
|
+
const mm = String(abs % 60).padStart(2, '0');
|
|
312
|
+
return `${sign}${hh}:${mm}`;
|
|
313
|
+
};
|
|
314
|
+
const toLocal = (iso) => {
|
|
315
|
+
if (!iso)
|
|
316
|
+
return iso;
|
|
317
|
+
const d = new Date(iso);
|
|
318
|
+
if (isNaN(d.getTime()))
|
|
319
|
+
return iso;
|
|
320
|
+
const y = d.getFullYear();
|
|
321
|
+
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
322
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
323
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
324
|
+
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
325
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
326
|
+
return `${y}-${m}-${day}T${hh}:${mi}:${ss}${formatOffset(offsetMin)}`;
|
|
327
|
+
};
|
|
328
|
+
const raw = resp.data?.list ?? [];
|
|
329
|
+
const mapped = Array.isArray(raw)
|
|
330
|
+
? raw.map((it) => {
|
|
331
|
+
if (it && typeof it === 'object') {
|
|
332
|
+
const anyIt = it;
|
|
333
|
+
if (anyIt.triggerAt)
|
|
334
|
+
anyIt.triggerAtLocal = toLocal(anyIt.triggerAt);
|
|
335
|
+
if (anyIt.nextAt)
|
|
336
|
+
anyIt.nextAtLocal = toLocal(anyIt.nextAt);
|
|
337
|
+
if (anyIt.scheduledAt)
|
|
338
|
+
anyIt.scheduledAtLocal = toLocal(anyIt.scheduledAt);
|
|
339
|
+
}
|
|
340
|
+
return it;
|
|
341
|
+
})
|
|
342
|
+
: raw;
|
|
215
343
|
return {
|
|
216
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
344
|
+
content: [{ type: 'text', text: JSON.stringify(mapped, null, 2) }],
|
|
217
345
|
isError: false,
|
|
218
346
|
};
|
|
219
347
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcpcn/mcp-notification",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "系统通知MCP服务器",
|
|
5
5
|
"packageManager": "yarn@1.22.22",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
"typescript": "^5.7.2"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
40
40
|
}
|
|
41
41
|
}
|