@mcpcn/mcp-notification 1.1.5 → 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 +147 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,13 +12,10 @@ function resolveChatSessionId(request) {
|
|
|
12
12
|
}
|
|
13
13
|
return undefined;
|
|
14
14
|
};
|
|
15
|
-
return (pick(request?.
|
|
15
|
+
return (pick(request?.meta, ['chatSessionId', 'chatSessionID']) ??
|
|
16
16
|
pick(request?.params?.meta, ['chatSessionId', 'chatSessionID']) ??
|
|
17
|
-
pick(request?.
|
|
17
|
+
pick(request?.params?._meta, ['chatSessionId', 'chatSessionID']) ??
|
|
18
18
|
pick(request?._meta, ['chatSessionId', 'chatSessionID']) ??
|
|
19
|
-
pick(request?.params, ['chatSessionId', 'chatSessionID']) ??
|
|
20
|
-
pick(request?.params?.arguments?.meta, ['chatSessionId', 'chatSessionID']) ??
|
|
21
|
-
pick(request?.params?.arguments?._meta, ['chatSessionId', 'chatSessionID']) ??
|
|
22
19
|
pick(request?.params?.arguments, ['chatSessionId', 'chatSessionID']));
|
|
23
20
|
}
|
|
24
21
|
async function postJson(path, body, chatSessionId) {
|
|
@@ -98,21 +95,45 @@ class ReminderServer {
|
|
|
98
95
|
tools: [
|
|
99
96
|
{
|
|
100
97
|
name: 'set_reminder',
|
|
101
|
-
description: '
|
|
98
|
+
description: '设置通知提醒,支持一次性、按间隔循环、每日循环;一次性提醒需提供triggerAt或delaySec,或提供timeOfDay用于推算,优先提供delaySec。',
|
|
102
99
|
inputSchema: {
|
|
103
100
|
type: 'object',
|
|
104
101
|
properties: {
|
|
105
|
-
content: { type: 'string' },
|
|
106
|
-
repeat: { type: 'string', enum: ['none', 'interval', 'daily'] },
|
|
107
|
-
delaySec: { type: 'number' },
|
|
108
|
-
triggerAt: { type: 'string', description: 'RFC3339
|
|
109
|
-
intervalSec: { type: 'number' },
|
|
110
|
-
timeOfDay: { type: 'string', description: '
|
|
111
|
-
tzOffsetMin: { type: 'number', description: '时区偏移分钟,例如北京为 480' },
|
|
112
|
-
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: '设备会话标识,由宿主环境传入。' },
|
|
113
110
|
},
|
|
114
111
|
required: ['content', 'repeat'],
|
|
115
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
|
+
],
|
|
116
137
|
},
|
|
117
138
|
},
|
|
118
139
|
{
|
|
@@ -120,7 +141,7 @@ class ReminderServer {
|
|
|
120
141
|
description: '获取设备的提醒列表(仅未触发的 scheduled)。',
|
|
121
142
|
inputSchema: {
|
|
122
143
|
type: 'object',
|
|
123
|
-
properties: { chatSessionId: { type: 'string' } },
|
|
144
|
+
properties: { chatSessionId: { type: 'string', minLength: 1, description: '设备会话标识。' } },
|
|
124
145
|
required: [],
|
|
125
146
|
additionalProperties: false,
|
|
126
147
|
},
|
|
@@ -130,7 +151,7 @@ class ReminderServer {
|
|
|
130
151
|
description: '取消指定提醒。',
|
|
131
152
|
inputSchema: {
|
|
132
153
|
type: 'object',
|
|
133
|
-
properties: { id: { type: 'string' }, chatSessionId: { type: 'string' } },
|
|
154
|
+
properties: { id: { type: 'string', minLength: 1, description: '提醒ID。' }, chatSessionId: { type: 'string', minLength: 1, description: '设备会话标识。' } },
|
|
134
155
|
required: ['id'],
|
|
135
156
|
additionalProperties: false,
|
|
136
157
|
},
|
|
@@ -139,15 +160,17 @@ class ReminderServer {
|
|
|
139
160
|
}));
|
|
140
161
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
141
162
|
try {
|
|
163
|
+
console.error('[调试] request.params keys = ' + JSON.stringify(Object.keys(request.params || {})));
|
|
142
164
|
if (!request.params.arguments || typeof request.params.arguments !== 'object') {
|
|
143
165
|
throw new McpError(ErrorCode.InvalidParams, '无效的参数');
|
|
144
166
|
}
|
|
145
167
|
const name = request.params.name;
|
|
146
168
|
const args = request.params.arguments;
|
|
169
|
+
console.error(`收到工具调用请求: ${name}, 输入: ${JSON.stringify(args)}`);
|
|
147
170
|
const chatSessionId = resolveChatSessionId(request);
|
|
148
171
|
if (!chatSessionId) {
|
|
149
|
-
console.error('
|
|
150
|
-
const baseMsg = '解析设备失败: chatSessionId
|
|
172
|
+
console.error('通知工具未在请求中检测到 chatSessionId(meta.chatSessionId)');
|
|
173
|
+
const baseMsg = '解析设备失败: chatSessionId不能为空,通知工具暂无法使用';
|
|
151
174
|
const errText = name === 'set_reminder'
|
|
152
175
|
? `设置失败:${baseMsg}`
|
|
153
176
|
: name === 'list_reminders'
|
|
@@ -158,7 +181,7 @@ class ReminderServer {
|
|
|
158
181
|
return { content: [{ type: 'text', text: errText }], isError: true };
|
|
159
182
|
}
|
|
160
183
|
else {
|
|
161
|
-
console.error(
|
|
184
|
+
console.error(`通知工具接收到 chatSessionId: ${chatSessionId}`);
|
|
162
185
|
}
|
|
163
186
|
if (name === 'set_reminder') {
|
|
164
187
|
const params = {
|
|
@@ -170,6 +193,73 @@ class ReminderServer {
|
|
|
170
193
|
timeOfDay: args.timeOfDay,
|
|
171
194
|
tzOffsetMin: args.tzOffsetMin,
|
|
172
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
|
+
}
|
|
173
263
|
const resp = await postJson('/reminder/set', params, chatSessionId);
|
|
174
264
|
if (resp.code !== 0) {
|
|
175
265
|
return { content: [{ type: 'text', text: `设置失败:${resp.msg}` }], isError: true };
|
|
@@ -213,8 +303,45 @@ class ReminderServer {
|
|
|
213
303
|
if (resp.code !== 0) {
|
|
214
304
|
return { content: [{ type: 'text', text: `获取失败:${resp.msg}` }], isError: true };
|
|
215
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;
|
|
216
343
|
return {
|
|
217
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
344
|
+
content: [{ type: 'text', text: JSON.stringify(mapped, null, 2) }],
|
|
218
345
|
isError: false,
|
|
219
346
|
};
|
|
220
347
|
}
|