targetprocess-mcp-server 2.4.4 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/build/handlers/create_epic.js +14 -0
- package/build/handlers/get_epic_content.js +38 -0
- package/build/handlers/get_epic_features.js +24 -0
- package/build/handlers/update_epic.js +14 -0
- package/build/index.js +82 -2
- package/build/tp.js +81 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,9 @@ Cards — Write
|
|
|
96
96
|
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
97
97
|
- `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)
|
|
98
98
|
> Call `get_user_story_content` first to read the current content, then reconstruct the structured fields before calling this tool
|
|
99
|
+
- `get_epic_content` — Get a Targetprocess Epic by ID, including description, state, and progress (id)
|
|
100
|
+
- `update_epic` — Update an epic's title, description, release, or project (id, optional title, optional description, optional releaseId, optional projectId)
|
|
101
|
+
- `get_epic_features` — Get all features belonging to an epic (id)
|
|
99
102
|
- `create_feature` — Create a new feature (title, optional description, optional epicId, optional releaseId, optional projectId, optional teamId)
|
|
100
103
|
> [!NOTE]
|
|
101
104
|
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
@@ -129,6 +132,11 @@ Time Tracking
|
|
|
129
132
|
- `log_time` — Log time spent on a Task, User Story, or Bug (entityId, entityType: Task | UserStory | Bug, hours, optional description, optional date)
|
|
130
133
|
- `get_my_time_logs` — Get recent time log entries submitted by the current user (optional take)
|
|
131
134
|
|
|
135
|
+
Assignments
|
|
136
|
+
- `assign_role` — Assign a user to a role (e.g. Business Analyst, Developer, QA Engineer) on a TP card (cardId, userId, roleId)
|
|
137
|
+
- `assign_role_to_feature` — Assign a user to a role on all user stories in a feature in one call (featureId, userId, roleId)
|
|
138
|
+
- `get_assignment_roles` — List all available assignment roles with their IDs
|
|
139
|
+
|
|
132
140
|
Developer Tools
|
|
133
141
|
- `get_commit_message` — Returns a formatted commit message string for a task or bug ID (id, type: task | bug)
|
|
134
142
|
> Format for task on a user story: `F#<featureId> US#<userStoryId> T#<taskId> <title>`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function handleCreateEpic(tp, params) {
|
|
2
|
+
const response = await tp.createEpic(params);
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to create epic "${params.title}"\n JSON: ${JSON.stringify(response, null, 2)}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
export async function handleGetEpicContent(tp, id) {
|
|
3
|
+
const epic = await tp.getEpic(id);
|
|
4
|
+
if (!epic) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `Failed to get epic, id: ${id}\n JSON: ${JSON.stringify(epic, null, 2)}`
|
|
9
|
+
}],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const result = {
|
|
13
|
+
name: epic.Name,
|
|
14
|
+
id: epic.Id,
|
|
15
|
+
description: '',
|
|
16
|
+
entityState: epic.EntityState?.Name,
|
|
17
|
+
release: epic.Release?.Name,
|
|
18
|
+
portfolioEpic: epic.PortfolioEpic?.Name,
|
|
19
|
+
progress: epic.Progress,
|
|
20
|
+
effort: epic.Effort,
|
|
21
|
+
customFields: epic.CustomFields,
|
|
22
|
+
};
|
|
23
|
+
const description = epic.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 epic description:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export async function handleGetEpicFeatures(tp, epicId) {
|
|
2
|
+
const response = await tp.getEpicFeatures(epicId);
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to get features for epic id: ${epicId}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const features = response.Items ?? [];
|
|
12
|
+
const result = features.map(f => ({
|
|
13
|
+
id: f.Id,
|
|
14
|
+
name: f.Name,
|
|
15
|
+
entityState: f.EntityState?.Name,
|
|
16
|
+
team: f.Team?.Name,
|
|
17
|
+
release: f.Release?.Name,
|
|
18
|
+
progress: f.Progress,
|
|
19
|
+
effort: f.Effort,
|
|
20
|
+
}));
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: 'text', text: JSON.stringify({ total: result.length, features: result }) }],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function handleUpdateEpic(tp, params) {
|
|
2
|
+
const response = await tp.updateEpic(params);
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to update epic 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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { JSDOM } from "jsdom";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
import { TpClient } from "./tp.js";
|
|
6
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
8
|
import { config } from "./config.js";
|
|
@@ -25,6 +26,10 @@ import { handleGetBugComments } from "./handlers/get_bug_comments.js";
|
|
|
25
26
|
import { handleCreateBug } from "./handlers/create_bug.js";
|
|
26
27
|
import { handleCreateUserStory } from "./handlers/create_user_story.js";
|
|
27
28
|
import { handleCreateFeature } from "./handlers/create_feature.js";
|
|
29
|
+
import { handleCreateEpic } from "./handlers/create_epic.js";
|
|
30
|
+
import { handleGetEpicContent } from "./handlers/get_epic_content.js";
|
|
31
|
+
import { handleUpdateEpic } from "./handlers/update_epic.js";
|
|
32
|
+
import { handleGetEpicFeatures } from "./handlers/get_epic_features.js";
|
|
28
33
|
import { handleCreateTask } from "./handlers/create_task.js";
|
|
29
34
|
import { handleUpdateBug } from "./handlers/update_bug.js";
|
|
30
35
|
import { handleGetInProgressTasksAndBugs } from "./handlers/get_in_progress_tasks_and_bugs.js";
|
|
@@ -474,9 +479,12 @@ server.registerTool('update_user_story', {
|
|
|
474
479
|
entityStateId: z.string()
|
|
475
480
|
.optional()
|
|
476
481
|
.describe('Optional Entity State ID — if user gave a state name, resolve it via "get_user_story_workflows" first'),
|
|
482
|
+
featureId: z.string()
|
|
483
|
+
.optional()
|
|
484
|
+
.describe('Optional Feature ID — moves this user story to the specified feature'),
|
|
477
485
|
},
|
|
478
|
-
}, async ({ id, title, description, projectId, teamId, entityStateId }) => {
|
|
479
|
-
const response = await tp.updateUserStory({ id, title, description, projectId, teamId, entityStateId });
|
|
486
|
+
}, async ({ id, title, description, projectId, teamId, entityStateId, featureId }) => {
|
|
487
|
+
const response = await tp.updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId });
|
|
480
488
|
if (!response) {
|
|
481
489
|
return {
|
|
482
490
|
content: [{
|
|
@@ -718,6 +726,69 @@ server.registerTool('create_feature', {
|
|
|
718
726
|
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
719
727
|
},
|
|
720
728
|
}, async ({ title, description, epicId, releaseId, projectId, teamId }) => handleCreateFeature(tp, { title, description, epicId, releaseId, projectId, teamId }));
|
|
729
|
+
server.registerTool('create_epic', {
|
|
730
|
+
title: 'Create a new epic',
|
|
731
|
+
description: `Create a new Epic in Targetprocess.`,
|
|
732
|
+
inputSchema: {
|
|
733
|
+
title: z.string()
|
|
734
|
+
.describe('Epic title'),
|
|
735
|
+
description: z.string()
|
|
736
|
+
.optional()
|
|
737
|
+
.describe('Optional epic description (when provided, format as HTML)'),
|
|
738
|
+
releaseId: z.string()
|
|
739
|
+
.min(5)
|
|
740
|
+
.max(6)
|
|
741
|
+
.optional()
|
|
742
|
+
.describe('Optional Release ID to link this epic to (e.g. 145200)'),
|
|
743
|
+
projectId: z.string()
|
|
744
|
+
.optional()
|
|
745
|
+
.describe('Optional Project ID -- defaults to TP_PROJECT_ID from config'),
|
|
746
|
+
},
|
|
747
|
+
}, async ({ title, description, releaseId, projectId }) => handleCreateEpic(tp, { title, description, releaseId, projectId }));
|
|
748
|
+
server.registerTool('get_epic_content', {
|
|
749
|
+
title: 'Get epic content',
|
|
750
|
+
description: 'Get a Targetprocess Epic by ID, including its description, state, and progress.',
|
|
751
|
+
inputSchema: {
|
|
752
|
+
id: z.string()
|
|
753
|
+
.min(5)
|
|
754
|
+
.max(6)
|
|
755
|
+
.describe('Epic ID (e.g. 148813)'),
|
|
756
|
+
},
|
|
757
|
+
}, async ({ id }) => handleGetEpicContent(tp, id));
|
|
758
|
+
server.registerTool('update_epic', {
|
|
759
|
+
title: 'Update an epic',
|
|
760
|
+
description: 'Update a Targetprocess Epic. Pass only the fields to change.',
|
|
761
|
+
inputSchema: {
|
|
762
|
+
id: z.string()
|
|
763
|
+
.min(5)
|
|
764
|
+
.max(6)
|
|
765
|
+
.describe('Epic ID (e.g. 148813)'),
|
|
766
|
+
title: z.string()
|
|
767
|
+
.optional()
|
|
768
|
+
.describe('Updated epic title'),
|
|
769
|
+
description: z.string()
|
|
770
|
+
.optional()
|
|
771
|
+
.describe('Updated epic description (format as HTML)'),
|
|
772
|
+
releaseId: z.string()
|
|
773
|
+
.min(5)
|
|
774
|
+
.max(6)
|
|
775
|
+
.optional()
|
|
776
|
+
.describe('Optional Release ID to link this epic to'),
|
|
777
|
+
projectId: z.string()
|
|
778
|
+
.optional()
|
|
779
|
+
.describe('Optional Project ID'),
|
|
780
|
+
},
|
|
781
|
+
}, async ({ id, title, description, releaseId, projectId }) => handleUpdateEpic(tp, { id, title, description, releaseId, projectId }));
|
|
782
|
+
server.registerTool('get_epic_features', {
|
|
783
|
+
title: 'Get features in an epic',
|
|
784
|
+
description: 'Get all Features belonging to a Targetprocess Epic.',
|
|
785
|
+
inputSchema: {
|
|
786
|
+
id: z.string()
|
|
787
|
+
.min(5)
|
|
788
|
+
.max(6)
|
|
789
|
+
.describe('Epic ID (e.g. 148813)'),
|
|
790
|
+
},
|
|
791
|
+
}, async ({ id }) => handleGetEpicFeatures(tp, id));
|
|
721
792
|
server.registerTool('create_test_plan', {
|
|
722
793
|
title: 'Create a new test plan linked to a TP card',
|
|
723
794
|
description: `Create a new test plan linked to a UserStory, Bug, or Feature. Name and Project are required by the API; Description, StartDate, and EndDate are optional.`,
|
|
@@ -1380,6 +1451,15 @@ server.registerTool('get_my_time_logs', {
|
|
|
1380
1451
|
.describe('Number of entries to return, default is 25'),
|
|
1381
1452
|
},
|
|
1382
1453
|
}, async ({ take }) => handleGetMyTimeLogs(tp, take));
|
|
1454
|
+
const require = createRequire(import.meta.url);
|
|
1455
|
+
const { version } = require("../package.json");
|
|
1456
|
+
server.registerTool('get_version', {
|
|
1457
|
+
title: 'Get server version',
|
|
1458
|
+
description: 'Returns the current version of the MCP server from package.json.',
|
|
1459
|
+
inputSchema: {},
|
|
1460
|
+
}, async () => ({
|
|
1461
|
+
content: [{ type: "text", text: version }]
|
|
1462
|
+
}));
|
|
1383
1463
|
async function main() {
|
|
1384
1464
|
const transport = new StdioServerTransport();
|
|
1385
1465
|
await server.connect(transport);
|
package/build/tp.js
CHANGED
|
@@ -197,7 +197,7 @@ export class TpClient {
|
|
|
197
197
|
param: { "format": "json" },
|
|
198
198
|
}, userStory);
|
|
199
199
|
}
|
|
200
|
-
async updateUserStory({ id, title, description, projectId, teamId, entityStateId }) {
|
|
200
|
+
async updateUserStory({ id, title, description, projectId, teamId, entityStateId, featureId }) {
|
|
201
201
|
const userStory = { "Id": id };
|
|
202
202
|
if (title)
|
|
203
203
|
userStory["Name"] = title;
|
|
@@ -209,6 +209,8 @@ export class TpClient {
|
|
|
209
209
|
userStory["assignedTeams"] = [{ "team": { "id": teamId } }];
|
|
210
210
|
if (entityStateId)
|
|
211
211
|
userStory["EntityState"] = { "Id": entityStateId };
|
|
212
|
+
if (featureId)
|
|
213
|
+
userStory["Feature"] = { "Id": featureId };
|
|
212
214
|
return this.post({
|
|
213
215
|
pathParam: ["UserStories"],
|
|
214
216
|
param: { "format": "json" },
|
|
@@ -283,6 +285,52 @@ export class TpClient {
|
|
|
283
285
|
param: { "format": "json" },
|
|
284
286
|
}, userStory);
|
|
285
287
|
}
|
|
288
|
+
async getEpic(epicId) {
|
|
289
|
+
return this.get({
|
|
290
|
+
pathParam: ["Epics", epicId],
|
|
291
|
+
param: { "format": "json" },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async updateEpic({ id, title, description, releaseId, projectId }) {
|
|
295
|
+
const epic = { "Id": id };
|
|
296
|
+
if (title)
|
|
297
|
+
epic["Name"] = title;
|
|
298
|
+
if (description)
|
|
299
|
+
epic["Description"] = description;
|
|
300
|
+
if (projectId)
|
|
301
|
+
epic["Project"] = { "Id": projectId };
|
|
302
|
+
if (releaseId)
|
|
303
|
+
epic["Release"] = { "Id": releaseId };
|
|
304
|
+
return this.post({
|
|
305
|
+
pathParam: ["Epics"],
|
|
306
|
+
param: { "format": "json" },
|
|
307
|
+
}, epic);
|
|
308
|
+
}
|
|
309
|
+
async getEpicFeatures(epicId) {
|
|
310
|
+
return this.get({
|
|
311
|
+
pathParam: ["Features"],
|
|
312
|
+
param: {
|
|
313
|
+
"format": "json",
|
|
314
|
+
"where": `Epic.Id eq ${epicId}`,
|
|
315
|
+
"include": "[Id,Name,Description,EntityState[Name],Team[Name],Release[Name],Progress,Effort]",
|
|
316
|
+
"take": 100,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async createEpic({ title, description, releaseId, projectId }) {
|
|
321
|
+
const epic = {
|
|
322
|
+
"Name": title,
|
|
323
|
+
"Project": { "Id": projectId || config.tp.projectId },
|
|
324
|
+
};
|
|
325
|
+
if (description)
|
|
326
|
+
epic["Description"] = description;
|
|
327
|
+
if (releaseId)
|
|
328
|
+
epic["Release"] = { "Id": releaseId };
|
|
329
|
+
return this.post({
|
|
330
|
+
pathParam: ["Epics"],
|
|
331
|
+
param: { "format": "json" },
|
|
332
|
+
}, epic);
|
|
333
|
+
}
|
|
286
334
|
async createFeature({ title, description, epicId, releaseId, projectId, teamId }) {
|
|
287
335
|
const feature = {
|
|
288
336
|
"Name": title,
|
|
@@ -772,6 +820,38 @@ export class TpClient {
|
|
|
772
820
|
param: { "format": "json" },
|
|
773
821
|
}, body);
|
|
774
822
|
}
|
|
823
|
+
async assignRole(cardId, userId, roleId) {
|
|
824
|
+
return this.post({
|
|
825
|
+
pathParam: ["Assignments"],
|
|
826
|
+
param: { "format": "json" },
|
|
827
|
+
}, {
|
|
828
|
+
Assignable: { Id: parseInt(cardId) },
|
|
829
|
+
GeneralUser: { Id: parseInt(userId) },
|
|
830
|
+
Role: { Id: parseInt(roleId) },
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
async assignRoleToAllStoriesInFeature(featureId, userId, roleId) {
|
|
834
|
+
const storiesResponse = await this.getFeatureUserStories(featureId);
|
|
835
|
+
const storyItems = storiesResponse?.items?.[0]?.userStories?.items ?? [];
|
|
836
|
+
const succeeded = [];
|
|
837
|
+
const failed = [];
|
|
838
|
+
await Promise.all(storyItems.map(async (story) => {
|
|
839
|
+
const result = await this.assignRole(String(story.id), userId, roleId);
|
|
840
|
+
if (result) {
|
|
841
|
+
succeeded.push(result);
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
failed.push(story.id);
|
|
845
|
+
}
|
|
846
|
+
}));
|
|
847
|
+
return { succeeded, failed };
|
|
848
|
+
}
|
|
849
|
+
async getAssignmentRoles() {
|
|
850
|
+
return this.get({
|
|
851
|
+
pathParam: ["Roles"],
|
|
852
|
+
param: { "format": "json" },
|
|
853
|
+
});
|
|
854
|
+
}
|
|
775
855
|
async getMyTimeLogs(take = 25) {
|
|
776
856
|
return this.get({
|
|
777
857
|
pathParam: ["Times"],
|