@leeguoo/zentao-mcp 0.4.1 → 0.5.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/README.md +93 -139
- package/package.json +13 -13
- package/skills/zentao-cli.md +130 -0
- package/src/cli/args.js +99 -0
- package/src/cli/help.js +49 -0
- package/src/commands/bug.js +69 -0
- package/src/commands/bugs.js +138 -0
- package/src/commands/login.js +60 -0
- package/src/commands/products.js +58 -0
- package/src/commands/release.js +198 -0
- package/src/commands/selftest.js +52 -0
- package/src/commands/whoami.js +32 -0
- package/src/config/store.js +81 -0
- package/src/index.js +49 -453
- package/src/zentao/client.js +324 -0
package/src/index.js
CHANGED
|
@@ -1,475 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
i += 1;
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
args[key] = true;
|
|
24
|
-
}
|
|
25
|
-
return args;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getOption(cliArgs, envName, cliName) {
|
|
29
|
-
if (cliArgs[cliName]) return cliArgs[cliName];
|
|
30
|
-
const envValue = process.env[envName];
|
|
31
|
-
if (envValue) return envValue;
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalizeBaseUrl(url) {
|
|
36
|
-
return url.replace(/\/+$/, "");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function toInt(value, fallback) {
|
|
40
|
-
if (value === undefined || value === null || value === "") return fallback;
|
|
41
|
-
const parsed = Number(value);
|
|
42
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { extractCommand, hasHelpFlag } from "./cli/args.js";
|
|
4
|
+
import { printRootHelp } from "./cli/help.js";
|
|
5
|
+
import { runSelfTest } from "./commands/selftest.js";
|
|
6
|
+
import { runRelease } from "./commands/release.js";
|
|
7
|
+
import { runProducts } from "./commands/products.js";
|
|
8
|
+
import { runBugs } from "./commands/bugs.js";
|
|
9
|
+
import { runBug } from "./commands/bug.js";
|
|
10
|
+
import { runLogin } from "./commands/login.js";
|
|
11
|
+
import { runWhoami } from "./commands/whoami.js";
|
|
12
|
+
|
|
13
|
+
const argv = process.argv.slice(2);
|
|
14
|
+
const { command, argv: argvWithoutCommand } = extractCommand(argv);
|
|
15
|
+
|
|
16
|
+
function exitWithError(error) {
|
|
17
|
+
const message = error?.message || String(error);
|
|
18
|
+
process.stderr.write(`${message}\n`);
|
|
19
|
+
process.exit(error?.exitCode || 1);
|
|
43
20
|
}
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
function normalizeError(message, payload) {
|
|
50
|
-
return { status: 0, msg: message || "error", result: payload ?? [] };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function normalizeAccountValue(value) {
|
|
54
|
-
return String(value || "").trim().toLowerCase();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function extractAccounts(value) {
|
|
58
|
-
if (value === undefined || value === null) return [];
|
|
59
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
60
|
-
const normalized = normalizeAccountValue(value);
|
|
61
|
-
return normalized ? [normalized] : [];
|
|
62
|
-
}
|
|
63
|
-
if (Array.isArray(value)) {
|
|
64
|
-
return value.flatMap((item) => extractAccounts(item));
|
|
65
|
-
}
|
|
66
|
-
if (typeof value === "object") {
|
|
67
|
-
if (value.account) return extractAccounts(value.account);
|
|
68
|
-
if (value.user) return extractAccounts(value.user);
|
|
69
|
-
if (value.name) return extractAccounts(value.name);
|
|
70
|
-
if (value.realname) return extractAccounts(value.realname);
|
|
71
|
-
return [];
|
|
22
|
+
try {
|
|
23
|
+
if (!command || hasHelpFlag(argv)) {
|
|
24
|
+
printRootHelp();
|
|
25
|
+
process.exit(0);
|
|
72
26
|
}
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function matchesAccount(value, matchAccount) {
|
|
77
|
-
const candidates = extractAccounts(value);
|
|
78
|
-
return candidates.includes(matchAccount);
|
|
79
|
-
}
|
|
80
27
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.account = account;
|
|
85
|
-
this.password = password;
|
|
86
|
-
this.token = null;
|
|
28
|
+
if (command === "self-test") {
|
|
29
|
+
await runSelfTest({ argv: argvWithoutCommand, env: process.env });
|
|
30
|
+
process.exit(0);
|
|
87
31
|
}
|
|
88
32
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
33
|
+
if (command === "release") {
|
|
34
|
+
await runRelease({ argv: argvWithoutCommand, env: process.env });
|
|
35
|
+
process.exit(0);
|
|
92
36
|
}
|
|
93
37
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: { "Content-Type": "application/json" },
|
|
99
|
-
body: JSON.stringify({
|
|
100
|
-
account: this.account,
|
|
101
|
-
password: this.password,
|
|
102
|
-
}),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const text = await res.text();
|
|
106
|
-
let json;
|
|
107
|
-
try {
|
|
108
|
-
json = JSON.parse(text);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
throw new Error(`Token response parse failed: ${text.slice(0, 200)}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (json.error) {
|
|
114
|
-
throw new Error(`Token request failed: ${json.error}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!json.token) {
|
|
118
|
-
throw new Error(`Token missing in response: ${text.slice(0, 200)}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return json.token;
|
|
38
|
+
if (command === "login") {
|
|
39
|
+
await runLogin({ argv: argvWithoutCommand, env: process.env });
|
|
40
|
+
process.exit(0);
|
|
122
41
|
}
|
|
123
42
|
|
|
124
|
-
|
|
125
|
-
await
|
|
126
|
-
|
|
127
|
-
const url = new URL(`${this.baseUrl}${path}`);
|
|
128
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
129
|
-
if (value === undefined || value === null) return;
|
|
130
|
-
url.searchParams.set(key, String(value));
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const headers = {
|
|
134
|
-
Token: this.token,
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const options = { method, headers };
|
|
138
|
-
|
|
139
|
-
if (body !== undefined) {
|
|
140
|
-
headers["Content-Type"] = "application/json";
|
|
141
|
-
options.body = JSON.stringify(body);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const res = await fetch(url, options);
|
|
145
|
-
const text = await res.text();
|
|
146
|
-
let json;
|
|
147
|
-
try {
|
|
148
|
-
json = JSON.parse(text);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
throw new Error(`Response parse failed: ${text.slice(0, 200)}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return json;
|
|
43
|
+
if (command === "whoami") {
|
|
44
|
+
await runWhoami({ argv: argvWithoutCommand, env: process.env });
|
|
45
|
+
process.exit(0);
|
|
154
46
|
}
|
|
155
47
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
path: "/api.php/v1/products",
|
|
160
|
-
query: {
|
|
161
|
-
page: toInt(page, 1),
|
|
162
|
-
limit: toInt(limit, 1000),
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
if (payload.error) return normalizeError(payload.error, payload);
|
|
167
|
-
return normalizeResult(payload);
|
|
48
|
+
if (command === "products") {
|
|
49
|
+
await runProducts({ argv: argvWithoutCommand, env: process.env });
|
|
50
|
+
process.exit(0);
|
|
168
51
|
}
|
|
169
52
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const payload = await this.request({
|
|
174
|
-
method: "GET",
|
|
175
|
-
path: "/api.php/v1/bugs",
|
|
176
|
-
query: {
|
|
177
|
-
product,
|
|
178
|
-
page: toInt(page, 1),
|
|
179
|
-
limit: toInt(limit, 20),
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
if (payload.error) return normalizeError(payload.error, payload);
|
|
184
|
-
return normalizeResult(payload);
|
|
53
|
+
if (command === "bugs") {
|
|
54
|
+
await runBugs({ argv: argvWithoutCommand, env: process.env });
|
|
55
|
+
process.exit(0);
|
|
185
56
|
}
|
|
186
57
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const payload = await this.request({
|
|
191
|
-
method: "GET",
|
|
192
|
-
path: `/api.php/v1/bugs/${id}`,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
if (payload.error) return normalizeError(payload.error, payload);
|
|
196
|
-
return normalizeResult(payload);
|
|
58
|
+
if (command === "bug") {
|
|
59
|
+
await runBug({ argv: argvWithoutCommand, env: process.env });
|
|
60
|
+
process.exit(0);
|
|
197
61
|
}
|
|
198
62
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let total = null;
|
|
203
|
-
const pageSize = toInt(perPage, 100);
|
|
204
|
-
const cap = toInt(maxItems, 0);
|
|
205
|
-
|
|
206
|
-
while (true) {
|
|
207
|
-
const payload = await this.request({
|
|
208
|
-
method: "GET",
|
|
209
|
-
path: "/api.php/v1/bugs",
|
|
210
|
-
query: {
|
|
211
|
-
product,
|
|
212
|
-
page,
|
|
213
|
-
limit: pageSize,
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
if (payload.error) {
|
|
218
|
-
throw new Error(payload.error);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const pageBugs = Array.isArray(payload.bugs) ? payload.bugs : [];
|
|
222
|
-
total = payload.total ?? total;
|
|
223
|
-
for (const bug of pageBugs) {
|
|
224
|
-
bugs.push(bug);
|
|
225
|
-
if (cap > 0 && bugs.length >= cap) {
|
|
226
|
-
return { bugs, total };
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (total !== null && payload.limit) {
|
|
231
|
-
if (page * payload.limit >= total) break;
|
|
232
|
-
} else if (pageBugs.length < pageSize) {
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
page += 1;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return { bugs, total };
|
|
63
|
+
if (command === "help") {
|
|
64
|
+
printRootHelp();
|
|
65
|
+
process.exit(0);
|
|
240
66
|
}
|
|
241
67
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
status,
|
|
246
|
-
productIds,
|
|
247
|
-
includeZero,
|
|
248
|
-
perPage,
|
|
249
|
-
maxItems,
|
|
250
|
-
includeDetails,
|
|
251
|
-
}) {
|
|
252
|
-
const matchAccount = normalizeAccountValue(account || this.account);
|
|
253
|
-
const targetScope = (scope || "assigned").toLowerCase();
|
|
254
|
-
const rawStatus = status ?? "active";
|
|
255
|
-
const statusList = Array.isArray(rawStatus)
|
|
256
|
-
? rawStatus
|
|
257
|
-
: String(rawStatus).split(/[|,]/);
|
|
258
|
-
const statusSet = new Set(
|
|
259
|
-
statusList.map((item) => String(item).trim().toLowerCase()).filter(Boolean)
|
|
260
|
-
);
|
|
261
|
-
const allowAllStatus = statusSet.has("all") || statusSet.size === 0;
|
|
262
|
-
|
|
263
|
-
const productsResponse = await this.listProducts({ page: 1, limit: 1000 });
|
|
264
|
-
if (productsResponse.status !== 1) return productsResponse;
|
|
265
|
-
const products = productsResponse.result.products || [];
|
|
266
|
-
|
|
267
|
-
const productSet = Array.isArray(productIds) && productIds.length
|
|
268
|
-
? new Set(productIds.map((id) => Number(id)))
|
|
269
|
-
: null;
|
|
270
|
-
|
|
271
|
-
const rows = [];
|
|
272
|
-
const bugs = [];
|
|
273
|
-
let totalMatches = 0;
|
|
274
|
-
const maxCollect = toInt(maxItems, 200);
|
|
275
|
-
|
|
276
|
-
for (const product of products) {
|
|
277
|
-
if (productSet && !productSet.has(Number(product.id))) continue;
|
|
278
|
-
const { bugs: productBugs } = await this.fetchAllBugsForProduct({
|
|
279
|
-
product: product.id,
|
|
280
|
-
perPage,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
const matches = productBugs.filter((bug) => {
|
|
284
|
-
if (!allowAllStatus) {
|
|
285
|
-
const bugStatus = String(bug.status || "").trim().toLowerCase();
|
|
286
|
-
if (!statusSet.has(bugStatus)) return false;
|
|
287
|
-
}
|
|
288
|
-
const assigned = matchesAccount(bug.assignedTo, matchAccount);
|
|
289
|
-
const opened = matchesAccount(bug.openedBy, matchAccount);
|
|
290
|
-
const resolved = matchesAccount(bug.resolvedBy, matchAccount);
|
|
291
|
-
if (targetScope === "assigned") return assigned;
|
|
292
|
-
if (targetScope === "opened") return opened;
|
|
293
|
-
if (targetScope === "resolved") return resolved;
|
|
294
|
-
return assigned || opened || resolved;
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
if (!includeZero && matches.length === 0) continue;
|
|
298
|
-
totalMatches += matches.length;
|
|
299
|
-
|
|
300
|
-
rows.push({
|
|
301
|
-
id: product.id,
|
|
302
|
-
name: product.name,
|
|
303
|
-
totalBugs: toInt(product.totalBugs, 0),
|
|
304
|
-
myBugs: matches.length,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
if (includeDetails && bugs.length < maxCollect) {
|
|
308
|
-
for (const bug of matches) {
|
|
309
|
-
if (bugs.length >= maxCollect) break;
|
|
310
|
-
bugs.push({
|
|
311
|
-
id: bug.id,
|
|
312
|
-
title: bug.title,
|
|
313
|
-
product: bug.product,
|
|
314
|
-
status: bug.status,
|
|
315
|
-
pri: bug.pri,
|
|
316
|
-
severity: bug.severity,
|
|
317
|
-
assignedTo: bug.assignedTo,
|
|
318
|
-
openedBy: bug.openedBy,
|
|
319
|
-
resolvedBy: bug.resolvedBy,
|
|
320
|
-
openedDate: bug.openedDate,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return normalizeResult({
|
|
327
|
-
account: matchAccount,
|
|
328
|
-
scope: targetScope,
|
|
329
|
-
status: allowAllStatus ? "all" : Array.from(statusSet),
|
|
330
|
-
total: totalMatches,
|
|
331
|
-
products: rows,
|
|
332
|
-
bugs: includeDetails ? bugs : [],
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function createClient() {
|
|
338
|
-
const cliArgs = parseCliArgs(process.argv.slice(2));
|
|
339
|
-
const baseUrl = getOption(cliArgs, "ZENTAO_URL", "zentao-url");
|
|
340
|
-
const account = getOption(cliArgs, "ZENTAO_ACCOUNT", "zentao-account");
|
|
341
|
-
const password = getOption(cliArgs, "ZENTAO_PASSWORD", "zentao-password");
|
|
342
|
-
|
|
343
|
-
if (!baseUrl) throw new Error("Missing ZENTAO_URL or --zentao-url");
|
|
344
|
-
if (!account) throw new Error("Missing ZENTAO_ACCOUNT or --zentao-account");
|
|
345
|
-
if (!password) throw new Error("Missing ZENTAO_PASSWORD or --zentao-password");
|
|
346
|
-
return new ZentaoClient({ baseUrl, account, password });
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
let client;
|
|
350
|
-
function getClient() {
|
|
351
|
-
if (!client) client = createClient();
|
|
352
|
-
return client;
|
|
68
|
+
throw new Error(`Unknown subcommand: ${command}`);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
exitWithError(error);
|
|
353
71
|
}
|
|
354
|
-
|
|
355
|
-
const server = new Server(
|
|
356
|
-
{
|
|
357
|
-
name: "zentao-mcp",
|
|
358
|
-
version: "0.4.1",
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
capabilities: {
|
|
362
|
-
tools: {},
|
|
363
|
-
},
|
|
364
|
-
}
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
const tools = [
|
|
368
|
-
{
|
|
369
|
-
name: "zentao_products_list",
|
|
370
|
-
description: "List all products from ZenTao. Use this to get product IDs before querying bugs. Returns product information including ID, name, and bug counts.",
|
|
371
|
-
inputSchema: {
|
|
372
|
-
type: "object",
|
|
373
|
-
properties: {
|
|
374
|
-
page: { type: "integer", description: "Page number (default 1)." },
|
|
375
|
-
limit: { type: "integer", description: "Page size (default 1000)." },
|
|
376
|
-
},
|
|
377
|
-
additionalProperties: false,
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: "zentao_bugs_list",
|
|
382
|
-
description: "List bugs (缺陷/问题) for a specific product in ZenTao. Use this when user asks to 'see bugs', 'view bugs', 'show bugs', '看bug', '查看bug', '显示bug', or wants to check issues for a product. Requires product ID which can be obtained from zentao_products_list.",
|
|
383
|
-
inputSchema: {
|
|
384
|
-
type: "object",
|
|
385
|
-
properties: {
|
|
386
|
-
product: { type: "integer", description: "Product ID (required). Get this from zentao_products_list first." },
|
|
387
|
-
page: { type: "integer", description: "Page number (default 1)." },
|
|
388
|
-
limit: { type: "integer", description: "Page size (default 20)." },
|
|
389
|
-
},
|
|
390
|
-
required: ["product"],
|
|
391
|
-
additionalProperties: false,
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
{
|
|
395
|
-
name: "zentao_bug_get",
|
|
396
|
-
description: "Get bug details (获取Bug详情) by bug ID.",
|
|
397
|
-
inputSchema: {
|
|
398
|
-
type: "object",
|
|
399
|
-
properties: {
|
|
400
|
-
id: { type: "integer", description: "Bug ID (required)." },
|
|
401
|
-
},
|
|
402
|
-
required: ["id"],
|
|
403
|
-
additionalProperties: false,
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
name: "zentao_bugs_mine",
|
|
408
|
-
description: "List my bugs (我的Bug) by assignment or creator. Default scope is assigned. Use when user asks for 'my bugs', '我的bug', '分配给我', or personal bug list.",
|
|
409
|
-
inputSchema: {
|
|
410
|
-
type: "object",
|
|
411
|
-
properties: {
|
|
412
|
-
account: { type: "string", description: "Account to match (default: login account)." },
|
|
413
|
-
scope: {
|
|
414
|
-
type: "string",
|
|
415
|
-
description: "Filter scope: assigned|opened|resolved|all (default assigned).",
|
|
416
|
-
},
|
|
417
|
-
status: {
|
|
418
|
-
type: ["string", "array"],
|
|
419
|
-
description: "Status filter: active|resolved|closed|all (default active).",
|
|
420
|
-
},
|
|
421
|
-
productIds: {
|
|
422
|
-
type: "array",
|
|
423
|
-
items: { type: "integer" },
|
|
424
|
-
description: "Optional product IDs to limit search.",
|
|
425
|
-
},
|
|
426
|
-
includeZero: { type: "boolean", description: "Include products with zero matches (default false)." },
|
|
427
|
-
perPage: { type: "integer", description: "Page size when scanning products (default 100)." },
|
|
428
|
-
maxItems: { type: "integer", description: "Max bug items to return (default 200)." },
|
|
429
|
-
includeDetails: { type: "boolean", description: "Include bug details list (default false)." },
|
|
430
|
-
},
|
|
431
|
-
additionalProperties: false,
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
|
-
];
|
|
435
|
-
|
|
436
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
437
|
-
|
|
438
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
439
|
-
const args = request.params.arguments || {};
|
|
440
|
-
try {
|
|
441
|
-
const api = getClient();
|
|
442
|
-
let result;
|
|
443
|
-
switch (request.params.name) {
|
|
444
|
-
case "zentao_products_list":
|
|
445
|
-
result = await api.listProducts(args);
|
|
446
|
-
break;
|
|
447
|
-
case "zentao_bugs_list":
|
|
448
|
-
result = await api.listBugs(args);
|
|
449
|
-
break;
|
|
450
|
-
case "zentao_bug_get":
|
|
451
|
-
result = await api.getBug(args);
|
|
452
|
-
break;
|
|
453
|
-
case "zentao_bugs_mine":
|
|
454
|
-
result = await api.bugsMine(args);
|
|
455
|
-
break;
|
|
456
|
-
default:
|
|
457
|
-
return {
|
|
458
|
-
isError: true,
|
|
459
|
-
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return {
|
|
464
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
465
|
-
};
|
|
466
|
-
} catch (error) {
|
|
467
|
-
return {
|
|
468
|
-
isError: true,
|
|
469
|
-
content: [{ type: "text", text: error.message }],
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
const transport = new StdioServerTransport();
|
|
475
|
-
await server.connect(transport);
|