senq-mcp 1.4.2 → 1.4.3

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 CHANGED
@@ -9,7 +9,20 @@ if (process.argv[2] === "setup") {
9
9
  await runSetup();
10
10
  process.exit(0);
11
11
  }
12
- const server = new Server({ name: "senq-mcp", version: "1.4.2" }, { capabilities: { tools: {}, prompts: {} } });
12
+ const server = new Server({ name: "senq-mcp", version: "1.4.3" }, {
13
+ capabilities: { tools: {}, prompts: {} },
14
+ instructions: `Ты подключён к Senq — системе управления задачами агентства.
15
+
16
+ Когда пользователь спрашивает о задачах, сроках, проектах, команде или знаниях — ВСЕГДА используй инструменты Senq напрямую, не ищи в памяти или файлах:
17
+ • list_tasks — задачи текущего пользователя (сегодня, просроченные и т.д.)
18
+ • find_employee("имя") → list_team_tasks / get_team_report — задачи конкретного сотрудника
19
+ • search_tasks("запрос") — поиск задач по тексту
20
+ • list_projects — список проектов
21
+ • search_knowledge("запрос") — база знаний проектов
22
+ • get_analytics("stuck") — застрявшие задачи
23
+
24
+ Для вопросов про сотрудника: сначала find_employee, потом list_team_tasks или get_team_report.`,
25
+ });
13
26
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
14
27
  tools: [
15
28
  {
@@ -327,11 +340,22 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
327
340
  if (args?.offset)
328
341
  params.offset = String(args.offset);
329
342
  const data = await apiGet("/api/tasks/team", params);
343
+ const todayStr = new Date().toISOString().slice(0, 10);
330
344
  const tasks = withUrls(data.tasks ?? []).map((t) => {
331
- if (typeof t.assigneeId === "string") {
332
- return { ...t, assigneeUrl: `${BASE_URL}/#/team/${t.assigneeId}` };
345
+ const result = typeof t.assigneeId === "string"
346
+ ? { ...t, assigneeUrl: `${BASE_URL}/#/team/${t.assigneeId}` }
347
+ : { ...t };
348
+ const due = typeof t.dueDate === "string" ? t.dueDate : null;
349
+ const completed = typeof t.completedAt === "string" ? t.completedAt.slice(0, 10) : null;
350
+ if (due) {
351
+ if (t.status === "open" && due < todayStr) {
352
+ result.daysOverdue = Math.round((new Date(todayStr).getTime() - new Date(due).getTime()) / 86400000);
353
+ }
354
+ else if (completed) {
355
+ result.daysLate = Math.round((new Date(completed).getTime() - new Date(due).getTime()) / 86400000);
356
+ }
333
357
  }
334
- return t;
358
+ return result;
335
359
  });
336
360
  const total = data.total ?? tasks.length;
337
361
  return {
@@ -364,6 +388,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
364
388
  return true;
365
389
  });
366
390
  const today = new Date().toISOString().slice(0, 10);
391
+ const daysDiff = (from, to) => Math.round((new Date(to).getTime() - new Date(from).getTime()) / 86400000);
367
392
  const completedOnTime = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) <= t.dueDate);
368
393
  const completedLate = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) > t.dueDate);
369
394
  const overdue = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate < today);
@@ -374,8 +399,19 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
374
399
  ? `, [${t.assigneeName}](${BASE_URL}/#/team/${assigneeId})`
375
400
  : "";
376
401
  const parts = [`срок: ${t.dueDate ?? "—"}`];
377
- if (t.completedAt)
378
- parts.push(`выполнена: ${t.completedAt.slice(0, 10)}`);
402
+ if (t.completedAt && t.dueDate) {
403
+ const completed = t.completedAt.slice(0, 10);
404
+ const diff = daysDiff(t.dueDate, completed);
405
+ if (diff > 0)
406
+ parts.push(`закрыта с опозданием на ${diff} дн.`);
407
+ else if (diff === 0)
408
+ parts.push("закрыта в срок");
409
+ else
410
+ parts.push(`закрыта на ${-diff} дн. раньше срока`);
411
+ }
412
+ else if (t.status === "open" && t.dueDate && t.dueDate < today) {
413
+ parts.push(`просрочена на ${daysDiff(t.dueDate, today)} дн.`);
414
+ }
379
415
  if (t.projectName)
