targetprocess-mcp-server 2.6.0 → 2.6.2

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 CHANGED
@@ -42,6 +42,7 @@ Releases
42
42
  - `get_release_open_user_stories` — Get only active/open user stories for a release (name, withDescription, optional results)
43
43
 
44
44
  Features
45
+ - `get_feature_content` — Get a Targetprocess Feature by ID, including description, state, and progress (id)
45
46
  - `get_feature_user_stories` — Get all user stories for a feature by its ID (id)
46
47
  - `get_not_covered_user_stories_in_feature` — Get user stories in a feature not yet covered by tests, includes `covered` field based on "Test Automation" custom field (id)
47
48
 
@@ -91,7 +92,7 @@ Cards — Write
91
92
  - `create_user_story` — Create a new user story (title, optional description, optional featureId, optional releaseId, optional projectId, optional teamId)
92
93
  > [!NOTE]
93
94
  > `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
94
- - `create_formatted_user_story` — Create a new user story with a structured template description (title, header object with asA/iWant/soThat, acceptanceCriteria array, scenarios array with Gherkin steps, optional definitions, examplesTable, edgeCases, references, notes, optional featureId, releaseId, projectId, teamId)
95
+ - `create_formatted_user_story` — Create a new user story with a structured template description in Header → Definitions → Scenarios → Examples Table → Edge Cases → Acceptance Criteria → References → Notes order (title, header object with asA/iWant/soThat, scenarios array with Gherkin steps, acceptanceCriteria array, optional definitions, examplesTable, edgeCases, references, notes, optional featureId, releaseId, projectId, teamId)
95
96
  > [!NOTE]
96
97
  > `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
97
98
  - `format_existing_user_story` — Re-format the description of an existing user story using the structured template (id, header object with asA/iWant/soThat, acceptanceCriteria array, scenarios array with Gherkin steps, optional title, definitions, examplesTable, edgeCases, references, notes)
@@ -102,6 +103,8 @@ Cards — Write
102
103
  - `create_feature` — Create a new feature (title, optional description, optional epicId, optional releaseId, optional projectId, optional teamId)
103
104
  > [!NOTE]
