tapd-mcp 1.0.0
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/LICENSE +21 -0
- package/README.md +355 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +777 -0
- package/dist/index.js.map +1 -0
- package/dist/tapd-client.d.ts +314 -0
- package/dist/tapd-client.d.ts.map +1 -0
- package/dist/tapd-client.js +479 -0
- package/dist/tapd-client.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TAPD MCP Server - Model Context Protocol server for TAPD operations
|
|
4
|
+
*
|
|
5
|
+
* Supports: Stories, Tasks, Bugs, Iterations, Comments, Releases
|
|
6
|
+
*/
|
|
7
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
import { TapdClient } from './tapd-client.js';
|
|
11
|
+
const API_TOKEN = process.env.TAPD_API_TOKEN;
|
|
12
|
+
const DEFAULT_WORKSPACE_ID = process.env.TAPD_WORKSPACE_ID;
|
|
13
|
+
const CURRENT_USER = process.env.TAPD_CURRENT_USER;
|
|
14
|
+
if (!API_TOKEN) {
|
|
15
|
+
console.error('Error: TAPD_API_TOKEN environment variable is required');
|
|
16
|
+
console.error('Get your token from: https://www.tapd.cn/tapd_api_token/token');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const client = new TapdClient({ apiToken: API_TOKEN });
|
|
20
|
+
const tools = [
|
|
21
|
+
// ==================== Story Tools ====================
|
|
22
|
+
{
|
|
23
|
+
name: 'tapd_get_story',
|
|
24
|
+
description: 'Get details of a specific story/requirement by ID or URL. ' +
|
|
25
|
+
'By default returns only id/name/status to keep responses small; ' +
|
|
26
|
+
'use the `fields` parameter to request more fields, or `fields="*"` for everything.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
workspace_id: { type: 'string', description: 'Workspace ID (optional if URL provided or default set)' },
|
|
31
|
+
story_id: { type: 'string', description: 'Story ID' },
|
|
32
|
+
url: { type: 'string', description: 'TAPD story URL (alternative to workspace_id + story_id)' },
|
|
33
|
+
fields: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Comma-separated field list to return, e.g. "id,name,status,owner,priority,description,iteration_id,created". ' +
|
|
36
|
+
'Default: "id,name,status". Use "*" to return all TAPD fields (~260, response will be very large).',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'tapd_list_stories',
|
|
43
|
+
description: 'List stories in a workspace with optional filters. ' +
|
|
44
|
+
'By default each story only contains id/name/status; pass `fields` to request more.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
49
|
+
iteration_id: { type: 'string', description: 'Filter by iteration ID' },
|
|
50
|
+
status: { type: 'string', description: 'Filter by status (planning, developing, testing, resolved, closed)' },
|
|
51
|
+
owner: { type: 'string', description: 'Filter by owner name' },
|
|
52
|
+
creator: { type: 'string', description: 'Filter by creator name' },
|
|
53
|
+
limit: { type: 'number', description: 'Max results (default 30)' },
|
|
54
|
+
page: { type: 'number', description: 'Page number (default 1)' },
|
|
55
|
+
fields: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Comma-separated fields per item. Default: "id,name,status". Use "*" for all fields.',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['workspace_id'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'tapd_create_story',
|
|
65
|
+
description: 'Create a new story/requirement',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
70
|
+
name: { type: 'string', description: 'Story title' },
|
|
71
|
+
description: { type: 'string', description: 'Story description (supports HTML)' },
|
|
72
|
+
owner: { type: 'string', description: 'Owner name(s), semicolon separated' },
|
|
73
|
+
priority: { type: 'string', description: 'Priority (1-4, 1=highest)' },
|
|
74
|
+
iteration_id: { type: 'string', description: 'Iteration ID' },
|
|
75
|
+
parent_id: { type: 'string', description: 'Parent story ID' },
|
|
76
|
+
begin: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
|
77
|
+
due: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
|
|
78
|
+
category_id: { type: 'string', description: 'Category ID' },
|
|
79
|
+
release_id: { type: 'string', description: 'Release ID' },
|
|
80
|
+
},
|
|
81
|
+
required: ['workspace_id', 'name'],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'tapd_update_story',
|
|
86
|
+
description: 'Update an existing story/requirement',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
91
|
+
story_id: { type: 'string', description: 'Story ID to update' },
|
|
92
|
+
name: { type: 'string', description: 'New title' },
|
|
93
|
+
description: { type: 'string', description: 'New description' },
|
|
94
|
+
owner: { type: 'string', description: 'New owner(s)' },
|
|
95
|
+
status: { type: 'string', description: 'New status' },
|
|
96
|
+
priority: { type: 'string', description: 'New priority' },
|
|
97
|
+
iteration_id: { type: 'string', description: 'New iteration ID' },
|
|
98
|
+
begin: { type: 'string', description: 'New start date' },
|
|
99
|
+
due: { type: 'string', description: 'New due date' },
|
|
100
|
+
},
|
|
101
|
+
required: ['workspace_id', 'story_id'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
// ==================== Task Tools ====================
|
|
105
|
+
{
|
|
106
|
+
name: 'tapd_get_task',
|
|
107
|
+
description: 'Get details of a specific task',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
112
|
+
task_id: { type: 'string', description: 'Task ID' },
|
|
113
|
+
url: { type: 'string', description: 'TAPD task URL' },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'tapd_list_tasks',
|
|
119
|
+
description: 'List tasks, optionally filtered by story',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
124
|
+
story_id: { type: 'string', description: 'Filter by parent story ID' },
|
|
125
|
+
iteration_id: { type: 'string', description: 'Filter by iteration ID' },
|
|
126
|
+
status: { type: 'string', description: 'Filter by status (open, progressing, done)' },
|
|
127
|
+
owner: { type: 'string', description: 'Filter by owner' },
|
|
128
|
+
limit: { type: 'number', description: 'Max results' },
|
|
129
|
+
page: { type: 'number', description: 'Page number' },
|
|
130
|
+
},
|
|
131
|
+
required: ['workspace_id'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'tapd_create_task',
|
|
136
|
+
description: 'Create a new task under a story',
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
141
|
+
name: { type: 'string', description: 'Task name' },
|
|
142
|
+
story_id: { type: 'string', description: 'Parent story ID' },
|
|
143
|
+
description: { type: 'string', description: 'Task description' },
|
|
144
|
+
owner: { type: 'string', description: 'Task owner' },
|
|
145
|
+
priority: { type: 'string', description: 'Priority' },
|
|
146
|
+
iteration_id: { type: 'string', description: 'Iteration ID' },
|
|
147
|
+
begin: { type: 'string', description: 'Start date' },
|
|
148
|
+
due: { type: 'string', description: 'Due date' },
|
|
149
|
+
effort: { type: 'string', description: 'Estimated effort (hours)' },
|
|
150
|
+
},
|
|
151
|
+
required: ['workspace_id', 'name'],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'tapd_update_task',
|
|
156
|
+
description: 'Update a TAPD task. Supports status changes, work-hour tracking ' +
|
|
157
|
+
'(effort=estimated hours, effort_completed=spent hours, remain=remaining hours, ' +
|
|
158
|
+
'exceed=overrun hours, progress=completion %), reassignment, and date changes.',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
163
|
+
task_id: { type: 'string', description: 'Task ID to update (required)' },
|
|
164
|
+
name: { type: 'string', description: 'New task name' },
|
|
165
|
+
description: { type: 'string', description: 'New description' },
|
|
166
|
+
owner: { type: 'string', description: 'Reassign to a user (or semicolon-separated list)' },
|
|
167
|
+
status: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
description: 'Task status: open (未开始) | progressing (进行中) | done (已完成)',
|
|
170
|
+
},
|
|
171
|
+
priority: { type: 'string', description: 'Priority 1-4 (1=highest)' },
|
|
172
|
+
begin: { type: 'string', description: 'Start date YYYY-MM-DD' },
|
|
173
|
+
due: { type: 'string', description: 'Due date YYYY-MM-DD' },
|
|
174
|
+
effort: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
description: 'Estimated total work hours, e.g. "8" or "8.5"',
|
|
177
|
+
},
|
|
178
|
+
effort_completed: {
|
|
179
|
+
type: 'string',
|
|
180
|
+
description: 'Hours already spent, e.g. "3.5"',
|
|
181
|
+
},
|
|
182
|
+
remain: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
description: 'Hours remaining, e.g. "4.5". TAPD may auto-derive from effort - effort_completed; pass explicitly to override.',
|
|
185
|
+
},
|
|
186
|
+
exceed: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
description: 'Hours exceeded beyond the original estimate, e.g. "1"',
|
|
189
|
+
},
|
|
190
|
+
progress: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: 'Completion percentage 0-100, e.g. "50"',
|
|
193
|
+
},
|
|
194
|
+
iteration_id: { type: 'string', description: 'Move to a different iteration' },
|
|
195
|
+
},
|
|
196
|
+
required: ['workspace_id', 'task_id'],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
// ==================== Bug Tools ====================
|
|
200
|
+
{
|
|
201
|
+
name: 'tapd_get_bug',
|
|
202
|
+
description: 'Get details of a specific bug',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
207
|
+
bug_id: { type: 'string', description: 'Bug ID' },
|
|
208
|
+
url: { type: 'string', description: 'TAPD bug URL' },
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'tapd_list_bugs',
|
|
214
|
+
description: 'List bugs with optional filters',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
219
|
+
iteration_id: { type: 'string', description: 'Filter by iteration' },
|
|
220
|
+
status: { type: 'string', description: 'Filter by status (new, in_progress, resolved, verified, closed, rejected, reopened)' },
|
|
221
|
+
severity: { type: 'string', description: 'Filter by severity' },
|
|
222
|
+
current_owner: { type: 'string', description: 'Filter by current owner' },
|
|
223
|
+
limit: { type: 'number', description: 'Max results' },
|
|
224
|
+
page: { type: 'number', description: 'Page number' },
|
|
225
|
+
},
|
|
226
|
+
required: ['workspace_id'],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'tapd_create_bug',
|
|
231
|
+
description: 'Create a new bug report',
|
|
232
|
+
inputSchema: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
236
|
+
title: { type: 'string', description: 'Bug title' },
|
|
237
|
+
description: { type: 'string', description: 'Bug description' },
|
|
238
|
+
current_owner: { type: 'string', description: 'Assignee' },
|
|
239
|
+
severity: { type: 'string', description: 'Severity (fatal, serious, normal, prompt, advice)' },
|
|
240
|
+
priority: { type: 'string', description: 'Priority' },
|
|
241
|
+
iteration_id: { type: 'string', description: 'Iteration ID' },
|
|
242
|
+
module: { type: 'string', description: 'Module name' },
|
|
243
|
+
version_test: { type: 'string', description: 'Test version' },
|
|
244
|
+
version_report: { type: 'string', description: 'Report version' },
|
|
245
|
+
bugtype: { type: 'string', description: 'Bug type' },
|
|
246
|
+
},
|
|
247
|
+
required: ['workspace_id', 'title'],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'tapd_update_bug',
|
|
252
|
+
description: 'Update a bug',
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: 'object',
|
|
255
|
+
properties: {
|
|
256
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
257
|
+
bug_id: { type: 'string', description: 'Bug ID' },
|
|
258
|
+
title: { type: 'string', description: 'New title' },
|
|
259
|
+
description: { type: 'string', description: 'New description' },
|
|
260
|
+
current_owner: { type: 'string', description: 'New owner' },
|
|
261
|
+
status: { type: 'string', description: 'New status' },
|
|
262
|
+
severity: { type: 'string', description: 'New severity' },
|
|
263
|
+
priority: { type: 'string', description: 'New priority' },
|
|
264
|
+
},
|
|
265
|
+
required: ['workspace_id', 'bug_id'],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
// ==================== Iteration Tools ====================
|
|
269
|
+
{
|
|
270
|
+
name: 'tapd_get_iteration',
|
|
271
|
+
description: 'Get details of a specific iteration',
|
|
272
|
+
inputSchema: {
|
|
273
|
+
type: 'object',
|
|
274
|
+
properties: {
|
|
275
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
276
|
+
iteration_id: { type: 'string', description: 'Iteration ID' },
|
|
277
|
+
},
|
|
278
|
+
required: ['workspace_id', 'iteration_id'],
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: 'tapd_list_iterations',
|
|
283
|
+
description: 'List iterations in a workspace',
|
|
284
|
+
inputSchema: {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
288
|
+
status: { type: 'string', description: 'Filter by status' },
|
|
289
|
+
limit: { type: 'number', description: 'Max results' },
|
|
290
|
+
page: { type: 'number', description: 'Page number' },
|
|
291
|
+
},
|
|
292
|
+
required: ['workspace_id'],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: 'tapd_create_iteration',
|
|
297
|
+
description: 'Create a new iteration/sprint',
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: 'object',
|
|
300
|
+
properties: {
|
|
301
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
302
|
+
name: { type: 'string', description: 'Iteration name' },
|
|
303
|
+
description: { type: 'string', description: 'Description' },
|
|
304
|
+
startdate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
|
305
|
+
enddate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
|
306
|
+
},
|
|
307
|
+
required: ['workspace_id', 'name'],
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'tapd_update_iteration',
|
|
312
|
+
description: 'Update an iteration',
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: 'object',
|
|
315
|
+
properties: {
|
|
316
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
317
|
+
iteration_id: { type: 'string', description: 'Iteration ID' },
|
|
318
|
+
name: { type: 'string', description: 'New name' },
|
|
319
|
+
description: { type: 'string', description: 'New description' },
|
|
320
|
+
startdate: { type: 'string', description: 'New start date' },
|
|
321
|
+
enddate: { type: 'string', description: 'New end date' },
|
|
322
|
+
status: { type: 'string', description: 'New status' },
|
|
323
|
+
},
|
|
324
|
+
required: ['workspace_id', 'iteration_id'],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
// ==================== Comment Tools ====================
|
|
328
|
+
{
|
|
329
|
+
name: 'tapd_list_comments',
|
|
330
|
+
description: 'List comments on a story, task, or bug',
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties: {
|
|
334
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
335
|
+
entry_type: { type: 'string', enum: ['stories', 'tasks', 'bugs'], description: 'Type of item' },
|
|
336
|
+
entry_id: { type: 'string', description: 'ID of the story/task/bug' },
|
|
337
|
+
limit: { type: 'number', description: 'Max results' },
|
|
338
|
+
page: { type: 'number', description: 'Page number' },
|
|
339
|
+
},
|
|
340
|
+
required: ['workspace_id', 'entry_type', 'entry_id'],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: 'tapd_add_comment',
|
|
345
|
+
description: 'Add a comment to a story, task, or bug',
|
|
346
|
+
inputSchema: {
|
|
347
|
+
type: 'object',
|
|
348
|
+
properties: {
|
|
349
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
350
|
+
entry_type: { type: 'string', enum: ['stories', 'tasks', 'bugs'], description: 'Type of item' },
|
|
351
|
+
entry_id: { type: 'string', description: 'ID of the story/task/bug' },
|
|
352
|
+
description: { type: 'string', description: 'Comment content' },
|
|
353
|
+
},
|
|
354
|
+
required: ['workspace_id', 'entry_type', 'entry_id', 'description'],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
// ==================== Release Tools ====================
|
|
358
|
+
{
|
|
359
|
+
name: 'tapd_list_releases',
|
|
360
|
+
description: 'List releases in a workspace',
|
|
361
|
+
inputSchema: {
|
|
362
|
+
type: 'object',
|
|
363
|
+
properties: {
|
|
364
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
365
|
+
status: { type: 'string', description: 'Filter by status' },
|
|
366
|
+
limit: { type: 'number', description: 'Max results' },
|
|
367
|
+
page: { type: 'number', description: 'Page number' },
|
|
368
|
+
},
|
|
369
|
+
required: ['workspace_id'],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'tapd_create_release',
|
|
374
|
+
description: 'Create a new release',
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
379
|
+
name: { type: 'string', description: 'Release name' },
|
|
380
|
+
description: { type: 'string', description: 'Description' },
|
|
381
|
+
startdate: { type: 'string', description: 'Start date' },
|
|
382
|
+
enddate: { type: 'string', description: 'End date' },
|
|
383
|
+
},
|
|
384
|
+
required: ['workspace_id', 'name'],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'tapd_update_release',
|
|
389
|
+
description: 'Update a release',
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: 'object',
|
|
392
|
+
properties: {
|
|
393
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
394
|
+
release_id: { type: 'string', description: 'Release ID' },
|
|
395
|
+
name: { type: 'string', description: 'New name' },
|
|
396
|
+
description: { type: 'string', description: 'New description' },
|
|
397
|
+
startdate: { type: 'string', description: 'New start date' },
|
|
398
|
+
enddate: { type: 'string', description: 'New end date' },
|
|
399
|
+
status: { type: 'string', description: 'New status' },
|
|
400
|
+
},
|
|
401
|
+
required: ['workspace_id', 'release_id'],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
// ==================== Code Relation Tools ====================
|
|
405
|
+
{
|
|
406
|
+
name: 'tapd_get_story_commits',
|
|
407
|
+
description: 'Get code commits associated with a story',
|
|
408
|
+
inputSchema: {
|
|
409
|
+
type: 'object',
|
|
410
|
+
properties: {
|
|
411
|
+
workspace_id: { type: 'string', description: 'Workspace ID' },
|
|
412
|
+
story_id: { type: 'string', description: 'Story ID' },
|
|
413
|
+
limit: { type: 'number', description: 'Max results' },
|
|
414
|
+
page: { type: 'number', description: 'Page number' },
|
|
415
|
+
},
|
|
416
|
+
required: ['workspace_id', 'story_id'],
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
// ==================== Utility Tools ====================
|
|
420
|
+
{
|
|
421
|
+
name: 'tapd_parse_url',
|
|
422
|
+
description: 'Parse a TAPD URL to extract workspace ID, resource type, and resource ID',
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
url: { type: 'string', description: 'TAPD URL to parse' },
|
|
427
|
+
},
|
|
428
|
+
required: ['url'],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: 'tapd_get_current_user',
|
|
433
|
+
description: 'Get the current TAPD user (the "me" identity behind the API token). ' +
|
|
434
|
+
'Reads from the TAPD_CURRENT_USER env var as the source of truth. ' +
|
|
435
|
+
'When workspace_id is provided (or TAPD_WORKSPACE_ID is set), enriches the result ' +
|
|
436
|
+
'with name/email/role looked up via /users/get_users_by_workspace_id.',
|
|
437
|
+
inputSchema: {
|
|
438
|
+
type: 'object',
|
|
439
|
+
properties: {
|
|
440
|
+
workspace_id: {
|
|
441
|
+
type: 'string',
|
|
442
|
+
description: 'Optional workspace ID to enrich with member details. Falls back to TAPD_WORKSPACE_ID.',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
];
|
|
448
|
+
function getWorkspaceId(args) {
|
|
449
|
+
const wsId = args.workspace_id;
|
|
450
|
+
if (wsId)
|
|
451
|
+
return wsId;
|
|
452
|
+
if (DEFAULT_WORKSPACE_ID)
|
|
453
|
+
return DEFAULT_WORKSPACE_ID;
|
|
454
|
+
throw new Error('workspace_id is required (or set TAPD_WORKSPACE_ID env var)');
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Resolve the `fields` parameter for read tools.
|
|
458
|
+
* - undefined / empty => default minimal set 'id,name,status' (or caller-provided default)
|
|
459
|
+
* - '*' / 'all' => undefined (let TAPD return everything)
|
|
460
|
+
* - otherwise => trimmed comma string
|
|
461
|
+
*
|
|
462
|
+
* Returns both the api-bound string and a Set of requested field names so callers
|
|
463
|
+
* can decide whether to attach derived helpers (e.g. status_name).
|
|
464
|
+
*/
|
|
465
|
+
function resolveFields(args, defaultFields = 'id,name,status') {
|
|
466
|
+
const raw = args.fields?.trim();
|
|
467
|
+
if (raw === '*' || raw === 'all') {
|
|
468
|
+
return { fields: undefined, requested: new Set(), isAll: true };
|
|
469
|
+
}
|
|
470
|
+
const fields = raw && raw.length > 0 ? raw : defaultFields;
|
|
471
|
+
const requested = new Set(fields.split(',').map(f => f.trim()).filter(Boolean));
|
|
472
|
+
return { fields, requested, isAll: false };
|
|
473
|
+
}
|
|
474
|
+
async function handleToolCall(name, args) {
|
|
475
|
+
switch (name) {
|
|
476
|
+
// Story handlers
|
|
477
|
+
case 'tapd_get_story': {
|
|
478
|
+
const { fields, requested, isAll } = resolveFields(args);
|
|
479
|
+
const attachStatusName = (s) => s && (isAll || requested.has('status'))
|
|
480
|
+
? { ...s, status_name: client.translateStoryStatus(s.status || '') }
|
|
481
|
+
: s;
|
|
482
|
+
if (args.url) {
|
|
483
|
+
const parsed = client.parseUrl(args.url);
|
|
484
|
+
if (!parsed)
|
|
485
|
+
throw new Error('Invalid TAPD URL');
|
|
486
|
+
const story = await client.getStory(parsed.workspaceId, parsed.resourceId, fields);
|
|
487
|
+
return attachStatusName(story);
|
|
488
|
+
}
|
|
489
|
+
const wsId = getWorkspaceId(args);
|
|
490
|
+
const story = await client.getStory(wsId, args.story_id, fields);
|
|
491
|
+
return attachStatusName(story);
|
|
492
|
+
}
|
|
493
|
+
case 'tapd_list_stories': {
|
|
494
|
+
const { fields, requested, isAll } = resolveFields(args);
|
|
495
|
+
const stories = await client.listStories(getWorkspaceId(args), {
|
|
496
|
+
iterationId: args.iteration_id,
|
|
497
|
+
status: args.status,
|
|
498
|
+
owner: args.owner,
|
|
499
|
+
creator: args.creator,
|
|
500
|
+
limit: args.limit,
|
|
501
|
+
page: args.page,
|
|
502
|
+
fields,
|
|
503
|
+
});
|
|
504
|
+
const showStatus = isAll || requested.has('status');
|
|
505
|
+
return stories.map(s => showStatus ? { ...s, status_name: client.translateStoryStatus(s.status || '') } : s);
|
|
506
|
+
}
|
|
507
|
+
case 'tapd_create_story': {
|
|
508
|
+
return await client.createStory(getWorkspaceId(args), {
|
|
509
|
+
name: args.name,
|
|
510
|
+
description: args.description,
|
|
511
|
+
owner: args.owner,
|
|
512
|
+
priority: args.priority,
|
|
513
|
+
iterationId: args.iteration_id,
|
|
514
|
+
parentId: args.parent_id,
|
|
515
|
+
begin: args.begin,
|
|
516
|
+
due: args.due,
|
|
517
|
+
categoryId: args.category_id,
|
|
518
|
+
releaseId: args.release_id,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
case 'tapd_update_story': {
|
|
522
|
+
return await client.updateStory(getWorkspaceId(args), args.story_id, {
|
|
523
|
+
name: args.name,
|
|
524
|
+
description: args.description,
|
|
525
|
+
owner: args.owner,
|
|
526
|
+
status: args.status,
|
|
527
|
+
priority: args.priority,
|
|
528
|
+
iterationId: args.iteration_id,
|
|
529
|
+
begin: args.begin,
|
|
530
|
+
due: args.due,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
// Task handlers
|
|
534
|
+
case 'tapd_get_task': {
|
|
535
|
+
if (args.url) {
|
|
536
|
+
const parsed = client.parseUrl(args.url);
|
|
537
|
+
if (!parsed)
|
|
538
|
+
throw new Error('Invalid TAPD URL');
|
|
539
|
+
const task = await client.getTask(parsed.workspaceId, parsed.resourceId);
|
|
540
|
+
return task ? { ...task, status_name: client.translateTaskStatus(task.status || '') } : null;
|
|
541
|
+
}
|
|
542
|
+
const wsId = getWorkspaceId(args);
|
|
543
|
+
const task = await client.getTask(wsId, args.task_id);
|
|
544
|
+
return task ? { ...task, status_name: client.translateTaskStatus(task.status || '') } : null;
|
|
545
|
+
}
|
|
546
|
+
case 'tapd_list_tasks': {
|
|
547
|
+
const tasks = await client.listTasks(getWorkspaceId(args), {
|
|
548
|
+
storyId: args.story_id,
|
|
549
|
+
iterationId: args.iteration_id,
|
|
550
|
+
status: args.status,
|
|
551
|
+
owner: args.owner,
|
|
552
|
+
limit: args.limit,
|
|
553
|
+
page: args.page,
|
|
554
|
+
});
|
|
555
|
+
return tasks.map(t => ({ ...t, status_name: client.translateTaskStatus(t.status || '') }));
|
|
556
|
+
}
|
|
557
|
+
case 'tapd_create_task': {
|
|
558
|
+
return await client.createTask(getWorkspaceId(args), {
|
|
559
|
+
name: args.name,
|
|
560
|
+
storyId: args.story_id,
|
|
561
|
+
description: args.description,
|
|
562
|
+
owner: args.owner,
|
|
563
|
+
priority: args.priority,
|
|
564
|
+
iterationId: args.iteration_id,
|
|
565
|
+
begin: args.begin,
|
|
566
|
+
due: args.due,
|
|
567
|
+
effort: args.effort,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
case 'tapd_update_task': {
|
|
571
|
+
return await client.updateTask(getWorkspaceId(args), args.task_id, {
|
|
572
|
+
name: args.name,
|
|
573
|
+
description: args.description,
|
|
574
|
+
owner: args.owner,
|
|
575
|
+
status: args.status,
|
|
576
|
+
priority: args.priority,
|
|
577
|
+
begin: args.begin,
|
|
578
|
+
due: args.due,
|
|
579
|
+
effort: args.effort,
|
|
580
|
+
effortCompleted: args.effort_completed,
|
|
581
|
+
remain: args.remain,
|
|
582
|
+
exceed: args.exceed,
|
|
583
|
+
progress: args.progress,
|
|
584
|
+
iterationId: args.iteration_id,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
// Bug handlers
|
|
588
|
+
case 'tapd_get_bug': {
|
|
589
|
+
if (args.url) {
|
|
590
|
+
const parsed = client.parseUrl(args.url);
|
|
591
|
+
if (!parsed)
|
|
592
|
+
throw new Error('Invalid TAPD URL');
|
|
593
|
+
const bug = await client.getBug(parsed.workspaceId, parsed.resourceId);
|
|
594
|
+
return bug ? { ...bug, status_name: client.translateBugStatus(bug.status || '') } : null;
|
|
595
|
+
}
|
|
596
|
+
const wsId = getWorkspaceId(args);
|
|
597
|
+
const bug = await client.getBug(wsId, args.bug_id);
|
|
598
|
+
return bug ? { ...bug, status_name: client.translateBugStatus(bug.status || '') } : null;
|
|
599
|
+
}
|
|
600
|
+
case 'tapd_list_bugs': {
|
|
601
|
+
const bugs = await client.listBugs(getWorkspaceId(args), {
|
|
602
|
+
iterationId: args.iteration_id,
|
|
603
|
+
status: args.status,
|
|
604
|
+
severity: args.severity,
|
|
605
|
+
currentOwner: args.current_owner,
|
|
606
|
+
limit: args.limit,
|
|
607
|
+
page: args.page,
|
|
608
|
+
});
|
|
609
|
+
return bugs.map(b => ({ ...b, status_name: client.translateBugStatus(b.status || '') }));
|
|
610
|
+
}
|
|
611
|
+
case 'tapd_create_bug': {
|
|
612
|
+
return await client.createBug(getWorkspaceId(args), {
|
|
613
|
+
title: args.title,
|
|
614
|
+
description: args.description,
|
|
615
|
+
currentOwner: args.current_owner,
|
|
616
|
+
severity: args.severity,
|
|
617
|
+
priority: args.priority,
|
|
618
|
+
iterationId: args.iteration_id,
|
|
619
|
+
module: args.module,
|
|
620
|
+
versionTest: args.version_test,
|
|
621
|
+
versionReport: args.version_report,
|
|
622
|
+
bugtype: args.bugtype,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
case 'tapd_update_bug': {
|
|
626
|
+
return await client.updateBug(getWorkspaceId(args), args.bug_id, {
|
|
627
|
+
title: args.title,
|
|
628
|
+
description: args.description,
|
|
629
|
+
currentOwner: args.current_owner,
|
|
630
|
+
status: args.status,
|
|
631
|
+
severity: args.severity,
|
|
632
|
+
priority: args.priority,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
// Iteration handlers
|
|
636
|
+
case 'tapd_get_iteration': {
|
|
637
|
+
return await client.getIteration(getWorkspaceId(args), args.iteration_id);
|
|
638
|
+
}
|
|
639
|
+
case 'tapd_list_iterations': {
|
|
640
|
+
return await client.listIterations(getWorkspaceId(args), {
|
|
641
|
+
status: args.status,
|
|
642
|
+
limit: args.limit,
|
|
643
|
+
page: args.page,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
case 'tapd_create_iteration': {
|
|
647
|
+
return await client.createIteration(getWorkspaceId(args), {
|
|
648
|
+
name: args.name,
|
|
649
|
+
description: args.description,
|
|
650
|
+
startdate: args.startdate,
|
|
651
|
+
enddate: args.enddate,
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
case 'tapd_update_iteration': {
|
|
655
|
+
return await client.updateIteration(getWorkspaceId(args), args.iteration_id, {
|
|
656
|
+
name: args.name,
|
|
657
|
+
description: args.description,
|
|
658
|
+
startdate: args.startdate,
|
|
659
|
+
enddate: args.enddate,
|
|
660
|
+
status: args.status,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
// Comment handlers
|
|
664
|
+
case 'tapd_list_comments': {
|
|
665
|
+
return await client.listComments(getWorkspaceId(args), args.entry_type, args.entry_id, { limit: args.limit, page: args.page });
|
|
666
|
+
}
|
|
667
|
+
case 'tapd_add_comment': {
|
|
668
|
+
return await client.addComment(getWorkspaceId(args), args.entry_type, args.entry_id, args.description);
|
|
669
|
+
}
|
|
670
|
+
// Release handlers
|
|
671
|
+
case 'tapd_list_releases': {
|
|
672
|
+
return await client.listReleases(getWorkspaceId(args), {
|
|
673
|
+
status: args.status,
|
|
674
|
+
limit: args.limit,
|
|
675
|
+
page: args.page,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
case 'tapd_create_release': {
|
|
679
|
+
return await client.createRelease(getWorkspaceId(args), {
|
|
680
|
+
name: args.name,
|
|
681
|
+
description: args.description,
|
|
682
|
+
startdate: args.startdate,
|
|
683
|
+
enddate: args.enddate,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
case 'tapd_update_release': {
|
|
687
|
+
return await client.updateRelease(getWorkspaceId(args), args.release_id, {
|
|
688
|
+
name: args.name,
|
|
689
|
+
description: args.description,
|
|
690
|
+
startdate: args.startdate,
|
|
691
|
+
enddate: args.enddate,
|
|
692
|
+
status: args.status,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
// Code relation handlers
|
|
696
|
+
case 'tapd_get_story_commits': {
|
|
697
|
+
return await client.getStoryCodeCommits(getWorkspaceId(args), args.story_id, { limit: args.limit, page: args.page });
|
|
698
|
+
}
|
|
699
|
+
// Utility handlers
|
|
700
|
+
case 'tapd_parse_url': {
|
|
701
|
+
const parsed = client.parseUrl(args.url);
|
|
702
|
+
if (!parsed)
|
|
703
|
+
throw new Error('Invalid TAPD URL format');
|
|
704
|
+
return parsed;
|
|
705
|
+
}
|
|
706
|
+
case 'tapd_get_current_user': {
|
|
707
|
+
if (!CURRENT_USER) {
|
|
708
|
+
throw new Error('TAPD_CURRENT_USER env var is not set. ' +
|
|
709
|
+
'Set it to your TAPD user id (e.g. "xiaopeng_lei") in the MCP server config so I can identify "you".');
|
|
710
|
+
}
|
|
711
|
+
const base = { user: CURRENT_USER, source: 'env' };
|
|
712
|
+
const wsId = args.workspace_id || DEFAULT_WORKSPACE_ID;
|
|
713
|
+
if (wsId) {
|
|
714
|
+
try {
|
|
715
|
+
const members = await client.getWorkspaceUsers(wsId);
|
|
716
|
+
const match = members.find(m => m.user === CURRENT_USER || m.name === CURRENT_USER || m.nick === CURRENT_USER);
|
|
717
|
+
if (match) {
|
|
718
|
+
return { ...base, workspace_id: wsId, matched_in_workspace: true, ...match };
|
|
719
|
+
}
|
|
720
|
+
return { ...base, workspace_id: wsId, matched_in_workspace: false };
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
base.enrich_error = e instanceof Error ? e.message : String(e);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return base;
|
|
727
|
+
}
|
|
728
|
+
default:
|
|
729
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
async function main() {
|
|
733
|
+
const server = new Server({
|
|
734
|
+
name: 'tapd-mcp',
|
|
735
|
+
version: '1.0.0',
|
|
736
|
+
}, {
|
|
737
|
+
capabilities: {
|
|
738
|
+
tools: {},
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
742
|
+
tools,
|
|
743
|
+
}));
|
|
744
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
745
|
+
try {
|
|
746
|
+
const result = await handleToolCall(request.params.name, (request.params.arguments || {}));
|
|
747
|
+
return {
|
|
748
|
+
content: [
|
|
749
|
+
{
|
|
750
|
+
type: 'text',
|
|
751
|
+
text: JSON.stringify(result, null, 2),
|
|
752
|
+
},
|
|
753
|
+
],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
758
|
+
return {
|
|
759
|
+
content: [
|
|
760
|
+
{
|
|
761
|
+
type: 'text',
|
|
762
|
+
text: `Error: ${message}`,
|
|
763
|
+
},
|
|
764
|
+
],
|
|
765
|
+
isError: true,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
const transport = new StdioServerTransport();
|
|
770
|
+
await server.connect(transport);
|
|
771
|
+
console.error('TAPD MCP Server running on stdio');
|
|
772
|
+
}
|
|
773
|
+
main().catch((error) => {
|
|
774
|
+
console.error('Fatal error:', error);
|
|
775
|
+
process.exit(1);
|
|
776
|
+
});
|
|
777
|
+
//# sourceMappingURL=index.js.map
|