targetprocess-mcp-server 2.6.0 → 2.6.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.
@@ -0,0 +1,19 @@
1
+ export async function handleDeleteCard(tp, params) {
2
+ const result = await tp.deleteCard(params);
3
+ if (!result.ok) {
4
+ return {
5
+ content: [{
6
+ type: 'text',
7
+ text: `Failed to delete ${params.type} id: ${params.id}\n` +
8
+ `HTTP status: ${result.status}\n` +
9
+ `Response body: ${result.body}`
10
+ }],
11
+ };
12
+ }
13
+ return {
14
+ content: [{
15
+ type: 'text',
16
+ text: JSON.stringify({ deleted: true, id: Number(params.id), type: params.type, card: result.data })
17
+ }],
18
+ };
19
+ }
@@ -0,0 +1,30 @@
1
+ export async function handleGetTeamIterations(tp, params) {
2
+ const response = await tp.getTeamIterations(params);
3
+ if (!response) {
4
+ return {
5
+ content: [{
6
+ type: 'text',
7
+ text: `Failed to get team iterations, JSON: ${JSON.stringify(response, null, 2)}`
8
+ }],
9
+ };
10
+ }
11
+ const items = response.Items || [];
12
+ if (items.length === 0) {
13
+ return {
14
+ content: [{ type: 'text', text: 'No team iterations found' }],
15
+ };
16
+ }
17
+ return {
18
+ content: [{
19
+ type: 'text',
20
+ text: JSON.stringify(items.map((i) => ({
21
+ id: i.Id,
22
+ name: i.Name,
23
+ startDate: i.StartDate,
24
+ endDate: i.EndDate,
25
+ teamId: i.Team?.Id,
26
+ teamName: i.Team?.Name,
27
+ }))),
28
+ }],
29
+ };
30
+ }
package/build/index.js CHANGED
@@ -20,6 +20,7 @@ import { handleGetReleaseOpenBugs } from "./handlers/get_release_open_bugs.js";
20
20
  import { handleGetReleaseOpenUserStories } from "./handlers/get_release_open_user_stories.js";
21
21
  import { handleGetUsers } from "./handlers/get_users.js";
22
22
  import { handleGetTeams, handleGetTeamsAndTeamAssignments } from "./handlers/get_teams.js";
23
+ import { handleGetTeamIterations } from "./handlers/get_team_iterations.js";
23
24
  import { handleAddComment } from "./handlers/add_comment.js";
24
25
  import { handleGetUserStoryComments } from "./handlers/get_user_story_comments.js";
25
26
  import { handleGetBugComments } from "./handlers/get_bug_comments.js";
@@ -44,6 +45,7 @@ import { handleUpdateUserStorySubState } from "./handlers/update_user_story_sub_
44
45
  import { handleGetCardRelations } from "./handlers/get_card_relations.js";
45
46
  import { handleCreateCardRelation } from "./handlers/create_card_relation.js";
46
47
  import { handleDeleteCardRelation } from "./handlers/delete_card_relation.js";
48
+ import { handleDeleteCard } from "./handlers/delete_card.js";
47
49
  import { handleGetTestPlanById } from "./handlers/get_test_plan_by_id.js";
48
50
  import { handleGetTestPlanTestCasesById } from "./handlers/get_test_plan_test_cases_by_id.js";
49
51
  import { handleGetTestPlanTestCasesWithStepsById } from "./handlers/get_test_plan_test_cases_with_steps_by_id.js";
@@ -409,7 +411,8 @@ server.registerTool('update_bug', {
409
411
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
410
412
  1) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
411
413
  2) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
