@mcpcn/mcp-notification 1.1.5 → 1.1.7

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 (2) hide show
  1. package/dist/index.js +158 -20
  2. 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?.params?._meta, ['chatSessionId', 'chatSessionID']) ??
15
+ return (pick(request?.meta, ['chatSessionId', 'chatSessionID']) ??
16
16
  pick(request?.params?.meta, ['chatSessionId', 'chatSessionID']) ??
17
- pick(request?.meta, ['chatSessionId', 'chatSessionID']) ??
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 时间,例如 2025-11-15T20:00:00+08:00' },
109
- intervalSec: { type: 'number' },
110
- timeOfDay: { type: 'string', description: '例如 18:00 或 18:00:00' },
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('未在请求中检测到 chatSessionId(meta.chatSessionId)');
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(`接收到 chatSessionId: ${chatSessionId}`);
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,56 @@ 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 = (v) => {
315
+ if (v === undefined || v === null)
316
+ return v;
317
+ let ms;
318
+ if (typeof v === 'number') {
319
+ ms = v > 1e12 ? v : v * 1000;
320
+ }
321
+ else if (typeof v === 'string') {
322
+ const s = v.trim();
323
+ if (/^\d{10}$/.test(s))
324
+ ms = parseInt(s, 10) * 1000;
325
+ else if (/^\d{13}$/.test(s))
326
+ ms = parseInt(s, 10);
327
+ }
328
+ const d = ms !== undefined ? new Date(ms) : new Date(v);
329
+ if (isNaN(d.getTime()))
330
+ return v;
331
+ const y = d.getFullYear();
332
+ const m = String(d.getMonth() + 1).padStart(2, '0');
333
+ const day = String(d.getDate()).padStart(2, '0');
334
+ const hh = String(d.getHours()).padStart(2, '0');
335
+ const mi = String(d.getMinutes()).padStart(2, '0');
336
+ const ss = String(d.getSeconds()).padStart(2, '0');
337
+ return `${y}-${m}-${day}T${hh}:${mi}:${ss}${formatOffset(offsetMin)}`;
338
+ };
339
+ const raw = resp.data?.list ?? [];
340
+ const mapped = Array.isArray(raw)
341
+ ? raw.map((it) => {
342
+ if (it && typeof it === 'object') {
343
+ const anyIt = it;
344
+ if (anyIt.triggerAt)
345
+ anyIt.triggerAtLocal = toLocal(anyIt.triggerAt);
346
+ if (anyIt.nextAt)
347
+ anyIt.nextAtLocal = toLocal(anyIt.nextAt);
348
+ if (anyIt.scheduledAt)
349
+ anyIt.scheduledAtLocal = toLocal(anyIt.scheduledAt);
350
+ }
351
+ return it;
352
+ })
353
+ : raw;
216
354
  return {
217
- content: [{ type: 'text', text: JSON.stringify(resp.data?.list ?? [], null, 2) }],
355
+ content: [{ type: 'text', text: JSON.stringify(mapped, null, 2) }],
218
356
  isError: false,
219
357
  };
220
358
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpcn/mcp-notification",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "系统通知MCP服务器",
5
5
  "packageManager": "yarn@1.22.22",
6
6
  "main": "dist/index.js",