104
105
  > `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
106
+ - `create_formatted_feature` — Create a new feature with a feature-level TDRE template in Header → Definitions → Scope & Boundaries → Non-Functional Requirements → Cross-Cutting Scenarios → Child Stories → Open Questions/Risks → References → Notes order (title, header object with businessBackground, nonFunctionalRequirements array with area/requirement/status/storyOrOwner, optional definitions, scope, crossCuttingScenarios, childStories, openQuestions, references, notes, optional epicId, releaseId, projectId, teamId)
107
+ - `update_feature` — Update a feature card with data provided from user input; pass only the fields to change (id, optional title, optional description, optional epicId, optional releaseId, optional projectId, optional teamId, optional entityStateId, optional tags, optional teamIterationId)
105
108
  - `create_test_plan` — Create a test plan linked to a UserStory, Bug, or Feature (title, resourceId, optional resourceType, optional description/startDate/endDate)
106
109
  > [!NOTE]
107
110
  > requires `TP_PROJECT_ID`,
@@ -0,0 +1,81 @@
1
+ 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>');
2
+ export async function handleCreateFormattedFeature(tp, params) {
3
+ const { title, header, definitions, scope, nonFunctionalRequirements, crossCuttingScenarios, childStories, openQuestions, references, notes, epicId, releaseId, projectId, teamId } = params;
4
+ const parts = ['<div>'];
5
+ parts.push('<h3>Header</h3>');
6
+ if (header.featureId)
7
+ parts.push(`<p><strong>Feature ID:</strong> ${header.featureId}</p>`);
8
+ parts.push(`<p><strong>Business Background:</strong> ${header.businessBackground}</p>`);
9
+ if (definitions && definitions.length > 0) {
10
+ parts.push('<h3>Definitions</h3>');
11
+ parts.push('<div>');
12
+ for (const def of definitions) {
13
+ parts.push(`<p><strong>${def.term}</strong> — ${def.description}</p>`);
14
+ }
15
+ parts.push('</div>');
16
+ }
17
+ if (scope && ((scope.includes?.length ?? 0) > 0 || (scope.excludes?.length ?? 0) > 0)) {
18
+ parts.push('<h3>Scope & Boundaries</h3>');
19
+ if (scope.includes && scope.includes.length > 0) {
20
+ parts.push('<p><strong>Includes:</strong></p>');
21
+ parts.push('<ul>');
22
+ for (const item of scope.includes)
23
+ parts.push(`<li>${item}</li>`);
24
+ parts.push('</ul>');
25
+ }
26
+ if (scope.excludes && scope.excludes.length > 0) {
27
+ parts.push('<p><strong>Excludes:</strong></p>');
28
+ parts.push('<ul>');
29
+ for (const item of scope.excludes)
30
+ parts.push(`<li>${item}</li>`);
31
+ parts.push('</ul>');
32
+ }
33
+ }
34
+ parts.push('<h3>Non-Functional Requirements</h3>');
35
+ parts.push('<table><tr><th>NFR Area</th><th>Requirement</th><th>Status</th><th>Story / Owner</th></tr>');
36
+ for (const nfr of nonFunctionalRequirements) {
37
+ parts.push(`<tr><td>${nfr.area}</td><td>${nfr.requirement}</td><td>${nfr.status}</td><td>${nfr.storyOrOwner}</td></tr>`);
38
+ }
39
+ parts.push('</table>');
40
+ if (crossCuttingScenarios && crossCuttingScenarios.length > 0) {
41
+ parts.push('<h3>Cross-Cutting Scenarios</h3>');
42
+ parts.push(gherkinBlock(crossCuttingScenarios));
43
+ }
44
+ if (childStories && childStories.length > 0) {
45
+ parts.push('<h3>Child Stories</h3>');
46
+ parts.push('<ul>');
47
+ for (const story of childStories) {
48
+ parts.push(`<li>[${story.covered ? 'x' : ' '}] ${story.id} — ${story.name}${story.covered ? ' (covered)' : ' (not yet covered by tests)'}</li>`);
49
+ }
50
+ parts.push('</ul>');
51
+ }
52
+ if (openQuestions && openQuestions.length > 0) {
53
+ parts.push('<h3>Open Questions / Risks</h3>');
54
+ parts.push('<ul>');
55
+ for (const question of openQuestions)
56
+ parts.push(`<li>${question}</li>`);
57
+ parts.push('</ul>');
58
+ }
59
+ if (references) {
60
+ parts.push('<h3>References</h3>');
61
+ parts.push(`<p>${references}</p>`);
62
+ }
63
+ if (notes) {
64
+ parts.push('<h3>Notes</h3>');
65
+ parts.push(`<p>${notes}</p>`);
66
+ }
67
+ parts.push('</div>');
68
+ const description = parts.join('\n');
69
+ const featureResponse = await tp.createFeature({ title, description, epicId, releaseId, projectId, teamId });
70
+ if (!featureResponse) {
71
+ return {
72
+ content: [{
73
+ type: 'text',
74
+ text: `Failed to create formatted feature "${title}"\n JSON: ${JSON.stringify(featureResponse, null, 2)}`
75
+ }],
76
+ };
77
+ }
78
+ return {
79
+ content: [{ type: 'text', text: JSON.stringify(featureResponse) }],
80
+ };
81
+ }
@@ -0,0 +1,55 @@
1
+ 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>');
2
+ export async function handleCreateFormattedUserStory(tp, params) {
3
+ const { title, header, definitions, scenarios, examplesTable, edgeCases, acceptanceCriteria, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId } = params;
4
+ const parts = ['<div>'];
5
+ parts.push('<h3>Header</h3>');
6
+ if (header.storyId)
7
+ parts.push(`<p><strong>Story ID:</strong> ${header.storyId}</p>`);
8
+ parts.push(`<p>As a ${header.asA} <br> I want ${header.iWant} <br> so that ${header.soThat}</p>`);
9
+ if (definitions && definitions.length > 0) {
10
+ parts.push('<h3>Definitions</h3>');
11
+ parts.push('<div>');
12
+ for (const def of definitions) {
13
+ parts.push(`<p><strong>${def.term}</strong> — ${def.description}</p>`);
14
+ }
15
+ parts.push('</div>');
16
+ }
17
+ parts.push('<h3>Scenarios</h3>');
18
+ parts.push(gherkinBlock(scenarios));
19
+ if (examplesTable) {
20
+ parts.push('<h3>Examples Table</h3>');
21
+ parts.push(`<pre>${examplesTable}</pre>`);
22
+ }
23
+ if (edgeCases && edgeCases.length > 0) {
24
+ parts.push('<h3>Edge Cases</h3>');
25
+ parts.push(gherkinBlock(edgeCases));
26
+ }
27
+ parts.push('<h3>Acceptance Criteria</h3>');
28
+ parts.push('<ol>');
29
+ for (const criterion of acceptanceCriteria) {
30
+ parts.push(`<li>${criterion}</li>`);
31
+ }
32
+ parts.push('</ol>');
33
+ if (references) {
34
+ parts.push('<h3>References</h3>');
35
+ parts.push(`<p>${references}</p>`);
36
+ }
37
+ if (notes) {
38
+ parts.push('<h3>Notes</h3>');
39
+ parts.push(`<p>${notes}</p>`);
40
+ }
41
+ parts.push('</div>');
42
+ const description = parts.join('\n');
43
+ const userStoryResponse = await tp.createUserStory({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId });
44
+ if (!userStoryResponse) {
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: `Failed to create formatted user story "${title}"\n JSON: ${JSON.stringify(userStoryResponse, null, 2)}`
49
+ }],
50
+ };
51
+ }
52
+ return {
53
+ content: [{ type: 'text', text: JSON.stringify(userStoryResponse) }],
54
+ };
55
+ }
@@ -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,38 @@
1
+ import { JSDOM } from 'jsdom';
2
+ export async function handleGetFeatureContent(tp, id) {
3
+ const feature = await tp.getFeature(id);
4
+ if (!feature) {
5
+ return {
6
+ content: [{
7
+ type: 'text',
8
+ text: `Failed to get feature, id: ${id}\n JSON: ${JSON.stringify(feature, null, 2)}`
9
+ }],
10
+ };
11
+ }
12
+ const result = {
13
+ name: feature.Name,
14
+ id: feature.Id,
15
+ description: '',
16
+ entityState: feature.EntityState?.Name,
17
+ release: feature.Release?.Name,
18
+ epic: feature.Epic?.Name,
19
+ progress: feature.Progress,
20
+ effort: feature.Effort,
21
+ customFields: feature.CustomFields,
22
+ };
23
+ const description = feature.Description || '';
24
+ if (description) {
25
+ try {
26
+ const dom = new JSDOM(`<html><body><div id="content">${description}</div></body></html>`);
27
+ const text = dom.window.document.getElementById('content')?.textContent;
28
+ if (text)
29
+ result.description = text;
30
+ }
31
+ catch (error) {
32
+ console.error('Error parsing feature description:', error);
33
+ }
34
+ }
35
+ return {
36
+ content: [{ type: 'text', text: JSON.stringify(result) }],
37
+ };
38
+ }
@@ -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
+ }
@@ -0,0 +1,14 @@
1
+ export async function handleUpdateFeature(tp, params) {
2
+ const response = await tp.updateFeature(params);
3
+ if (!response) {
4
+ return {
5
+ content: [{
6
+ type: 'text',
7
+ text: `Failed to update feature id: ${params.id}\n JSON: ${JSON.stringify(response, null, 2)}`
8
+ }],
9
+ };
10
+ }
11
+ return {
12
+ content: [{ type: 'text', text: JSON.stringify(response) }],
13
+ };
14
+ }
package/build/index.js CHANGED
@@ -20,11 +20,15 @@ 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";
26
27
  import { handleCreateBug } from "./handlers/create_bug.js";
27
28
  import { handleCreateUserStory } from "./handlers/create_user_story.js";
29
+ import { handleCreateFormattedUserStory } from "./handlers/create_formatted_user_story.js";
30
+ import { handleCreateFormattedFeature } from "./handlers/create_formatted_feature.js";
31
+ import { handleUpdateFeature } from "./handlers/update_feature.js";
28
32
  import { handleCreateFeature } from "./handlers/create_feature.js";
29
33
  import { handleCreateEpic } from "./handlers/create_epic.js";
30
34
  import { handleGetEpicContent } from "./handlers/get_epic_content.js";
@@ -38,12 +42,14 @@ import { handleListMyBugs } from "./handlers/list_my_bugs.js";
38
42
  import { handleLogTime } from "./handlers/log_time.js";
39
43
  import { handleGetMyTimeLogs } from "./handlers/get_my_time_logs.js";
40
44
  import { handleGetFeatureUserStories } from "./handlers/get_feature_user_stories.js";
45
+ import { handleGetFeatureContent } from "./handlers/get_feature_content.js";
41
46
  import { handleGetUserStoryBugs } from "./handlers/get_user_story_bugs.js";
42
47
  import { handleGetCardCurrentStatus } from "./handlers/get_card_current_status.js";
43
48
  import { handleUpdateUserStorySubState } from "./handlers/update_user_story_sub_state.js";
44
49
  import { handleGetCardRelations } from "./handlers/get_card_relations.js";
45
50
  import { handleCreateCardRelation } from "./handlers/create_card_relation.js";
46
51
  import { handleDeleteCardRelation } from "./handlers/delete_card_relation.js";
52
+ import { handleDeleteCard } from "./handlers/delete_card.js";
47
53
  import { handleGetTestPlanById } from "./handlers/get_test_plan_by_id.js";
48
54
  import { handleGetTestPlanTestCasesById } from "./handlers/get_test_plan_test_cases_by_id.js";
49
55
  import { handleGetTestPlanTestCasesWithStepsById } from "./handlers/get_test_plan_test_cases_with_steps_by_id.js";
@@ -409,7 +415,8 @@ server.registerTool('update_bug', {
409
415
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
410
416
  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
417
  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;`,