380
416
  parts.push(`проект: ${t.projectName}`);
381
417
  return ` • [${t.title}](${taskUrl}) (${parts.join(", ")}${assigneePart})`;
@@ -414,8 +450,8 @@ const PROMPTS = [
414
450
  },
415
451
  {
416
452
  name: "что-просрочено",
417
- description: "Мои просроченные задачи и дедлайны",
418
- arguments: [],
453
+ description: "Просроченные задачи мои или конкретного сотрудника",
454
+ arguments: [{ name: "сотрудник", description: "Имя или email сотрудника (необязательно — без него показывает мои задачи)", required: false }],
419
455
  },
420
456
  {
421
457
  name: "создать-задачу",
@@ -450,7 +486,13 @@ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
450
486
  messages.push({ role: "user", content: { type: "text", text: "Покажи мои задачи на сегодня" } });
451
487
  }
452
488
  else if (name === "что-просрочено") {
453
- messages.push({ role: "user", content: { type: "text", text: "Что у меня просрочено? Покажи задачи с прошедшим дедлайном" } });
489
+ const emp = promptArgs?.["сотрудник"];
490
+ if (emp) {
491
+ messages.push({ role: "user", content: { type: "text", text: `Сначала найди сотрудника «${emp}» через find_employee, затем покажи его просроченные задачи через list_team_tasks с status=open. Для каждой задачи укажи, на сколько дней она просрочена (поле daysOverdue). Форматируй названия как markdown-ссылки [название](url).` } });
492
+ }
493
+ else {
494
+ messages.push({ role: "user", content: { type: "text", text: "Что у меня просрочено? Используй list_tasks и покажи задачи с прошедшим дедлайном. Для каждой укажи срок и сколько дней просрочено." } });
495
+ }
454
496
  }
455
497
  else if (name === "создать-задачу") {
456
498
  const desc = promptArgs?.["описание"] ?? "...";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senq-mcp",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "MCP server for Senq — connect your AI to tasks and knowledge",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -17,8 +17,21 @@ if (process.argv[2] === "setup") {
17
17
  }
18
18
 
19
19
  const server = new Server(
20
- { name: "senq-mcp", version: "1.4.2" },
21
- { capabilities: { tools: {}, prompts: {} } },
20
+ { name: "senq-mcp", version: "1.4.3" },
21
+ {
22
+ capabilities: { tools: {}, prompts: {} },
23
+ instructions: `Ты подключён к Senq — системе управления задачами агентства.
24
+
25
+ Когда пользователь спрашивает о задачах, сроках, проектах, команде или знаниях — ВСЕГДА используй инструменты Senq напрямую, не ищи в памяти или файлах:
26
+ • list_tasks — задачи текущего пользователя (сегодня, просроченные и т.д.)
27
+ • find_employee("имя") → list_team_tasks / get_team_report — задачи конкретного сотрудника
28
+ • search_tasks("запрос") — поиск задач по тексту
29
+ • list_projects — список проектов
30
+ • search_knowledge("запрос") — база знаний проектов
31
+ • get_analytics("stuck") — застрявшие задачи
32
+
33
+ Для вопросов про сотрудника: сначала find_employee, потом list_team_tasks или get_team_report.`,
34
+ },
22
35
  );
23
36
 
24
37
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -309,11 +322,21 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
309
322
  if (args?.limit) params.limit = String(args.limit);
310
323
  if (args?.offset) params.offset = String(args.offset);
311
324
  const data = await apiGet<{ tasks?: unknown[]; total?: number }>("/api/tasks/team", params);