412
- 3) IF the user specified a state by name (not ID), call "get_bug_workflows" to find the matching state and use its ID as entityStateId;`,
414
+ 3) IF the user specified a state by name (not ID), call "get_bug_workflows" to find the matching state and use its ID as entityStateId;
415
+ 4) IF the user specified a sprint/iteration by name, call "get_team_iterations" to find the matching iteration and use its ID as teamIterationId;`,
413
416
  inputSchema: {
414
417
  id: z.string()
415
418
  .min(5)
@@ -443,8 +446,14 @@ server.registerTool('update_bug', {
443
446
  entityStateId: z.string()
444
447
  .optional()
445
448
  .describe('Optional Entity State ID — if user gave a state name, resolve it via "get_bug_workflows" first; defaults to "Done"'),
449
+ tags: z.string()
450
+ .optional()
451
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
452
+ teamIterationId: z.string()
453
+ .optional()
454
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
446
455
  },
447
- }, async ({ id, title, bugContent, origin, projectId, teamId, entityStateId }) => handleUpdateBug(tp, { id, title, bugContent, origin, projectId, teamId, entityStateId }));
456
+ }, async ({ id, title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }) => handleUpdateBug(tp, { id, title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }));
448
457
  server.registerTool('update_user_story_state', {
449
458
  title: 'Update a user story card sub state',
450
459
  description: `Update a user story card sub state with data provided from user input.
@@ -474,7 +483,8 @@ server.registerTool('update_user_story', {
474
483
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
475
484
  1) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
476
485
  2) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
477
- 3) IF the user specified a state by name (not ID), call "get_user_story_workflows" to find the matching state and use its ID as entityStateId;`,
486
+ 3) IF the user specified a state by name (not ID), call "get_user_story_workflows" to find the matching state and use its ID as entityStateId;
487
+ 4) IF the user specified a sprint/iteration by name, call "get_team_iterations" to find the matching iteration and use its ID as teamIterationId;`,
478
488
  inputSchema: {
479
489
  id: z.string()
480
490
  .min(5)
@@ -498,9 +508,15 @@ server.registerTool('update_user_story', {
498
508
  featureId: z.string()
499
509
  .optional()
500
510
  .describe('Optional Feature ID — moves this user story to the specified feature'),
511
+ tags: z.string()
512
+ .optional()
513
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
514
+ teamIterationId: z.string()
515
+ .optional()
516
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
501
517
  },
502
- }, async ({ id, title, description, projectId, teamId, entityStateId, featureId }) => {
503
- const response = await tp.updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId });
518
+ }, async ({ id, title, description, projectId, teamId, entityStateId, featureId, tags, teamIterationId }) => {
519
+ const response = await tp.updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId, tags, teamIterationId });
504
520
  if (!response) {
505
521
  return {
506
522
  content: [{
@@ -523,7 +539,8 @@ server.registerTool('create_bug', {
523
539
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
524
540
  1) format the new bug inside html <div> tags with Environment(describes where bug was found, dev, feature, review or uat Environment), Issue Description, Steps to Reproduce, Expected Behavior, Actual Behavior and Attachments sections (note: section titles should be wrapped in <h3> tags, e.g. <h3>Issue Description</h3>, step to reproduce should be wrapped in <ol>);
525
541
  2) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
526
- 3) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;`,
542
+ 3) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
543
+ 4) IF the user specified a sprint/iteration by name, call "get_team_iterations" to find the matching iteration and use its ID as teamIterationId;`,
527
544
  inputSchema: {
528
545
  title: z.string()
529
546
  .describe('Bug card title that summarizes the problem in concise, descriptive, and actionable manner, enabling a developer to understand the issue without opening the report'),
@@ -552,11 +569,18 @@ server.registerTool('create_bug', {
552
569
  entityStateId: z.string()
553
570
  .optional()
554
571
  .describe('Optional Entity State ID — if user gave a state name, resolve it via "get_bug_workflows" first; defaults to "Done"'),
572
+ tags: z.string()
573
+ .optional()
574
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
575
+ teamIterationId: z.string()
576
+ .optional()
577
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
555
578
  },
556
- }, async ({ title, bugContent, origin, projectId, teamId, entityStateId }) => handleCreateBug(tp, { title, bugContent, origin, projectId, teamId, entityStateId }));
579
+ }, async ({ title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }) => handleCreateBug(tp, { title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }));
557
580
  server.registerTool('create_user_story', {
558
581
  title: 'Create a new user story',
559
- description: `Create a new user story in Targetprocess.`,
582
+ description: `Create a new user story in Targetprocess.
583
+ CRITICAL WORKFLOW: Before calling this tool, IF the user specified a sprint/iteration by name, call "get_team_iterations" to find the matching iteration and use its ID as teamIterationId.`,
560
584
  inputSchema: {
561
585
  title: z.string()
562
586
  .describe('User story title'),
@@ -579,8 +603,14 @@ server.registerTool('create_user_story', {
579
603
  teamId: z.string()
580
604
  .optional()
581
605
  .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
606
+ tags: z.string()
607
+ .optional()
608
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
609
+ teamIterationId: z.string()
610
+ .optional()
611
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
582
612
  },
583
- }, async ({ title, description, featureId, releaseId, projectId, teamId }) => handleCreateUserStory(tp, { title, description, featureId, releaseId, projectId, teamId }));
613
+ }, async ({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }) => handleCreateUserStory(tp, { title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }));
584
614
  server.registerTool('create_formatted_user_story', {
585
615
  title: 'Create a formatted user story',
586
616
  description: `Create a new user story in Targetprocess with a structured, template-driven description.