418
+ 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;
419
+ 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
420
  inputSchema: {
414
421
  id: z.string()
415
422
  .min(5)
@@ -443,8 +450,14 @@ server.registerTool('update_bug', {
443
450
  entityStateId: z.string()
444
451
  .optional()
445
452
  .describe('Optional Entity State ID — if user gave a state name, resolve it via "get_bug_workflows" first; defaults to "Done"'),
453
+ tags: z.string()
454
+ .optional()
455
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
456
+ teamIterationId: z.string()
457
+ .optional()
458
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
446
459
  },
447
- }, async ({ id, title, bugContent, origin, projectId, teamId, entityStateId }) => handleUpdateBug(tp, { id, title, bugContent, origin, projectId, teamId, entityStateId }));
460
+ }, async ({ id, title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }) => handleUpdateBug(tp, { id, title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }));
448
461
  server.registerTool('update_user_story_state', {
449
462
  title: 'Update a user story card sub state',
450
463
  description: `Update a user story card sub state with data provided from user input.
@@ -474,7 +487,8 @@ server.registerTool('update_user_story', {
474
487
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
475
488
  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
489
  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;`,
490
+ 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;
491
+ 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
492
  inputSchema: {
479
493
  id: z.string()
480
494
  .min(5)
@@ -498,9 +512,15 @@ server.registerTool('update_user_story', {
498
512
  featureId: z.string()
499
513
  .optional()
500
514
  .describe('Optional Feature ID — moves this user story to the specified feature'),
515
+ tags: z.string()
516
+ .optional()
517
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
518
+ teamIterationId: z.string()
519
+ .optional()
520
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
501
521
  },
502
- }, async ({ id, title, description, projectId, teamId, entityStateId, featureId }) => {
503
- const response = await tp.updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId });
522
+ }, async ({ id, title, description, projectId, teamId, entityStateId, featureId, tags, teamIterationId }) => {
523
+ const response = await tp.updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId, tags, teamIterationId });
504
524
  if (!response) {
505
525
  return {
506
526
  content: [{
@@ -523,7 +543,8 @@ server.registerTool('create_bug', {
523
543
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
524
544
  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
545
  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;`,
546
+ 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;
547
+ 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
548
  inputSchema: {
528
549
  title: z.string()
529
550
  .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 +573,18 @@ server.registerTool('create_bug', {
552
573
  entityStateId: z.string()
553
574
  .optional()
554
575
  .describe('Optional Entity State ID — if user gave a state name, resolve it via "get_bug_workflows" first; defaults to "Done"'),
576
+ tags: z.string()
577
+ .optional()
578
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
579
+ teamIterationId: z.string()
580
+ .optional()
581
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
555
582
  },
556
- }, async ({ title, bugContent, origin, projectId, teamId, entityStateId }) => handleCreateBug(tp, { title, bugContent, origin, projectId, teamId, entityStateId }));
583
+ }, async ({ title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }) => handleCreateBug(tp, { title, bugContent, origin, projectId, teamId, entityStateId, tags, teamIterationId }));
557
584
  server.registerTool('create_user_story', {
558
585
  title: 'Create a new user story',
559
- description: `Create a new user story in Targetprocess.`,
586
+ description: `Create a new user story in Targetprocess.
587
+ 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
588
  inputSchema: {
561
589
  title: z.string()
562
590
  .describe('User story title'),
@@ -579,17 +607,32 @@ server.registerTool('create_user_story', {
579
607
  teamId: z.string()
580
608
  .optional()
581
609
  .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
610
+ tags: z.string()
611
+ .optional()
612
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
613
+ teamIterationId: z.string()
614
+ .optional()
615
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
582
616
  },
583
- }, async ({ title, description, featureId, releaseId, projectId, teamId }) => handleCreateUserStory(tp, { title, description, featureId, releaseId, projectId, teamId }));
617
+ }, async ({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }) => handleCreateUserStory(tp, { title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }));
584
618
  server.registerTool('create_formatted_user_story', {
585
619
  title: 'Create a formatted user story',
586
- description: `Create a new user story in Targetprocess with a structured, template-driven description.
587
- The description is assembled from discrete sections (header, definitions, acceptance criteria, Gherkin scenarios, edge cases, references, notes) and stored as HTML.
620
+ description: `Create a new user story in Targetprocess with a structured, template-driven description, assembled from discrete sections and stored as HTML.
621
+ Fill in each field to this quality bar:
622
+ 1) Header — "asA" and "iWant" must be filled in, and "soThat" MUST be a real business outcome, not a restatement of "iWant";
623
+ 2) Definitions — check for jargon, feature flags, module names, and acronyms; if none apply, OMIT the "definitions" field entirely (do not send an empty section);
624
+ 3) Scenarios (write these BEFORE acceptanceCriteria) — at least one Gherkin scenario per distinct behavior; each scenario has exactly ONE "Then" outcome (split it into two scenarios if it needs two); avoid vague verbs like "system validates input" — spell out exactly what "validates" means in the "Then" step;
625
+ 4) Examples Table — if any behavior is described as "supports various formats/roles/states", convert it into a Scenario Outline (one of the "scenarios" entries) plus a matching "examplesTable" with real values; if no parameterized behavior exists, OMIT "examplesTable";
626
+ 5) Edge Cases — MANDATORY (include at least one error-state scenario and one boundary-condition scenario in "edgeCases") if the story touches validation/input handling, permissions/roles, or external data/integrations; otherwise OMIT "edgeCases";
627
+ 6) Acceptance Criteria — every bullet MUST be traceable to a specific scenario or edge case above; if a bullet isn't backed by a scenario, it isn't a criterion yet — put it in "notes" as a flagged gap instead;
628
+ 7) References — put mockup/spec links here, not inline in prose;
629
+ 8) Notes — capture open questions or known constraints here; don't let them hide inside acceptanceCriteria;
588
630
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
589
631
  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