325
+ const todayStr = new Date().toISOString().slice(0, 10);
312
326
  const tasks = withUrls(data.tasks ?? []).map((t) => {
313
- if (typeof t.assigneeId === "string") {
314
- return { ...t, assigneeUrl: `${BASE_URL}/#/team/${t.assigneeId}` };
327
+ const result: TaskLike = typeof t.assigneeId === "string"
328
+ ? { ...t, assigneeUrl: `${BASE_URL}/#/team/${t.assigneeId}` }
329
+ : { ...t };
330
+ const due = typeof t.dueDate === "string" ? t.dueDate : null;
331
+ const completed = typeof t.completedAt === "string" ? t.completedAt.slice(0, 10) : null;
332
+ if (due) {
333
+ if (t.status === "open" && due < todayStr) {
334
+ result.daysOverdue = Math.round((new Date(todayStr).getTime() - new Date(due).getTime()) / 86400000);
335
+ } else if (completed) {
336
+ result.daysLate = Math.round((new Date(completed).getTime() - new Date(due).getTime()) / 86400000);
337
+ }
315
338
  }
316
- return t;
339
+ return result;
317
340
  });
318
341
  const total = data.total ?? tasks.length;
319
342
  return {
@@ -351,6 +374,9 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
351
374
 
352
375
  const today = new Date().toISOString().slice(0, 10);
353
376
 
377
+ const daysDiff = (from: string, to: string) =>
378
+ Math.round((new Date(to).getTime() - new Date(from).getTime()) / 86400000);
379
+
354
380
  const completedOnTime = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) <= t.dueDate);
355
381
  const completedLate = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) > t.dueDate);
356
382
  const overdue = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate < today);
@@ -363,7 +389,15 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
363
389
  ? `, [${t.assigneeName}](${BASE_URL}/#/team/${assigneeId})`
364
390
  : "";
365
391
  const parts = [`срок: ${t.dueDate ?? "—"}`];
366
- if (t.completedAt) parts.push(`выполнена: ${t.completedAt.slice(0, 10)}`);
392
+ if (t.completedAt && t.dueDate) {
393
+ const completed = t.completedAt.slice(0, 10);
394
+ const diff = daysDiff(t.dueDate, completed);
395
+ if (diff > 0) parts.push(`закрыта с опозданием на ${diff} дн.`);
396
+ else if (diff === 0) parts.push("закрыта в срок");
397
+ else parts.push(`закрыта на ${-diff} дн. раньше срока`);
398
+ } else if (t.status === "open" && t.dueDate && t.dueDate < today) {
399
+ parts.push(`просрочена на ${daysDiff(t.dueDate, today)} дн.`);
400
+ }
367
401
  if (t.projectName) parts.push(`проект: ${t.projectName}`);
368
402
  return ` • [${t.title}](${taskUrl}) (${parts.join(", ")}${assigneePart})`;
369
403
  }).join("\n");
@@ -404,8 +438,8 @@ const PROMPTS = [
404
438
  },
405
439
  {
406
440
  name: "что-просрочено",
407
- description: "Мои просроченные задачи и дедлайны",
408
- arguments: [],
441
+ description: "Просроченные задачи мои или конкретного сотрудника",
442
+ arguments: [{ name: "сотрудник", description: "Имя или email сотрудника (необязательно — без него показывает мои задачи)", required: false }],
409
443
  },
410
444
  {
411
445
  name: "создать-задачу",
@@ -442,7 +476,12 @@ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
442
476
  if (name === "задачи-сегодня") {
443
477
  messages.push({ role: "user", content: { type: "text", text: "Покажи мои задачи на сегодня" } });
444
478
  } else if (name === "что-просрочено") {
445
- messages.push({ role: "user", content: { type: "text", text: "Что у меня просрочено? Покажи задачи с прошедшим дедлайном" } });
479
+ const emp = promptArgs?.["сотрудник"];
480
+ if (emp) {
481
+ messages.push({ role: "user", content: { type: "text", text: `Сначала найди сотрудника «${emp}» через find_employee, затем покажи его просроченные задачи через list_team_tasks с status=open. Для каждой задачи укажи, на сколько дней она просрочена (поле daysOverdue). Форматируй названия как markdown-ссылки [название](url).` } });
482
+ } else {
483
+ messages.push({ role: "user", content: { type: "text", text: "Что у меня просрочено? Используй list_tasks и покажи задачи с прошедшим дедлайном. Для каждой укажи срок и сколько дней просрочено." } });
484
+ }
446
485
  } else if (name === "создать-задачу") {
447
486
  const desc = promptArgs?.["описание"] ?? "...";
448
487
  messages.push({ role: "user", content: { type: "text", text: `Создай задачу: ${desc}` } });