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 +4 -1
- package/build/handlers/create_formatted_feature.js +81 -0
- package/build/handlers/create_formatted_user_story.js +55 -0
- package/build/handlers/delete_card.js +19 -0
- package/build/handlers/get_feature_content.js +38 -0
- package/build/handlers/get_team_iterations.js +30 -0
- package/build/handlers/update_feature.js +14 -0
- package/build/index.js +260 -79
- package/build/tp.js +77 -9
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
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
|
-
|
|
615
|
-
.
|
|
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
|
|
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
|
|
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('
|
|
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,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
for
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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":
|
|
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",
|