632
  2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
591
633
  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;`,
634
+ 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;
635
+ 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
636
  inputSchema: {
594
637
  title: z.string()
595
638
  .describe('User story title'),
@@ -602,7 +645,7 @@ server.registerTool('create_formatted_user_story', {
602
645
  iWant: z.string()
603
646
  .describe('Goal — the "I want ..." part'),
604
647
  soThat: z.string()
605
- .describe('Benefit — the "so that ..." part'),
648
+ .describe('Benefit — the "so that ..." part. MUST be a real business outcome, not a restatement of "iWant"'),
606
649
  })
607
650
  .describe('Story header following the As a / I want / so that format'),
608
651
  definitions: z.array(z.object({
@@ -610,22 +653,21 @@ server.registerTool('create_formatted_user_story', {
610
653
  .describe('The term, module name, or feature flag being defined'),
611
654
  description: z.string()
612
655
  .describe('Explanation of the term'),
613
- })),
614
- acceptanceCriteria: z.array(z.string())
615
- .min(1)
616
- .describe('Bullet checklist items for quick review sign-off — each string is one criterion'),
656
+ }))
657
+ .optional()
658
+ .describe('Jargon, feature flags, module names, or acronyms that need defining. Omit entirely if none apply — do not send an empty section'),
617
659
  scenarios: z.array(z.object({
618
660
  name: z.string()
619
661
  .describe('Scenario name'),
620
662
  steps: z.array(z.string())
621
663
  .min(1)
622
- .describe('Gherkin steps — each string is a full step line, e.g. "Given I am on the login page"'),
664
+ .describe('Gherkin steps — each string is a full step line, e.g. "Given I am on the login page". Exactly one "Then" step per scenario; split into another scenario if a second outcome is needed. Avoid vague verbs ("validates") — spell out what happens'),
623
665
  }))
624
666
  .min(1)
625
- .describe('Gherkin scenario blocks, one per behavior branch'),
667
+ .describe('Gherkin scenario blocks, one per distinct behavior. Write these before acceptanceCriteria'),
626
668
  examplesTable: z.string()
627
669
  .optional()
628
- .describe('Examples table for parameterized or matrix behavior (plain text or Gherkin Examples: table format)'),
670
+ .describe('Examples: table with real values backing a Scenario Outline for parameterized/matrix behavior (e.g. "supports various formats/roles/states"). Omit if no parameterized behavior exists'),
629
671
  edgeCases: z.array(z.object({
630
672
  name: z.string()
631
673
  .describe('Edge case scenario name'),
@@ -634,13 +676,16 @@ server.registerTool('create_formatted_user_story', {
634
676
  .describe('Gherkin steps for this edge case'),
635
677
  }))
636
678
  .optional()
637
- .describe('Explicit edge case or boundary condition scenarios'),
679
+ .describe('Explicit edge case or boundary condition scenarios. MANDATORY — at least one error-state and one boundary-condition scenario — if the story touches validation/input handling, permissions/roles, or external data/integrations; omit otherwise'),
680
+ acceptanceCriteria: z.array(z.string())
681
+ .min(1)
682
+ .describe('Bullet checklist items for quick review sign-off — each string is one criterion. Every bullet MUST be traceable to a specific scenario or edge case above'),
638
683
  references: z.string()
639
684
  .optional()
640
685
  .describe('Links to Axure mockups or other external references (not inline in prose)'),
641
686
  notes: z.string()
642
687
  .optional()
643
- .describe('Anything that helps understand the story context but does not fit other sections'),
688
+ .describe('Open questions or known constraints that do not fit other sections do not let these hide inside acceptanceCriteria'),
644
689
  featureId: z.string()
645
690
  .min(5)
646
691
  .max(9)
@@ -657,64 +702,121 @@ server.registerTool('create_formatted_user_story', {
657
702
  teamId: z.string()
658
703
  .optional()
659
704
  .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
705
+ tags: z.string()
706
+ .optional()
707
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
708
+ teamIterationId: z.string()
709
+ .optional()
710
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
660
711
  },
661
- }, async ({ title, header, definitions, acceptanceCriteria, scenarios, examplesTable, edgeCases, references, notes, featureId, releaseId, projectId, teamId }) => {
662
- 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
- const parts = ['<div>'];
664
- parts.push('<h3>Header</h3>');
665
- if (header.storyId)
666
- parts.push(`<p><strong>Story ID:</strong> ${header.storyId}</p>`);
667
- parts.push(`<p>As a ${header.asA} <br> I want ${header.iWant} <br> so that ${header.soThat}</p>`);
668
- if (definitions) {
669
- parts.push('<h3>Definitions</h3>');
670
- parts.push(`<div>`);
671
- for (const def of definitions) {
672
- parts.push(`<p><strong>${def.term}</strong>${def.description}</p>`);
673
- }
674
- parts.push(`</div>`);
675
- }
676
- parts.push('<h3>Acceptance Criteria</h3>');
677
- parts.push('<ol>');
678
- for (const criterion of acceptanceCriteria) {
679
- parts.push(`<li>${criterion}</li>`);
680
- }
681
- parts.push('</ol>');
682
- parts.push('<h3>Scenarios</h3>');
683
- parts.push(gherkinBlock(scenarios));
684
- if (examplesTable) {
685
- parts.push('<h3>Examples</h3>');
686
- parts.push(`<pre>${examplesTable}</pre>`);
687
- }
688
- if (edgeCases && edgeCases.length > 0) {
689
- parts.push('<h3>Edge Cases</h3>');
690
- parts.push(gherkinBlock(edgeCases));
691
- }
692
- if (references) {
693
- parts.push('<h3>References</h3>');
694
- parts.push(`<p>${references}</p>`);
695
- }
696
- if (notes) {
697
- parts.push('<h3>Notes</h3>');
698
- parts.push(`<p>${notes}</p>`);
699
- }
700
- parts.push('</div>');
701
- const description = parts.join('\n');
702
- const userStoryResponse = await tp.createUserStory({ title, description, featureId, releaseId, projectId, teamId });
703
- if (!userStoryResponse) {
704
- return {
705
- content: [{
706
- type: 'text',
707
- text: `Failed to create formatted user story "${title}"\n JSON: ${JSON.stringify(userStoryResponse, null, 2)}`
708
- }]
709
- };
710
- }
711
- return {
712
- content: [{
713
- type: 'text',
714
- text: JSON.stringify(userStoryResponse)
715
- }],
716
- };
717
- });
712
+ }, async ({ title, header, definitions, scenarios, examplesTable, edgeCases, acceptanceCriteria, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId }) => handleCreateFormattedUserStory(tp, { title, header, definitions, scenarios, examplesTable, edgeCases, acceptanceCriteria, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId }));
713
+ server.registerTool('create_formatted_feature', {
714
+ title: 'Create a formatted feature',
715
+ description: `Create a new Feature in Targetprocess with a structured, template-driven description (feature-level TDRE), assembled from discrete sections and stored as HTML.
716
+ Features sit above user stories — this template isn't about writing Gherkin for individual behaviors (that belongs on child stories); it's about tracking cross-cutting constraints, risks, and open questions to a testable/decided state before they get lost across many separate stories.
717
+ Fill in each field to this quality bar:
718
+ 1) Header — "businessBackground" is a 1-2 sentence value statement: who benefits and why;
719
+ 2) Definitions — cross-cutting terms used across multiple child stories, so they aren't redefined at every story level; if none apply, OMIT "definitions" entirely (do not send an empty section);
720
+ 3) Scope & Boundaries — what this feature explicitly includes/excludes, to stop child stories drifting into adjacent features; omit if genuinely trivial;
721
+ 4) Non-Functional Requirements ("nonFunctionalRequirements") — every NFR category (Security, Compliance, Billing, Operational, etc.) MUST be converted from prose into one row with status "Covered" (link the child story/scenario that proves it in storyOrOwner), "Gap" (no story covers it yet — storyOrOwner names who should follow up), or "Decision needed" (genuinely still open, not testable until resolved). This is the core of the template — never leave an NFR as untested prose;
722
+ 5) Cross-Cutting Scenarios — ONLY for behavior spanning multiple child stories that wouldn't naturally sit in any one of them (e.g. tenant isolation across all stories); do not duplicate per-story Gherkin here; omit if none apply;
723
+ 6) Child Stories ("childStories")pull this from "get_feature_user_stories" / "get_not_covered_user_stories_in_feature" rather than retyping it; keep it as a live pointer, not a duplicate spec; normally empty when first creating the feature;
724
+ 7) Open Questions / Risks ("openQuestions") — anything raised at feature conception that hasn't been resolved into either a Covered NFR row or a child story; this is the section most likely to get silently dropped — treat it as the running "not done yet" list until each line is promoted to a Covered NFR row;
725
+ 8) References — mockup/spec links here, not inline in prose;
726
+ 9) Notes — anything else that helps understand context but doesn't fit other sections;
727
+ CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
728
+ 1) IF the user specified an epic by name (not ID), resolve the epic ID first;
729
+ 2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
730
+ 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;
731
+ 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;
732
+ 5) IF this feature already has child stories, call "get_feature_user_stories" and "get_not_covered_user_stories_in_feature" to build "childStories" instead of guessing coverage;`,
733
+ inputSchema: {
734
+ title: z.string()
735
+ .describe('Feature title'),
736
+ header: z.object({
737
+ featureId: z.string()
738
+ .optional()
739
+ .describe('Feature ID if already known (e.g. TP-145636), omit for new features'),
740
+ businessBackground: z.string()
741
+ .describe('1-2 sentence value statement — who benefits and why'),
742
+ })
743
+ .describe('Feature header'),
744
+ definitions: z.array(z.object({
745
+ term: z.string()
746
+ .describe('The term, module name, or feature flag being defined'),
747
+ description: z.string()
748
+ .describe('Explanation of the term'),
749
+ }))
750
+ .optional()
751
+ .describe('Cross-cutting terms used across multiple child stories, avoiding re-defining the same term at every story level. Omit entirely if none apply'),
752
+ scope: z.object({
753
+ includes: z.array(z.string())
754
+ .optional()
755
+ .describe('What this feature explicitly includes'),
756
+ excludes: z.array(z.string())
757
+ .optional()
758
+ .describe('What this feature explicitly excludes prevents child stories drifting into adjacent features'),
759
+ })
760
+ .optional()
761
+ .describe('Scope & Boundaries. Omit if genuinely trivial'),
762
+ nonFunctionalRequirements: z.array(z.object({
763
+ area: z.string()
764
+ .describe('NFR category, e.g. Security, Compliance, Billing, Operational'),
765
+ requirement: z.string()
766
+ .describe('The requirement, stated so it can be judged Covered/Gap/Decision needed'),
767
+ status: z.enum(["Covered", "Gap", "Decision needed"])
768
+ .describe('Covered = a child story/scenario proves it; Gap = no story covers it yet; Decision needed = still open — not testable until resolved'),
769
+ storyOrOwner: z.string()
770
+ .describe('If Covered, the child story ID/scenario that proves it; if Gap or Decision needed, who owns the follow-up (e.g. "Needs legal/BA follow-up")'),
771
+ }))
772
+ .min(1)
773
+ .describe('Every NFR category converted from prose into a testable/decided row — the core of this template. Do not leave requirements as untested prose'),
774
+ crossCuttingScenarios: z.array(z.object({
775
+ name: z.string()
776
+ .describe('Scenario name'),
777
+ steps: z.array(z.string())
778
+ .min(1)
779
+ .describe('Gherkin steps — each string is a full step line'),
780
+ }))
781
+ .optional()
782
+ .describe('Only for behavior spanning multiple child stories that would not naturally sit in any single one of them. Do not duplicate per-story Gherkin here; omit if none apply'),
783
+ childStories: z.array(z.object({
784
+ id: z.string()
785
+ .describe('Child story ID (e.g. 145789)'),
786
+ name: z.string()
787
+ .describe('Child story title'),
788
+ covered: z.boolean()
789
+ .describe('Whether this story is covered by tests'),
790
+ }))
791
+ .optional()
792
+ .describe('Pull this from "get_feature_user_stories" / "get_not_covered_user_stories_in_feature" rather than retyping it — a live pointer, not a duplicate spec. Normally empty when first creating the feature'),
793
+ openQuestions: z.array(z.string())
794
+ .optional()
795
+ .describe('Anything raised at feature conception not yet resolved into a Covered NFR row or a child story — the running "not done yet" list'),
796
+ references: z.string()
797
+ .optional()
798
+ .describe('Links to specs/mockups (not inline in prose)'),
799
+ notes: z.string()
800
+ .optional()
801
+ .describe('Anything else that helps understand context but does not fit other sections'),
802
+ epicId: z.string()
803
+ .min(5)
804
+ .max(9)
805
+ .optional()
806
+ .describe('Optional Epic ID to link this feature to (e.g. 145636)'),
807
+ releaseId: z.string()
808
+ .min(5)
809
+ .max(9)
810
+ .optional()
811
+ .describe('Optional Release ID to link this feature to (e.g. 145200)'),
812
+ projectId: z.string()
813
+ .optional()
814
+ .describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
815
+ teamId: z.string()
816
+ .optional()
817
+ .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
818
+ },
819
+ }, async ({ title, header, definitions, scope, nonFunctionalRequirements, crossCuttingScenarios, childStories, openQuestions, references, notes, epicId, releaseId, projectId, teamId }) => handleCreateFormattedFeature(tp, { title, header, definitions, scope, nonFunctionalRequirements, crossCuttingScenarios, childStories, openQuestions, references, notes, epicId, releaseId, projectId, teamId }));
718
820
  server.registerTool('create_feature', {
719
821
  title: 'Create a new feature',
720
822
  description: `Create a new Feature in Targetprocess.`,
@@ -742,6 +844,54 @@ server.registerTool('create_feature', {
742
844
  .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
743
845
  },
744
846
  }, async ({ title, description, epicId, releaseId, projectId, teamId }) => handleCreateFeature(tp, { title, description, epicId, releaseId, projectId, teamId }));
847
+ server.registerTool('update_feature', {
848
+ title: 'Update a feature card',
849
+ description: `Update a feature card with data provided from user input.
850
+ NOTE: pass only the fields that user wants to update.
851
+ CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
852
+ 1) IF the user specified an epic by name (not ID), resolve the epic ID first;
853
+ 2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
854
+ 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;
855
+ 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;
856
+ 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;`,
857
+ inputSchema: {
858
+ id: z.string()
859
+ .min(5)
860
+ .max(9)
861
+ .describe('Feature card ID (e.g. 145636)'),
862
+ title: z.string()
863
+ .optional()
864
+ .describe('Updated feature title'),
865
+ description: z.string()
866
+ .optional()
867
+ .describe('Updated feature description (format as HTML)'),
868
+ epicId: z.string()
869
+ .min(5)
870
+ .max(9)
871
+ .optional()
872
+ .describe('Optional Epic ID — moves this feature to the specified epic'),
873
+ releaseId: z.string()
874
+ .min(5)
875
+ .max(9)
876
+ .optional()
877
+ .describe('Optional Release ID to link this feature to'),
878
+ projectId: z.string()
879
+ .optional()
880
+ .describe('Optional Project ID — if user gave a project name, resolve it via "get_projects" first'),
881
+ teamId: z.string()
882
+ .optional()
883
+ .describe('Optional Team ID — if user gave a team name, resolve it via "get_teams" first'),
884
+ entityStateId: z.string()
885
+ .optional()
886
+ .describe('Optional Entity State ID — ask the user for the exact ID if given a state name; no dedicated feature-workflow lookup tool exists yet'),
887
+ tags: z.string()
888
+ .optional()
889
+ .describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
890
+ teamIterationId: z.string()
891
+ .optional()
892
+ .describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
893
+ },
894
+ }, async ({ id, title, description, epicId, releaseId, projectId, teamId, entityStateId, tags, teamIterationId }) => handleUpdateFeature(tp, { id, title, description, epicId, releaseId, projectId, teamId, entityStateId, tags, teamIterationId }));
745
895
  server.registerTool('create_epic', {
746
896
  title: 'Create a new epic',
747
897
  description: `Create a new Epic in Targetprocess.`,
@@ -939,6 +1089,16 @@ server.registerTool('get_feature_user_stories', {
939
1089
  .describe('TP feature ID (e.g. 145636)'),
940
1090
  },
941
1091
  }, async ({ id }) => handleGetFeatureUserStories(tp, id));
1092
+ server.registerTool('get_feature_content', {
1093
+ title: 'Get TP feature content',
1094
+ description: 'Get a Targetprocess Feature by ID, including description, state, and progress',
1095
+ inputSchema: {
1096
+ id: z.string()
1097
+ .min(5)
1098
+ .max(9)
1099
+ .describe('TP feature ID (e.g. 145636)'),
1100
+ },
1101
+ }, async ({ id }) => handleGetFeatureContent(tp, id));
942
1102
  server.registerTool('get_user_story_bugs', {
943
1103
  title: 'Get user story bugs',
944
1104
  description: 'Get bugs linked to a TP user story by its ID',
@@ -961,6 +1121,16 @@ server.registerTool('get_teams', {
961
1121
  title: 'Get teams',
962
1122
  description: 'Get all Targetprocess teams',
963
1123
  }, async () => handleGetTeams(tp));
1124
+ server.registerTool('get_team_iterations', {
1125
+ title: 'Get team iterations',
1126
+ 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.
1127
+ 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.`,
1128
+ inputSchema: {
1129
+ teamId: z.string()
1130
+ .optional()
1131
+ .describe('Optional Team ID to filter iterations by — resolve it via "get_teams" first'),
1132
+ },
1133
+ }, async ({ teamId }) => handleGetTeamIterations(tp, { teamId }));
964
1134
  server.registerTool('get_logged_in_user', {
965
1135
  title: 'Get logged in user',
966
1136
  description: 'Get logged in user',
@@ -1235,6 +1405,17 @@ server.registerTool('delete_card_relation', {
1235
1405
  .describe('The relation ID to delete (the "relationId" field from "get_card_relations", e.g. 20748)'),
1236
1406
  },
1237
1407
  }, async ({ relationId }) => handleDeleteCardRelation(tp, relationId));
1408
+ server.registerTool('delete_card', {
1409
+ title: 'Delete a card (Bug, User Story, Feature, or Epic)',
1410
+ description: `Delete (remove) a Targetprocess card by its ID. Works on Bugs, User Stories, Features, and Epics.
1411
+ IF the type is uncertain, resolve it first via "search_tp_cards" or by fetching the card.`,
1412
+ inputSchema: {
1413
+ id: z.string()
1414
+ .describe('The card ID to delete (e.g. 148980)'),
1415
+ type: z.enum(["Bug", "UserStory", "Feature", "Epic"])
1416
+ .describe('The entity type of the card being deleted'),
1417
+ },
1418
+ }, async ({ id, type }) => handleDeleteCard(tp, { id, type }));
1238
1419
  server.registerTool('get_in_progress_tasks_and_bugs', {
1239
1420
  title: 'Get in-progress tasks and bugs for a user',
1240
1421
  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],
@@ -378,6 +409,31 @@ export class TpClient {
378
409
  param: { "format": "json" },
379
410
  }, feature);
380
411
  }