@@ -589,7 +619,8 @@ server.registerTool('create_formatted_user_story', {
589
619
  1) IF the user specified a feature by name (not ID), call "get_feature_user_stories" or "search_tp_cards" to resolve the feature ID;
590
620
  2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
591
621
  3) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
592
- 4) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;`,
622
+ 4) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
623
+ 5) IF the user specified a sprint/iteration by name, call "get_team_iterations" to find the matching iteration and use its ID as teamIterationId;`,
593
624
  inputSchema: {
594
625
  title: z.string()
595
626
  .describe('User story title'),
@@ -657,8 +688,14 @@ server.registerTool('create_formatted_user_story', {
657
688
  teamId: z.string()
658
689
  .optional()
659
690
  .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
691
+ tags: z.string()
692
+ .optional()
693
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
694
+ teamIterationId: z.string()
695
+ .optional()
696
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
660
697
  },
661
- }, async ({ title, header, definitions, acceptanceCriteria, scenarios, examplesTable, edgeCases, references, notes, featureId, releaseId, projectId, teamId }) => {
698
+ }, async ({ title, header, definitions, acceptanceCriteria, scenarios, examplesTable, edgeCases, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId }) => {
662
699
  const gherkinBlock = (items) => items.map((s, indx) => `<div><strong>Scenario ${indx + 1} - ${s.name}:</strong></div><div>${s.steps.map(step => `<div>\t${step}</div>`).join('\n')}</div>`).join('<br>');
663
700
  const parts = ['<div>'];
664
701
  parts.push('<h3>Header</h3>');
@@ -699,7 +736,7 @@ server.registerTool('create_formatted_user_story', {
699
736
  }
700
737
  parts.push('</div>');
701
738
  const description = parts.join('\n');
702
- const userStoryResponse = await tp.createUserStory({ title, description, featureId, releaseId, projectId, teamId });
739
+ const userStoryResponse = await tp.createUserStory({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId });
703
740
  if (!userStoryResponse) {
704
741
  return {
705
742
  content: [{
@@ -961,6 +998,16 @@ server.registerTool('get_teams', {
961
998
  title: 'Get teams',
962
999
  description: 'Get all Targetprocess teams',
963
1000
  }, async () => handleGetTeams(tp));
1001
+ server.registerTool('get_team_iterations', {
1002
+ title: 'Get team iterations',
1003
+ description: `Get Targetprocess team iterations (sprints), optionally filtered by team. Use this to resolve a sprint/iteration name to an ID before calling create_user_story, create_bug, update_user_story, or update_bug with teamIterationId.
1004
+ CRITICAL WORKFLOW: IF the user specified a team by name (not ID), call "get_teams" first to find the matching team and use its ID as teamId.`,
1005
+ inputSchema: {
1006
+ teamId: z.string()
1007
+ .optional()
1008
+ .describe('Optional Team ID to filter iterations by — resolve it via "get_teams" first'),
1009
+ },
1010
+ }, async ({ teamId }) => handleGetTeamIterations(tp, { teamId }));
964
1011
  server.registerTool('get_logged_in_user', {
965
1012
  title: 'Get logged in user',
966
1013
  description: 'Get logged in user',
@@ -1235,6 +1282,17 @@ server.registerTool('delete_card_relation', {
1235
1282
  .describe('The relation ID to delete (the "relationId" field from "get_card_relations", e.g. 20748)'),
1236
1283
  },
1237
1284
  }, async ({ relationId }) => handleDeleteCardRelation(tp, relationId));
1285
+ server.registerTool('delete_card', {
1286
+ title: 'Delete a card (Bug, User Story, Feature, or Epic)',
1287
+ description: `Delete (remove) a Targetprocess card by its ID. Works on Bugs, User Stories, Features, and Epics.
1288
+ IF the type is uncertain, resolve it first via "search_tp_cards" or by fetching the card.`,
1289
+ inputSchema: {
1290
+ id: z.string()
1291
+ .describe('The card ID to delete (e.g. 148980)'),
1292
+ type: z.enum(["Bug", "UserStory", "Feature", "Epic"])
1293
+ .describe('The entity type of the card being deleted'),
1294
+ },
1295
+ }, async ({ id, type }) => handleDeleteCard(tp, { id, type }));
1238
1296
  server.registerTool('get_in_progress_tasks_and_bugs', {
1239
1297
  title: 'Get in-progress tasks and bugs for a user',
1240
1298
  description: 'Get all Tasks and Bugs currently in "In Progress" state assigned to a given user ID',
package/build/tp.js CHANGED
@@ -24,6 +24,11 @@ export class TpClient {
24
24
  }
25
25
  return _url + "/?" + _urlParams.join("&");
26
26
  }
27
+ // Strips the access_token value out of a URL before it's logged, so the
28
+ // live TP credential never ends up in stderr/log files.
29
+ redact(url) {
30
+ return url.replace(this.token, "***");
31
+ }
27
32
  // @ts-ignore
28
33
  async getAll(params) {
29
34
  const allItems = [];
@@ -58,14 +63,14 @@ export class TpClient {
58
63
  }
59
64
  catch (error) {
60
65
  console.error("Error making TP request:", error);
61
- console.error("Request URL:", _url);
66
+ console.error("Request URL:", this.redact(_url));
62
67
  return null;
63
68
  }
64
69
  }
65
70
  async post(params, data) {
66
71
  params.param["access_token"] = this.token;
67
72
  let _url = this.params(params);
68
- console.error(JSON.stringify({ "TP_POST_URL": _url }));
73
+ console.error(JSON.stringify({ "TP_POST_URL": this.redact(_url) }));
69
74
  console.error(JSON.stringify({ "TP_POST_BODY": data }));
70
75
  try {
71
76
  const response = await fetch(_url, {
@@ -88,7 +93,7 @@ export class TpClient {
88
93
  async postRaw(params, data) {
89
94
  params.param["access_token"] = this.token;
90
95
  let _url = this.params(params);
91
- console.error(JSON.stringify({ "TP_POST_URL": _url }));
96
+ console.error(JSON.stringify({ "TP_POST_URL": this.redact(_url) }));
92
97
  console.error(JSON.stringify({ "TP_POST_BODY": data }));
93
98
  try {
94
99
  const response = await fetch(_url, {
@@ -113,7 +118,7 @@ export class TpClient {
113
118
  async del(params) {
114
119
  params.param["access_token"] = this.token;
115
120
  let _url = this.params(params);
116
- console.error(JSON.stringify({ "TP_DELETE_URL": _url }));
121
+ console.error(JSON.stringify({ "TP_DELETE_URL": this.redact(_url) }));
117
122
  try {
118
123
  const response = await fetch(_url, {
119
124
  method: "DELETE",
@@ -227,7 +232,7 @@ export class TpClient {
227
232
  param: { "format": "json" },
228
233
  }, userStory);
229
234
  }
230
- async updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId }) {
235
+ async updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId, tags, teamIterationId }) {
231
236
  const userStory = { "Id": id };
232
237
  if (title)
233
238
  userStory["Name"] = title;
@@ -241,12 +246,16 @@ export class TpClient {
241
246
  userStory["EntityState"] = { "Id": entityStateId };
242
247
  if (featureId)
243
248
  userStory["Feature"] = { "Id": featureId };
249
+ if (tags)
250
+ userStory["Tags"] = tags;
251
+ if (teamIterationId)
252
+ userStory["TeamIteration"] = { "Id": teamIterationId };
244
253
  return this.post({
245
254
  pathParam: ["UserStories"],
246
255
  param: { "format": "json" },
247
256
  }, userStory);
248
257
  }
249
- async updateBug({ id, title, bugContent, origin, projectId, teamId, entityStateId }) {
258
+ async updateBug({ id, title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }) {
250
259
  const bug = { "Id": id };
251
260
  if (title)
252
261
  bug["Name"] = title;
@@ -268,12 +277,16 @@ export class TpClient {
268
277
  }];
269
278
  if (entityStateId)
270
279
  bug["entityState"] = { "id": entityStateId };
280
+ if (tags)
281
+ bug["Tags"] = tags;
282
+ if (teamIterationId)
283
+ bug["TeamIteration"] = { "Id": teamIterationId };
271
284
  return this.post({
272
285
  pathParam: ["bugs"],
273
286
  param: { "format": "json" },
274
287
  }, bug);
275
288
  }
276
- async createBugOnly({ title, bugContent, origin = "Manual QA", projectId, teamId, entityStateId }) {
289
+ async createBugOnly({ title, bugContent, origin = "Manual QA", projectId, teamId, entityStateId, tags, teamIterationId }) {
277
290
  const bug = {
278
291
  "Name": title,
279
292
  "Project": {
@@ -293,12 +306,16 @@ export class TpClient {
293
306
  };
294
307
  if (entityStateId)
295
308
  bug["EntityState"] = { "Id": entityStateId };
309
+ if (tags)
310
+ bug["Tags"] = tags;
311
+ if (teamIterationId)
312
+ bug["TeamIteration"] = { "Id": teamIterationId };
296
313
  return this.post({
297
314
  pathParam: ["bugs"],
298
315
  param: { "format": "json" },
299
316
  }, bug);
300
317
  }
301
- async createUserStory({ title, description, featureId, releaseId, projectId, teamId }) {
318
+ async createUserStory({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }) {
302
319
  const userStory = {
303
320
  "Name": title,
304
321
  "Project": { "Id": projectId || config.tp.projectId },
@@ -310,11 +327,25 @@ export class TpClient {
310
327
  userStory["Feature"] = { "Id": featureId };
311
328
  if (releaseId)
312
329
  userStory["Release"] = { "Id": releaseId };
330
+ if (tags)
331
+ userStory["Tags"] = tags;
332
+ if (teamIterationId)
333
+ userStory["TeamIteration"] = { "Id": teamIterationId };
313
334
  return this.post({
314
335
  pathParam: ["UserStories"],
315
336
  param: { "format": "json" },
316
337
  }, userStory);
317
338
  }
339
+ async getTeamIterations({ teamId } = {}) {
340
+ return this.get({
341
+ pathParam: ["TeamIterations"],
342
+ param: {
343
+ "format": "json",
344
+ ...(teamId ? { "where": `Team.Id eq ${teamId}` } : {}),
345
+ "include": "[Id,Name,StartDate,EndDate,Team[Id,Name]]",
346
+ },
347
+ });
348
+ }
318
349
  async getEpic(epicId) {
319
350
  return this.get({
320
351
  pathParam: ["Epics", epicId],
@@ -798,6 +829,18 @@ export class TpClient {
798
829
  param: { "format": "json" },
799
830
  });
800
831
  }
832
+ async deleteCard({ id, type }) {
833
+ const pathSegment = {
834
+ "Bug": "bugs",
835
+ "UserStory": "userStories",
836
+ "Feature": "features",
837
+ "Epic": "Epics",
838
+ };
839
+ return this.del({
840
+ pathParam: [pathSegment[type], id],
841
+ param: { "format": "json" },
842
+ });
843
+ }
801
844
  async getProjects() {
802
845
  return this.get({
803
846
  pathParam: ["Projects"],
@@ -1106,7 +1149,7 @@ export class TpClient {
1106
1149
  formData.append("generalId", generalId);
1107
1150
  formData.append("file", blob, fileName);
1108
1151
  const url = `${this.baseUrl}/UploadFile.ashx?access_token=${this.token}`;
1109
- console.error(JSON.stringify({ "UPLOAD_URL": url.replace(this.token, "***") }, null, 2));
1152
+ console.error(JSON.stringify({ "UPLOAD_URL": this.redact(url) }, null, 2));
1110
1153
  try {
1111
1154
  const response = await fetch(url, {
1112
1155
  method: "POST",
package/package.json CHANGED
@@ -27,7 +27,7 @@
27
27
  "engines": {
28
28
  "node": ">=20.x"
29
29
  },
30
- "version": "2.6.0",
30
+ "version": "2.6.1",
31
31
  "description": "MCP server for Tartget Process",
32
32
  "main": "build/index.js",
33
33
  "keywords": [