senq-mcp 1.3.0 → 1.4.1
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/client.d.ts +1 -0
- package/dist/client.js +1 -1
- package/dist/index.js +126 -9
- package/package.json +1 -1
- package/src/client.ts +1 -1
- package/src/index.ts +131 -11
package/dist/client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const BASE_URL: string;
|
|
1
2
|
export declare function apiGet<T>(path: string, params?: Record<string, string>): Promise<T>;
|
|
2
3
|
export declare function apiPost<T>(path: string, body: unknown): Promise<T>;
|
|
3
4
|
export declare function apiPatch<T>(path: string, body: unknown): Promise<T>;
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const BASE_URL = (process.env.SENQ_BASE_URL ?? "https://senq.serenity.agency").replace(/\/$/, "");
|
|
1
|
+
export const BASE_URL = (process.env.SENQ_BASE_URL ?? "https://senq.serenity.agency").replace(/\/$/, "");
|
|
2
2
|
function getApiKey() {
|
|
3
3
|
const key = process.env.SENQ_API_KEY ?? "";
|
|
4
4
|
if (!key) {
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { apiGet, apiPost, apiPatch } from "./client.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { apiGet, apiPost, apiPatch, BASE_URL } from "./client.js";
|
|
6
6
|
import { storePending, consumePending } from "./pending-changes.js";
|
|
7
7
|
if (process.argv[2] === "setup") {
|
|
8
8
|
const { runSetup } = await import("./setup.js");
|
|
9
9
|
await runSetup();
|
|
10
10
|
process.exit(0);
|
|
11
11
|
}
|
|
12
|
-
const server = new Server({ name: "senq-mcp", version: "1.
|
|
12
|
+
const server = new Server({ name: "senq-mcp", version: "1.4.1" }, { capabilities: { tools: {}, prompts: {} } });
|
|
13
13
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
14
14
|
tools: [
|
|
15
15
|
{
|
|
@@ -106,6 +106,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: "find_employee",
|
|
111
|
+
description: "Найти сотрудника по имени или email. Возвращает ID, который нужен для list_team_tasks и get_team_report.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
required: ["name"],
|
|
115
|
+
properties: {
|
|
116
|
+
name: { type: "string", description: "Имя, фамилия или email сотрудника" },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
109
120
|
{
|
|
110
121
|
name: "list_team_tasks",
|
|
111
122
|
description: "Задачи сотрудника — только для администраторов. Возвращает все задачи, где сотрудник является исполнителем. Можно фильтровать по проекту, области, статусу и диапазону дат.",
|
|
@@ -141,6 +152,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
141
152
|
},
|
|
142
153
|
],
|
|
143
154
|
}));
|
|
155
|
+
function withUrl(task) {
|
|
156
|
+
if (typeof task.id === "string") {
|
|
157
|
+
return { ...task, url: `${BASE_URL}/#/tasks/${task.id}` };
|
|
158
|
+
}
|
|
159
|
+
return task;
|
|
160
|
+
}
|
|
161
|
+
function withUrls(tasks) {
|
|
162
|
+
return tasks.map((t) => withUrl(t));
|
|
163
|
+
}
|
|
144
164
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
145
165
|
const { name, arguments: args } = req.params;
|
|
146
166
|
try {
|
|
@@ -157,19 +177,36 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
157
177
|
if (args?.limit)
|
|
158
178
|
params.limit = String(args.limit);
|
|
159
179
|
const data = await apiGet("/api/tasks", params);
|
|
160
|
-
const tasks = data.tasks ?? (Array.isArray(data) ? data : []);
|
|
180
|
+
const tasks = withUrls(data.tasks ?? (Array.isArray(data) ? data : []));
|
|
161
181
|
return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
|
|
162
182
|
}
|
|
163
183
|
if (name === "get_task") {
|
|
164
184
|
const id = String(args?.id ?? "");
|
|
165
185
|
const data = await apiGet(`/api/tasks/${id}`);
|
|
166
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
186
|
+
return { content: [{ type: "text", text: JSON.stringify(withUrl(data), null, 2) }] };
|
|
167
187
|
}
|
|
168
188
|
if (name === "search_tasks") {
|
|
169
189
|
const query = String(args?.query ?? "");
|
|
170
190
|
const limit = args?.limit ? String(args.limit) : "10";
|
|
171
191
|
const data = await apiGet("/api/tasks/search", { q: query, limit });
|
|
172
|
-
|
|
192
|
+
const tasks = withUrls(data.tasks ?? (Array.isArray(data) ? data : []));
|
|
193
|
+
return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
|
|
194
|
+
}
|
|
195
|
+
if (name === "find_employee") {
|
|
196
|
+
const query = String(args?.name ?? "").toLowerCase().trim();
|
|
197
|
+
if (!query)
|
|
198
|
+
return { content: [{ type: "text", text: "Ошибка: name обязателен" }] };
|
|
199
|
+
const data = await apiGet("/api/registry");
|
|
200
|
+
const employees = data.employees ?? [];
|
|
201
|
+
const found = employees.filter((e) => {
|
|
202
|
+
const name = (e.displayName ?? "").toLowerCase();
|
|
203
|
+
const email = (e.email ?? "").toLowerCase();
|
|
204
|
+
return name.includes(query) || email.includes(query);
|
|
205
|
+
});
|
|
206
|
+
if (!found.length)
|
|
207
|
+
return { content: [{ type: "text", text: `Сотрудник «${args?.name}» не найден` }] };
|
|
208
|
+
const result = found.map((e) => ({ id: e.id, displayName: e.displayName, email: e.email, profileUrl: `${BASE_URL}/#/team/${e.id}` }));
|
|
209
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
173
210
|
}
|
|
174
211
|
if (name === "propose_task_change") {
|
|
175
212
|
const action = String(args?.action ?? "create");
|
|
@@ -290,12 +327,17 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
290
327
|
if (args?.offset)
|
|
291
328
|
params.offset = String(args.offset);
|
|
292
329
|
const data = await apiGet("/api/tasks/team", params);
|
|
293
|
-
const tasks = data.tasks ?? []
|
|
330
|
+
const tasks = withUrls(data.tasks ?? []).map((t) => {
|
|
331
|
+
if (typeof t.assigneeId === "string") {
|
|
332
|
+
return { ...t, assigneeUrl: `${BASE_URL}/#/team/${t.assigneeId}` };
|
|
333
|
+
}
|
|
334
|
+
return t;
|
|
335
|
+
});
|
|
294
336
|
const total = data.total ?? tasks.length;
|
|
295
337
|
return {
|
|
296
338
|
content: [{
|
|
297
339
|
type: "text",
|
|
298
|
-
text: `Найдено задач: ${total}\n\n${JSON.stringify(tasks, null, 2)}`,
|
|
340
|
+
text: `Найдено задач: ${total}\n\nПри отображении форматируй названия задач как markdown-ссылки [название](url), имена исполнителей — как [имя](assigneeUrl).\n\n${JSON.stringify(tasks, null, 2)}`,
|
|
299
341
|
}],
|
|
300
342
|
};
|
|
301
343
|
}
|
|
@@ -326,7 +368,18 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
326
368
|
const completedLate = filtered.filter((t) => t.status === "completed" && t.dueDate && t.completedAt && t.completedAt.slice(0, 10) > t.dueDate);
|
|
327
369
|
const overdue = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate < today);
|
|
328
370
|
const inProgress = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate >= today);
|
|
329
|
-
const fmt = (list) => list.map((t) =>
|
|
371
|
+
const fmt = (list) => list.map((t) => {
|
|
372
|
+
const taskUrl = `${BASE_URL}/#/tasks/${t.id}`;
|
|
373
|
+
const assigneePart = t.assigneeName
|
|
374
|
+
? `, [${t.assigneeName}](${BASE_URL}/#/team/${assigneeId})`
|
|
375
|
+
: "";
|
|
376
|
+
const parts = [`срок: ${t.dueDate ?? "—"}`];
|
|
377
|
+
if (t.completedAt)
|
|
378
|
+
parts.push(`выполнена: ${t.completedAt.slice(0, 10)}`);
|
|
379
|
+
if (t.projectName)
|
|
380
|
+
parts.push(`проект: ${t.projectName}`);
|
|
381
|
+
return ` • [${t.title}](${taskUrl}) (${parts.join(", ")}${assigneePart})`;
|
|
382
|
+
}).join("\n");
|
|
330
383
|
const period = dateFrom || dateTo ? ` (период: ${dateFrom ?? "..."} — ${dateTo ?? "..."})` : "";
|
|
331
384
|
const lines = [
|
|
332
385
|
`Отчёт по сотруднику${period}`,
|
|
@@ -353,5 +406,69 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
353
406
|
return { content: [{ type: "text", text: `Ошибка: ${msg}` }], isError: true };
|
|
354
407
|
}
|
|
355
408
|
});
|
|
409
|
+
const PROMPTS = [
|
|
410
|
+
{
|
|
411
|
+
name: "задачи-сегодня",
|
|
412
|
+
description: "Что у меня запланировано на сегодня",
|
|
413
|
+
arguments: [],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "что-просрочено",
|
|
417
|
+
description: "Мои просроченные задачи и дедлайны",
|
|
418
|
+
arguments: [],
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "создать-задачу",
|
|
422
|
+
description: "Создать новую задачу",
|
|
423
|
+
arguments: [{ name: "описание", description: "Что нужно сделать", required: true }],
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "отчёт-сотрудника",
|
|
427
|
+
description: "Отчёт по задачам сотрудника за период (для руководителей)",
|
|
428
|
+
arguments: [
|
|
429
|
+
{ name: "сотрудник", description: "Имя или email сотрудника", required: true },
|
|
430
|
+
{ name: "период", description: "Например: июнь 2026 или 2026-06-01 / 2026-06-30", required: false },
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "найти-задачу",
|
|
435
|
+
description: "Найти задачу по теме или ключевым словам",
|
|
436
|
+
arguments: [{ name: "тема", description: "Что искать", required: true }],
|
|
437
|
+
},
|
|
438
|
+
];
|
|
439
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
440
|
+
prompts: PROMPTS.map(({ name, description, arguments: args }) => ({
|
|
441
|
+
name,
|
|
442
|
+
description,
|
|
443
|
+
arguments: args,
|
|
444
|
+
})),
|
|
445
|
+
}));
|
|
446
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
447
|
+
const { name, arguments: promptArgs } = req.params;
|
|
448
|
+
const messages = [];
|
|
449
|
+
if (name === "задачи-сегодня") {
|
|
450
|
+
messages.push({ role: "user", content: { type: "text", text: "Покажи мои задачи на сегодня" } });
|
|
451
|
+
}
|
|
452
|
+
else if (name === "что-просрочено") {
|
|
453
|
+
messages.push({ role: "user", content: { type: "text", text: "Что у меня просрочено? Покажи задачи с прошедшим дедлайном" } });
|
|
454
|
+
}
|
|
455
|
+
else if (name === "создать-задачу") {
|
|
456
|
+
const desc = promptArgs?.["описание"] ?? "...";
|
|
457
|
+
messages.push({ role: "user", content: { type: "text", text: `Создай задачу: ${desc}` } });
|
|
458
|
+
}
|
|
459
|
+
else if (name === "отчёт-сотрудника") {
|
|
460
|
+
const emp = promptArgs?.["сотрудник"] ?? "...";
|
|
461
|
+
const period = promptArgs?.["период"] ? ` за ${promptArgs["период"]}` : "";
|
|
462
|
+
messages.push({ role: "user", content: { type: "text", text: `Сначала найди сотрудника «${emp}» через find_employee, затем сделай отчёт по его задачам${period}: что просрочено, что выполнено вовремя, что в работе` } });
|
|
463
|
+
}
|
|
464
|
+
else if (name === "найти-задачу") {
|
|
465
|
+
const topic = promptArgs?.["тема"] ?? "...";
|
|
466
|
+
messages.push({ role: "user", content: { type: "text", text: `Найди задачи по теме: ${topic}` } });
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
messages.push({ role: "user", content: { type: "text", text: name } });
|
|
470
|
+
}
|
|
471
|
+
return { messages };
|
|
472
|
+
});
|
|
356
473
|
const transport = new StdioServerTransport();
|
|
357
474
|
await server.connect(transport);
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const BASE_URL = (process.env.SENQ_BASE_URL ?? "https://senq.serenity.agency").replace(/\/$/, "");
|
|
1
|
+
export const BASE_URL = (process.env.SENQ_BASE_URL ?? "https://senq.serenity.agency").replace(/\/$/, "");
|
|
2
2
|
|
|
3
3
|
function getApiKey(): string {
|
|
4
4
|
const key = process.env.SENQ_API_KEY ?? "";
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import {
|
|
5
5
|
CallToolRequestSchema,
|
|
6
6
|
ListToolsRequestSchema,
|
|
7
|
+
ListPromptsRequestSchema,
|
|
8
|
+
GetPromptRequestSchema,
|
|
7
9
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
-
import { apiGet, apiPost, apiPatch } from "./client.js";
|
|
10
|
+
import { apiGet, apiPost, apiPatch, BASE_URL } from "./client.js";
|
|
9
11
|
import { storePending, consumePending } from "./pending-changes.js";
|
|
10
12
|
|
|
11
13
|
if (process.argv[2] === "setup") {
|
|
@@ -15,8 +17,8 @@ if (process.argv[2] === "setup") {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const server = new Server(
|
|
18
|
-
{ name: "senq-mcp", version: "1.
|
|
19
|
-
{ capabilities: { tools: {} } },
|
|
20
|
+
{ name: "senq-mcp", version: "1.4.1" },
|
|
21
|
+
{ capabilities: { tools: {}, prompts: {} } },
|
|
20
22
|
);
|
|
21
23
|
|
|
22
24
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -115,6 +117,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
115
117
|
},
|
|
116
118
|
},
|
|
117
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "find_employee",
|
|
122
|
+
description: "Найти сотрудника по имени или email. Возвращает ID, который нужен для list_team_tasks и get_team_report.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
required: ["name"],
|
|
126
|
+
properties: {
|
|
127
|
+
name: { type: "string", description: "Имя, фамилия или email сотрудника" },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
118
131
|
{
|
|
119
132
|
name: "list_team_tasks",
|
|
120
133
|
description: "Задачи сотрудника — только для администраторов. Возвращает все задачи, где сотрудник является исполнителем. Можно фильтровать по проекту, области, статусу и диапазону дат.",
|
|
@@ -151,6 +164,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
151
164
|
],
|
|
152
165
|
}));
|
|
153
166
|
|
|
167
|
+
type TaskLike = Record<string, unknown>;
|
|
168
|
+
|
|
169
|
+
function withUrl(task: TaskLike): TaskLike {
|
|
170
|
+
if (typeof task.id === "string") {
|
|
171
|
+
return { ...task, url: `${BASE_URL}/#/tasks/${task.id}` };
|
|
172
|
+
}
|
|
173
|
+
return task;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function withUrls(tasks: unknown[]): TaskLike[] {
|
|
177
|
+
return tasks.map((t) => withUrl(t as TaskLike));
|
|
178
|
+
}
|
|
179
|
+
|
|
154
180
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
155
181
|
const { name, arguments: args } = req.params;
|
|
156
182
|
|
|
@@ -163,21 +189,37 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
163
189
|
if (args?.status) params.status = String(args.status);
|
|
164
190
|
if (args?.limit) params.limit = String(args.limit);
|
|
165
191
|
const data = await apiGet<{ tasks?: unknown[] }>("/api/tasks", params);
|
|
166
|
-
const tasks = data.tasks ?? (Array.isArray(data) ? data : []);
|
|
192
|
+
const tasks = withUrls(data.tasks ?? (Array.isArray(data) ? data : []));
|
|
167
193
|
return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
if (name === "get_task") {
|
|
171
197
|
const id = String(args?.id ?? "");
|
|
172
|
-
const data = await apiGet<
|
|
173
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
198
|
+
const data = await apiGet<TaskLike>(`/api/tasks/${id}`);
|
|
199
|
+
return { content: [{ type: "text", text: JSON.stringify(withUrl(data), null, 2) }] };
|
|
174
200
|
}
|
|
175
201
|
|
|
176
202
|
if (name === "search_tasks") {
|
|
177
203
|
const query = String(args?.query ?? "");
|
|
178
204
|
const limit = args?.limit ? String(args.limit) : "10";
|
|
179
|
-
const data = await apiGet<unknown>("/api/tasks/search", { q: query, limit });
|
|
180
|
-
|
|
205
|
+
const data = await apiGet<{ tasks?: unknown[] }>("/api/tasks/search", { q: query, limit });
|
|
206
|
+
const tasks = withUrls(data.tasks ?? (Array.isArray(data) ? data : []));
|
|
207
|
+
return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (name === "find_employee") {
|
|
211
|
+
const query = String(args?.name ?? "").toLowerCase().trim();
|
|
212
|
+
if (!query) return { content: [{ type: "text", text: "Ошибка: name обязателен" }] };
|
|
213
|
+
const data = await apiGet<{ employees?: Array<{ id: string; displayName?: string; email?: string; slackUserId?: string }> }>("/api/registry");
|
|
214
|
+
const employees = data.employees ?? [];
|
|
215
|
+
const found = employees.filter((e) => {
|
|
216
|
+
const name = (e.displayName ?? "").toLowerCase();
|
|
217
|
+
const email = (e.email ?? "").toLowerCase();
|
|
218
|
+
return name.includes(query) || email.includes(query);
|
|
219
|
+
});
|
|
220
|
+
if (!found.length) return { content: [{ type: "text", text: `Сотрудник «${args?.name}» не найден` }] };
|
|
221
|
+
const result = found.map((e) => ({ id: e.id, displayName: e.displayName, email: e.email, profileUrl: `${BASE_URL}/#/team/${e.id}` }));
|
|
222
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
181
223
|
}
|
|
182
224
|
|
|
183
225
|
if (name === "propose_task_change") {
|
|
@@ -267,12 +309,17 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
267
309
|
if (args?.limit) params.limit = String(args.limit);
|
|
268
310
|
if (args?.offset) params.offset = String(args.offset);
|
|
269
311
|
const data = await apiGet<{ tasks?: unknown[]; total?: number }>("/api/tasks/team", params);
|
|
270
|
-
const tasks = data.tasks ?? []
|
|
312
|
+
const tasks = withUrls(data.tasks ?? []).map((t) => {
|
|
313
|
+
if (typeof t.assigneeId === "string") {
|
|
314
|
+
return { ...t, assigneeUrl: `${BASE_URL}/#/team/${t.assigneeId}` };
|
|
315
|
+
}
|
|
316
|
+
return t;
|
|
317
|
+
});
|
|
271
318
|
const total = data.total ?? tasks.length;
|
|
272
319
|
return {
|
|
273
320
|
content: [{
|
|
274
321
|
type: "text",
|
|
275
|
-
text: `Найдено задач: ${total}\n\n${JSON.stringify(tasks, null, 2)}`,
|
|
322
|
+
text: `Найдено задач: ${total}\n\nПри отображении форматируй названия задач как markdown-ссылки [название](url), имена исполнителей — как [имя](assigneeUrl).\n\n${JSON.stringify(tasks, null, 2)}`,
|
|
276
323
|
}],
|
|
277
324
|
};
|
|
278
325
|
}
|
|
@@ -310,7 +357,16 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
310
357
|
const inProgress = filtered.filter((t) => t.status === "open" && t.dueDate && t.dueDate >= today);
|
|
311
358
|
|
|
312
359
|
const fmt = (list: typeof filtered) =>
|
|
313
|
-
list.map((t) =>
|
|
360
|
+
list.map((t) => {
|
|
361
|
+
const taskUrl = `${BASE_URL}/#/tasks/${t.id}`;
|
|
362
|
+
const assigneePart = t.assigneeName
|
|
363
|
+
? `, [${t.assigneeName}](${BASE_URL}/#/team/${assigneeId})`
|
|
364
|
+
: "";
|
|
365
|
+
const parts = [`срок: ${t.dueDate ?? "—"}`];
|
|
366
|
+
if (t.completedAt) parts.push(`выполнена: ${t.completedAt.slice(0, 10)}`);
|
|
367
|
+
if (t.projectName) parts.push(`проект: ${t.projectName}`);
|
|
368
|
+
return ` • [${t.title}](${taskUrl}) (${parts.join(", ")}${assigneePart})`;
|
|
369
|
+
}).join("\n");
|
|
314
370
|
|
|
315
371
|
const period = dateFrom || dateTo ? ` (период: ${dateFrom ?? "..."} — ${dateTo ?? "..."})` : "";
|
|
316
372
|
const lines = [
|
|
@@ -340,5 +396,69 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
340
396
|
}
|
|
341
397
|
});
|
|
342
398
|
|
|
399
|
+
const PROMPTS = [
|
|
400
|
+
{
|
|
401
|
+
name: "задачи-сегодня",
|
|
402
|
+
description: "Что у меня запланировано на сегодня",
|
|
403
|
+
arguments: [],
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: "что-просрочено",
|
|
407
|
+
description: "Мои просроченные задачи и дедлайны",
|
|
408
|
+
arguments: [],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "создать-задачу",
|
|
412
|
+
description: "Создать новую задачу",
|
|
413
|
+
arguments: [{ name: "описание", description: "Что нужно сделать", required: true }],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "отчёт-сотрудника",
|
|
417
|
+
description: "Отчёт по задачам сотрудника за период (для руководителей)",
|
|
418
|
+
arguments: [
|
|
419
|
+
{ name: "сотрудник", description: "Имя или email сотрудника", required: true },
|
|
420
|
+
{ name: "период", description: "Например: июнь 2026 или 2026-06-01 / 2026-06-30", required: false },
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "найти-задачу",
|
|
425
|
+
description: "Найти задачу по теме или ключевым словам",
|
|
426
|
+
arguments: [{ name: "тема", description: "Что искать", required: true }],
|
|
427
|
+
},
|
|
428
|
+
] as const;
|
|
429
|
+
|
|
430
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
431
|
+
prompts: PROMPTS.map(({ name, description, arguments: args }) => ({
|
|
432
|
+
name,
|
|
433
|
+
description,
|
|
434
|
+
arguments: (args as unknown) as { name: string; description: string; required: boolean }[],
|
|
435
|
+
})),
|
|
436
|
+
}));
|
|
437
|
+
|
|
438
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
439
|
+
const { name, arguments: promptArgs } = req.params;
|
|
440
|
+
const messages: { role: "user"; content: { type: "text"; text: string } }[] = [];
|
|
441
|
+
|
|
442
|
+
if (name === "задачи-сегодня") {
|
|
443
|
+
messages.push({ role: "user", content: { type: "text", text: "Покажи мои задачи на сегодня" } });
|
|
444
|
+
} else if (name === "что-просрочено") {
|
|
445
|
+
messages.push({ role: "user", content: { type: "text", text: "Что у меня просрочено? Покажи задачи с прошедшим дедлайном" } });
|
|
446
|
+
} else if (name === "создать-задачу") {
|
|
447
|
+
const desc = promptArgs?.["описание"] ?? "...";
|
|
448
|
+
messages.push({ role: "user", content: { type: "text", text: `Создай задачу: ${desc}` } });
|
|
449
|
+
} else if (name === "отчёт-сотрудника") {
|
|
450
|
+
const emp = promptArgs?.["сотрудник"] ?? "...";
|
|
451
|
+
const period = promptArgs?.["период"] ? ` за ${promptArgs["период"]}` : "";
|
|
452
|
+
messages.push({ role: "user", content: { type: "text", text: `Сначала найди сотрудника «${emp}» через find_employee, затем сделай отчёт по его задачам${period}: что просрочено, что выполнено вовремя, что в работе` } });
|
|
453
|
+
} else if (name === "найти-задачу") {
|
|
454
|
+
const topic = promptArgs?.["тема"] ?? "...";
|
|
455
|
+
messages.push({ role: "user", content: { type: "text", text: `Найди задачи по теме: ${topic}` } });
|
|
456
|
+
} else {
|
|
457
|
+
messages.push({ role: "user", content: { type: "text", text: name } });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return { messages };
|
|
461
|
+
});
|
|
462
|
+
|
|
343
463
|
const transport = new StdioServerTransport();
|
|
344
464
|
await server.connect(transport);
|