412
+ async updateFeature({ id, title, description, epicId, releaseId, projectId, teamId, entityStateId, tags, teamIterationId }) {
413
+ const feature = { "Id": id };
414
+ if (title)
415
+ feature["Name"] = title;
416
+ if (description)
417
+ feature["Description"] = description;
418
+ if (epicId)
419
+ feature["Epic"] = { "Id": epicId };
420
+ if (releaseId)
421
+ feature["Release"] = { "Id": releaseId };
422
+ if (projectId)
423
+ feature["Project"] = { "Id": projectId };
424
+ if (teamId)
425
+ feature["assignedTeams"] = [{ "team": { "id": teamId } }];
426
+ if (entityStateId)
427
+ feature["EntityState"] = { "Id": entityStateId };
428
+ if (tags)
429
+ feature["Tags"] = tags;
430
+ if (teamIterationId)
431
+ feature["TeamIteration"] = { "Id": teamIterationId };
432
+ return this.post({
433
+ pathParam: ["Features"],
434
+ param: { "format": "json" },
435
+ }, feature);
436
+ }
381
437
  async createBugBasedOnUserStory(title, userStoryId, bugContent) {
382
438
  const bug = {
383
439
  "Name": title,
@@ -798,6 +854,18 @@ export class TpClient {
798
854
  param: { "format": "json" },
799
855
  });
800
856
  }
857
+ async deleteCard({ id, type }) {
858
+ const pathSegment = {
859
+ "Bug": "bugs",
860
+ "UserStory": "userStories",
861
+ "Feature": "features",
862
+ "Epic": "Epics",
863
+ };
864
+ return this.del({
865
+ pathParam: [pathSegment[type], id],
866
+ param: { "format": "json" },
867
+ });
868
+ }
801
869
  async getProjects() {
802
870
  return this.get({
803
871
  pathParam: ["Projects"],
@@ -1106,7 +1174,7 @@ export class TpClient {
1106
1174
  formData.append("generalId", generalId);
1107
1175
  formData.append("file", blob, fileName);
1108
1176
  const url = `${this.baseUrl}/UploadFile.ashx?access_token=${this.token}`;
1109
- console.error(JSON.stringify({ "UPLOAD_URL": url.replace(this.token, "***") }, null, 2));
1177
+ console.error(JSON.stringify({ "UPLOAD_URL": this.redact(url) }, null, 2));
1110
1178
  try {
1111
1179
  const response = await fetch(url, {
1112
1180
  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.2",
31
31
  "description": "MCP server for Tartget Process",
32
32
  "main": "build/index.js",
33
33
  "keywords": [