@k-system/tickr-mcp 1.28.1 → 1.29.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/channel.d.ts +1 -0
- package/dist/channel.js +106 -8
- package/dist/server.js +1 -1
- package/package.json +2 -2
package/dist/channel.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* TICKR_TOKEN_ID — Guid tokenId (fallback)
|
|
19
19
|
* TICKR_AGENT_NAME — Název agenta pro session lookup (default: z config)
|
|
20
20
|
* TICKR_TENANT_ID — Tenant ID pro device grant
|
|
21
|
+
* TICKR_PROJECT — Project slug filtr (volitelný, pokud chybí = bez filtru)
|
|
21
22
|
* TICKR_NO_SIGNALR — "1" pro HTTP-only mode (bez SignalR)
|
|
22
23
|
*/
|
|
23
24
|
export {};
|
package/dist/channel.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* TICKR_TOKEN_ID — Guid tokenId (fallback)
|
|
19
19
|
* TICKR_AGENT_NAME — Název agenta pro session lookup (default: z config)
|
|
20
20
|
* TICKR_TENANT_ID — Tenant ID pro device grant
|
|
21
|
+
* TICKR_PROJECT — Project slug filtr (volitelný, pokud chybí = bez filtru)
|
|
21
22
|
* TICKR_NO_SIGNALR — "1" pro HTTP-only mode (bez SignalR)
|
|
22
23
|
*/
|
|
23
24
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -25,9 +26,42 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
25
26
|
import { HubConnectionBuilder } from "@microsoft/signalr";
|
|
26
27
|
import { loadConfig } from "./config.js";
|
|
27
28
|
import { getDeviceGrantToken, loadSession, extractJwtClaim, } from "./device-grant.js";
|
|
29
|
+
// --- Helpers ---
|
|
30
|
+
/** Joinne SignalR project groups aby channel dostával PipelineHandoff/DevTaskQueued eventy */
|
|
31
|
+
async function joinProjectGroups(connection, projectFilter, apiUrl, accessTokenFactory) {
|
|
32
|
+
if (projectFilter) {
|
|
33
|
+
try {
|
|
34
|
+
await connection.invoke("JoinProject", projectFilter);
|
|
35
|
+
console.error(`[tickr-channel] Joined project group: ${projectFilter}`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error(`[tickr-channel] Failed to join project group: ${err instanceof Error ? err.message : err}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Bez PROJECT_FILTER — získat projekty přes API a joinovat všechny
|
|
43
|
+
try {
|
|
44
|
+
const token = await accessTokenFactory();
|
|
45
|
+
const resp = await fetch(`${apiUrl}/api/projects`, {
|
|
46
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
47
|
+
});
|
|
48
|
+
if (resp.ok) {
|
|
49
|
+
const body = (await resp.json());
|
|
50
|
+
for (const p of body.data ?? []) {
|
|
51
|
+
await connection.invoke("JoinProject", p.slug).catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
console.error(`[tickr-channel] Joined ${body.data?.length ?? 0} project groups`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(`[tickr-channel] Failed to fetch projects for JoinProject: ${err instanceof Error ? err.message : err}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
28
61
|
// --- Config ---
|
|
29
62
|
const config = loadConfig();
|
|
30
63
|
const NO_SIGNALR = process.env.TICKR_NO_SIGNALR === "1";
|
|
64
|
+
const PROJECT_FILTER = process.env.TICKR_PROJECT || null;
|
|
31
65
|
// --- MCP Channel Server ---
|
|
32
66
|
const mcp = new Server({ name: "tickr-devqueue", version: "1.0.0" }, {
|
|
33
67
|
capabilities: {
|
|
@@ -151,25 +185,87 @@ if (!NO_SIGNALR) {
|
|
|
151
185
|
});
|
|
152
186
|
// Nový ticket v queue
|
|
153
187
|
connection.on("DevTaskQueued", async (data) => {
|
|
188
|
+
// Filtruj podle projektu (pokud je TICKR_PROJECT nastaven)
|
|
189
|
+
if (PROJECT_FILTER && data.projectSlug && data.projectSlug !== PROJECT_FILTER)
|
|
190
|
+
return;
|
|
154
191
|
await sendChannelNotification(`Novy ticket ve fronte: ${data.ticketDisplayNumber ?? "unknown"}`, { event: "task_queued" });
|
|
155
192
|
});
|
|
156
193
|
// Pipeline handoff — cílená notifikace
|
|
194
|
+
// Auto-poll: channel server sám zavolá poll_dev_queue a pošle agentovi strukturovaná data
|
|
157
195
|
connection.on("PipelineHandoff", async (data) => {
|
|
158
196
|
if (!data.assignedTokenId || data.assignedTokenId !== tokenId)
|
|
159
197
|
return;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
// Filtruj podle projektu (pokud je TICKR_PROJECT nastaven)
|
|
199
|
+
if (PROJECT_FILTER && data.projectSlug && data.projectSlug !== PROJECT_FILTER)
|
|
200
|
+
return;
|
|
201
|
+
let pollResult = null;
|
|
202
|
+
try {
|
|
203
|
+
// Timeout 10s — zabrání zaseknutí handleru pokud API neodpovídá
|
|
204
|
+
const abortController = new AbortController();
|
|
205
|
+
const timeout = setTimeout(() => abortController.abort(), 10_000);
|
|
206
|
+
const token = await accessTokenFactory();
|
|
207
|
+
const pollResponse = await fetch(`${config.apiUrl}/api/dev-queue/poll`, {
|
|
208
|
+
method: "GET",
|
|
209
|
+
headers: {
|
|
210
|
+
Authorization: `Bearer ${token}`,
|
|
211
|
+
Accept: "application/json",
|
|
212
|
+
},
|
|
213
|
+
signal: abortController.signal,
|
|
214
|
+
});
|
|
215
|
+
clearTimeout(timeout);
|
|
216
|
+
if (pollResponse.ok) {
|
|
217
|
+
const body = (await pollResponse.json());
|
|
218
|
+
pollResult = body?.data ?? null;
|
|
219
|
+
console.error(`[tickr-channel] Auto-poll success for ${data.ticketDisplayNumber}: assignmentId=${pollResult?.assignmentId ?? "none"}`);
|
|
220
|
+
}
|
|
221
|
+
else if (pollResponse.status === 204) {
|
|
222
|
+
console.error("[tickr-channel] Auto-poll: no tasks in queue (204)");
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
console.error(`[tickr-channel] Auto-poll failed: HTTP ${pollResponse.status}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
console.error("[tickr-channel] Auto-poll error:", err);
|
|
230
|
+
}
|
|
231
|
+
const ticket = pollResult?.ticket;
|
|
232
|
+
if (pollResult?.assignmentId && ticket) {
|
|
233
|
+
// Strukturovaná notifikace s daty ticketu — agent může rovnou začít pracovat
|
|
234
|
+
await sendChannelNotification(`Pipeline handoff: ${ticket.displayNumber ?? data.ticketDisplayNumber} "${ticket.title ?? data.ticketTitle}" [${ticket.priority ?? data.priority}] — ukol prirazen (assignmentId: ${pollResult.assignmentId}). Zavolej /check-queue pro zahajeni prace.`, {
|
|
235
|
+
event: "pipeline_handoff",
|
|
236
|
+
ticket: ticket.displayNumber ?? data.ticketDisplayNumber,
|
|
237
|
+
title: ticket.title ?? data.ticketTitle,
|
|
238
|
+
priority: ticket.priority ?? data.priority,
|
|
239
|
+
assignment_id: pollResult.assignmentId,
|
|
240
|
+
from_status: data.fromStatus,
|
|
241
|
+
to_status: data.toStatus,
|
|
242
|
+
auto_polled: true,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Fallback: poll selhal nebo prázdná fronta — pošli původní textovou notifikaci
|
|
247
|
+
await sendChannelNotification(`Pipeline handoff: ${data.ticketDisplayNumber} "${data.ticketTitle}" [${data.priority}] — status: ${data.fromStatus} → ${data.toStatus}. Zavolej poll_dev_queue pro prijeti ukolu.`, {
|
|
248
|
+
event: "pipeline_handoff",
|
|
249
|
+
ticket: data.ticketDisplayNumber,
|
|
250
|
+
priority: data.priority,
|
|
251
|
+
from_status: data.fromStatus,
|
|
252
|
+
to_status: data.toStatus,
|
|
253
|
+
auto_polled: false,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
167
256
|
});
|
|
168
257
|
connection.onreconnecting(() => {
|
|
169
258
|
console.error("[tickr-channel] SignalR reconnecting...");
|
|
170
259
|
});
|
|
171
|
-
connection.onreconnected(() => {
|
|
260
|
+
connection.onreconnected(async () => {
|
|
172
261
|
console.error("[tickr-channel] SignalR reconnected");
|
|
262
|
+
// Re-join project groups po reconnectu
|
|
263
|
+
try {
|
|
264
|
+
await joinProjectGroups(connection, PROJECT_FILTER, config.apiUrl, accessTokenFactory);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// joinProjectGroups má vlastní error handling, toto je safety net
|
|
268
|
+
}
|
|
173
269
|
});
|
|
174
270
|
connection.onclose((err) => {
|
|
175
271
|
console.error("[tickr-channel] SignalR connection closed", err?.message);
|
|
@@ -177,6 +273,8 @@ if (!NO_SIGNALR) {
|
|
|
177
273
|
try {
|
|
178
274
|
await connection.start();
|
|
179
275
|
console.error(`[tickr-channel] Connected to ${config.apiUrl}/hubs/board, filtering for tokenId=${tokenId}`);
|
|
276
|
+
// Joinni project groups aby channel dostával PipelineHandoff a DevTaskQueued eventy
|
|
277
|
+
await joinProjectGroups(connection, PROJECT_FILTER, config.apiUrl, accessTokenFactory);
|
|
180
278
|
}
|
|
181
279
|
catch (err) {
|
|
182
280
|
console.error(`[tickr-channel] SignalR connection failed: ${err instanceof Error ? err.message : err}`);
|
package/dist/server.js
CHANGED
|
@@ -112,7 +112,7 @@ export async function startServer() {
|
|
|
112
112
|
}
|
|
113
113
|
const server = new McpServer({
|
|
114
114
|
name: "tickr",
|
|
115
|
-
version: "1.
|
|
115
|
+
version: "1.29.1",
|
|
116
116
|
});
|
|
117
117
|
// Debug logging wrapper (dedup odstraněn — nefunkční cross-process, řeší se na API straně: TKR-ADR-0043)
|
|
118
118
|
{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k-system/tickr-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Tickr project management —
|
|
3
|
+
"version": "1.29.1",
|
|
4
|
+
"description": "MCP server for Tickr project management — 74 tools + setup CLI wizard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|