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 +51 -9
- package/package.json +1 -1
- package/src/index.ts +48 -9
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.
|
|
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
|
-
|
|
332
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
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.
|
|
21
|
-
{
|
|
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
|
-
|
|
314
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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}` } });
|