targetprocess-mcp-server 2.1.8 → 2.2.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 +7 -4
- package/build/index.js +155 -8
- package/build/tp.js +47 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,13 +55,16 @@ Cards — Read
|
|
|
55
55
|
|
|
56
56
|
Cards — Write
|
|
57
57
|
- `add_comment` — Post a comment to any card (id, comment)
|
|
58
|
-
- `create_bug` — Create a standalone bug (title, bugContent, optional origin)
|
|
58
|
+
- `create_bug` — Create a standalone bug (title, bugContent, optional origin, optional projectId, optional teamId)
|
|
59
59
|
> `origin` accepted values: `Production - Customer`, `Production - Internal`, `Pre-Release - Customer`, `Pre-Release - Internal`, `Regression - Dev01`, `Regression - Team Env`, `Manual QA` *(default)*, `Developer Raised`, `Operations`
|
|
60
60
|
> [!NOTE]
|
|
61
|
-
>
|
|
62
|
-
- `create_bug_based_on_card` — Create a bug linked to an existing user story or bug card (card object with id+type, title, bugContent, optional origin)
|
|
61
|
+
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
62
|
+
- `create_bug_based_on_card` — Create a bug linked to an existing user story or bug card (card object with id+type, title, bugContent, optional origin, optional projectId, optional teamId)
|
|
63
63
|
> [!NOTE]
|
|
64
|
-
>
|
|
64
|
+
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
65
|
+
- `create_user_story` — Create a new user story (title, optional description, optional featureId, optional releaseId, optional projectId, optional teamId)
|
|
66
|
+
> [!NOTE]
|
|
67
|
+
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
65
68
|
- `create_test_plan` — Create a test plan linked to a UserStory, Bug, or Feature (title, resourceId, optional resourceType, optional description/startDate/endDate)
|
|
66
69
|
> [!NOTE]
|
|
67
70
|
> requires `TP_PROJECT_ID`,
|
package/build/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { homedir } from "os";
|
|
2
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
5
|
import { z } from "zod";
|
|
4
6
|
import { JSDOM } from "jsdom";
|
|
@@ -335,17 +337,21 @@ server.registerTool('get_release_open_user_stories', {
|
|
|
335
337
|
server.registerTool('search_tp_cards', {
|
|
336
338
|
title: 'Search TP cards by keyword or phrase in description',
|
|
337
339
|
description: `Searches TP cards (UserStories or Bugs) by keyword or phrase or partial keyphrase in Card Description e.g. "Text Element", "Font field"
|
|
338
|
-
NOTE: after results are returned, try analyze and filter results by most relevant to what user is looking for in the description text
|
|
340
|
+
NOTE: after results are returned, try analyze and filter results by most relevant to what user is looking for in the description text
|
|
341
|
+
FALLBACK: if no results are found, try spliting phrase by spaces and searching for each word and with "Generals" entity type`,
|
|
339
342
|
inputSchema: {
|
|
340
343
|
keyword: z.string()
|
|
341
344
|
.describe('Keyword or partial name or keyphrase to search for in description'),
|
|
342
|
-
entityType: z.enum(["UserStories", "Bugs"])
|
|
345
|
+
entityType: z.enum(["UserStories", "Bugs", "Generals"])
|
|
343
346
|
.default("UserStories")
|
|
344
347
|
.optional()
|
|
345
348
|
.describe('Type of TP entity to search — UserStories or Bugs (default: UserStories)'),
|
|
346
349
|
},
|
|
347
350
|
}, async ({ keyword, entityType = "UserStories" }) => {
|
|
348
|
-
const results = await
|
|
351
|
+
const results = await Promise.all([
|
|
352
|
+
tp.searchContainsNameText({ text: keyword, entityType }),
|
|
353
|
+
tp.searchContainsDescriptionText({ text: keyword, entityType })
|
|
354
|
+
]);
|
|
349
355
|
if (!results) {
|
|
350
356
|
return {
|
|
351
357
|
content: [{
|
|
@@ -354,7 +360,7 @@ server.registerTool('search_tp_cards', {
|
|
|
354
360
|
}],
|
|
355
361
|
};
|
|
356
362
|
}
|
|
357
|
-
const items = results.
|
|
363
|
+
const items = results.map((item) => item.Items).flat();
|
|
358
364
|
if (items.length == 0) {
|
|
359
365
|
return {
|
|
360
366
|
content: [{
|
|
@@ -629,9 +635,15 @@ server.registerTool('create_bug_based_on_card', {
|
|
|
629
635
|
.default("Manual QA")
|
|
630
636
|
.optional()
|
|
631
637
|
.describe('Where the bug was found, defaults to "Manual QA"'),
|
|
638
|
+
projectId: z.string()
|
|
639
|
+
.optional()
|
|
640
|
+
.describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
|
|
641
|
+
teamId: z.string()
|
|
642
|
+
.optional()
|
|
643
|
+
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
632
644
|
},
|
|
633
|
-
}, async ({ title, card, bugContent, origin }) => {
|
|
634
|
-
const bugResponse = await tp.createBug({ title, card, bugContent, origin });
|
|
645
|
+
}, async ({ title, card, bugContent, origin, projectId, teamId }) => {
|
|
646
|
+
const bugResponse = await tp.createBug({ title, card, bugContent, origin, projectId, teamId });
|
|
635
647
|
if (!bugResponse) {
|
|
636
648
|
return {
|
|
637
649
|
content: [{
|
|
@@ -672,9 +684,15 @@ server.registerTool('create_bug', {
|
|
|
672
684
|
.default("Manual QA")
|
|
673
685
|
.optional()
|
|
674
686
|
.describe('Where the bug was found, defaults to "Manual QA"'),
|
|
687
|
+
projectId: z.string()
|
|
688
|
+
.optional()
|
|
689
|
+
.describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
|
|
690
|
+
teamId: z.string()
|
|
691
|
+
.optional()
|
|
692
|
+
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
675
693
|
},
|
|
676
|
-
}, async ({ title, bugContent, origin }) => {
|
|
677
|
-
const bugResponse = await tp.createBugOnly({ title, bugContent, origin });
|
|
694
|
+
}, async ({ title, bugContent, origin, projectId, teamId }) => {
|
|
695
|
+
const bugResponse = await tp.createBugOnly({ title, bugContent, origin, projectId, teamId });
|
|
678
696
|
if (!bugResponse) {
|
|
679
697
|
return {
|
|
680
698
|
content: [{
|
|
@@ -690,6 +708,49 @@ server.registerTool('create_bug', {
|
|
|
690
708
|
}],
|
|
691
709
|
};
|
|
692
710
|
});
|
|
711
|
+
server.registerTool('create_user_story', {
|
|
712
|
+
title: 'Create a new user story',
|
|
713
|
+
description: `Create a new user story in Targetprocess.`,
|
|
714
|
+
inputSchema: {
|
|
715
|
+
title: z.string()
|
|
716
|
+
.describe('User story title'),
|
|
717
|
+
description: z.string()
|
|
718
|
+
.optional()
|
|
719
|
+
.describe('Optional user story description (when provided, format as HTML)'),
|
|
720
|
+
featureId: z.string()
|
|
721
|
+
.min(5)
|
|
722
|
+
.max(6)
|
|
723
|
+
.optional()
|
|
724
|
+
.describe('Optional Feature ID to link this user story to (e.g. 145636)'),
|
|
725
|
+
releaseId: z.string()
|
|
726
|
+
.min(5)
|
|
727
|
+
.max(6)
|
|
728
|
+
.optional()
|
|
729
|
+
.describe('Optional Release ID to link this user story to (e.g. 145200)'),
|
|
730
|
+
projectId: z.string()
|
|
731
|
+
.optional()
|
|
732
|
+
.describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
|
|
733
|
+
teamId: z.string()
|
|
734
|
+
.optional()
|
|
735
|
+
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
736
|
+
},
|
|
737
|
+
}, async ({ title, description, featureId, releaseId, projectId, teamId }) => {
|
|
738
|
+
const userStoryResponse = await tp.createUserStory({ title, description, featureId, releaseId, projectId, teamId });
|
|
739
|
+
if (!userStoryResponse) {
|
|
740
|
+
return {
|
|
741
|
+
content: [{
|
|
742
|
+
type: 'text',
|
|
743
|
+
text: `Failed to create user story "${title}"\n JSON: ${JSON.stringify(userStoryResponse, null, 2)}`
|
|
744
|
+
}]
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
content: [{
|
|
749
|
+
type: 'text',
|
|
750
|
+
text: JSON.stringify(userStoryResponse)
|
|
751
|
+
}],
|
|
752
|
+
};
|
|
753
|
+
});
|
|
693
754
|
server.registerTool('create_test_plan', {
|
|
694
755
|
title: 'Create a new test plan linked to a TP card',
|
|
695
756
|
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.`,
|
|
@@ -1136,6 +1197,92 @@ server.registerTool('add_test_cases_to_test_plan', {
|
|
|
1136
1197
|
}]
|
|
1137
1198
|
};
|
|
1138
1199
|
});
|
|
1200
|
+
server.registerTool('add_attached_file_to_card', {
|
|
1201
|
+
title: 'Attach a file to a TP card',
|
|
1202
|
+
description: 'Upload and attach a local file to a Targetprocess entity (UserStory, Bug, Feature, etc.) by its ID',
|
|
1203
|
+
inputSchema: {
|
|
1204
|
+
generalId: z.string()
|
|
1205
|
+
.min(5)
|
|
1206
|
+
.max(6)
|
|
1207
|
+
.describe('TP entity ID to attach the file to (e.g. 145789)'),
|
|
1208
|
+
filePath: z.string()
|
|
1209
|
+
.describe('Absolute path to the local file to upload (e.g. /Users/mcs/Desktop/image.png)'),
|
|
1210
|
+
},
|
|
1211
|
+
}, async ({ generalId, filePath }) => {
|
|
1212
|
+
try {
|
|
1213
|
+
const result = await tp.addAttachedFile(generalId, filePath);
|
|
1214
|
+
if (!result) {
|
|
1215
|
+
return {
|
|
1216
|
+
content: [{
|
|
1217
|
+
type: 'text',
|
|
1218
|
+
text: `Failed to attach file "${filePath}" to card id: ${generalId}`
|
|
1219
|
+
}]
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
return {
|
|
1223
|
+
content: [{
|
|
1224
|
+
type: 'text',
|
|
1225
|
+
text: result
|
|
1226
|
+
}]
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
catch (error) {
|
|
1230
|
+
return {
|
|
1231
|
+
content: [{
|
|
1232
|
+
type: 'text',
|
|
1233
|
+
text: `Error attaching file to card ${generalId}: ${error}`
|
|
1234
|
+
}]
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
server.registerTool('find_file_on_disk', {
|
|
1239
|
+
title: 'Find a file on disk by name',
|
|
1240
|
+
description: `Search for a file by name on the local filesystem.
|
|
1241
|
+
First tries an exact (case-insensitive) match.
|
|
1242
|
+
If nothing is found, retries using the first half of the filename (without extension) as a wildcard — e.g. "screenshot_bug" becomes "*screensho*".
|
|
1243
|
+
Returns a list of matching absolute paths.`,
|
|
1244
|
+
inputSchema: {
|
|
1245
|
+
fileName: z.string()
|
|
1246
|
+
.describe('Filename to search for, with or without extension (e.g. "screenshot.png" or "report")'),
|
|
1247
|
+
searchDir: z.string()
|
|
1248
|
+
.optional()
|
|
1249
|
+
.describe('Directory to search in — defaults to the user home directory'),
|
|
1250
|
+
},
|
|
1251
|
+
}, async ({ fileName, searchDir }) => {
|
|
1252
|
+
const dir = searchDir || homedir();
|
|
1253
|
+
const runFind = (pattern) => {
|
|
1254
|
+
try {
|
|
1255
|
+
const out = execSync(`find "${dir}" -iname "${pattern}" 2>/dev/null`, {
|
|
1256
|
+
encoding: 'utf8',
|
|
1257
|
+
timeout: 15000,
|
|
1258
|
+
});
|
|
1259
|
+
return out.trim().split('\n').filter(Boolean);
|
|
1260
|
+
}
|
|
1261
|
+
catch {
|
|
1262
|
+
return [];
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
// Exact match
|
|
1266
|
+
let files = runFind(fileName);
|
|
1267
|
+
if (files.length > 0) {
|
|
1268
|
+
return {
|
|
1269
|
+
content: [{ type: 'text', text: JSON.stringify({ match: 'exact', files }) }]
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
// Partial match: first half of name without extension
|
|
1273
|
+
const dotIndex = fileName.lastIndexOf('.');
|
|
1274
|
+
const nameOnly = dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
|
|
1275
|
+
const half = nameOnly.slice(0, Math.ceil(nameOnly.length / 2));
|
|
1276
|
+
files = runFind(`*${half}*`);
|
|
1277
|
+
if (files.length > 0) {
|
|
1278
|
+
return {
|
|
1279
|
+
content: [{ type: 'text', text: JSON.stringify({ match: 'partial', keyword: half, files }) }]
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{ type: 'text', text: `No files found matching "${fileName}" or partial keyword "${half}" in ${dir}` }]
|
|
1284
|
+
};
|
|
1285
|
+
});
|
|
1139
1286
|
async function main() {
|
|
1140
1287
|
const transport = new StdioServerTransport();
|
|
1141
1288
|
await server.connect(transport);
|
package/build/tp.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { basename } from "path";
|
|
1
3
|
import { config } from "./config.js";
|
|
2
4
|
export class TpClient {
|
|
3
5
|
baseUrl = config.tp.url;
|
|
@@ -100,11 +102,11 @@ export class TpClient {
|
|
|
100
102
|
});
|
|
101
103
|
return response;
|
|
102
104
|
}
|
|
103
|
-
async createBug({ title, card, bugContent, origin = "Manual QA" }) {
|
|
105
|
+
async createBug({ title, card, bugContent, origin = "Manual QA", projectId, teamId }) {
|
|
104
106
|
const bug = {
|
|
105
107
|
"Name": title,
|
|
106
108
|
"Project": {
|
|
107
|
-
"Id": config.tp.projectId
|
|
109
|
+
"Id": projectId || config.tp.projectId
|
|
108
110
|
},
|
|
109
111
|
"customFields": [{
|
|
110
112
|
"name": "Origin",
|
|
@@ -113,7 +115,7 @@ export class TpClient {
|
|
|
113
115
|
}],
|
|
114
116
|
"assignedTeams": [{
|
|
115
117
|
"team": {
|
|
116
|
-
"id": config.tp.teamId
|
|
118
|
+
"id": teamId || config.tp.teamId
|
|
117
119
|
}
|
|
118
120
|
}],
|
|
119
121
|
"Description": bugContent,
|
|
@@ -128,11 +130,11 @@ export class TpClient {
|
|
|
128
130
|
param: { "format": "json" },
|
|
129
131
|
}, bug);
|
|
130
132
|
}
|
|
131
|
-
async createBugOnly({ title, bugContent, origin = "Manual QA" }) {
|
|
133
|
+
async createBugOnly({ title, bugContent, origin = "Manual QA", projectId, teamId }) {
|
|
132
134
|
const bug = {
|
|
133
135
|
"Name": title,
|
|
134
136
|
"Project": {
|
|
135
|
-
"Id": config.tp.projectId
|
|
137
|
+
"Id": projectId || config.tp.projectId
|
|
136
138
|
},
|
|
137
139
|
"customFields": [{
|
|
138
140
|
"name": "Origin",
|
|
@@ -141,7 +143,7 @@ export class TpClient {
|
|
|
141
143
|
}],
|
|
142
144
|
"assignedTeams": [{
|
|
143
145
|
"team": {
|
|
144
|
-
"id": config.tp.teamId
|
|
146
|
+
"id": teamId || config.tp.teamId
|
|
145
147
|
}
|
|
146
148
|
}],
|
|
147
149
|
"Description": bugContent,
|
|
@@ -151,6 +153,23 @@ export class TpClient {
|
|
|
151
153
|
param: { "format": "json" },
|
|
152
154
|
}, bug);
|
|
153
155
|
}
|
|
156
|
+
async createUserStory({ title, description, featureId, releaseId, projectId, teamId }) {
|
|
157
|
+
const userStory = {
|
|
158
|
+
"Name": title,
|
|
159
|
+
"Project": { "Id": projectId || config.tp.projectId },
|
|
160
|
+
"assignedTeams": [{ "team": { "id": teamId || config.tp.teamId } }],
|
|
161
|
+
};
|
|
162
|
+
if (description)
|
|
163
|
+
userStory["Description"] = description;
|
|
164
|
+
if (featureId)
|
|
165
|
+
userStory["Feature"] = { "Id": featureId };
|
|
166
|
+
if (releaseId)
|
|
167
|
+
userStory["Release"] = { "Id": releaseId };
|
|
168
|
+
return this.post({
|
|
169
|
+
pathParam: { "UserStories": '' },
|
|
170
|
+
param: { "format": "json" },
|
|
171
|
+
}, userStory);
|
|
172
|
+
}
|
|
154
173
|
async createBugBasedOnUserStory(title, userStoryId, bugContent) {
|
|
155
174
|
const bug = {
|
|
156
175
|
"Name": title,
|
|
@@ -457,4 +476,26 @@ export class TpClient {
|
|
|
457
476
|
param: { "format": "json", }
|
|
458
477
|
});
|
|
459
478
|
}
|
|
479
|
+
async addAttachedFile(generalId, filePath) {
|
|
480
|
+
const fileContent = readFileSync(filePath);
|
|
481
|
+
const fileName = basename(filePath);
|
|
482
|
+
const formData = new FormData();
|
|
483
|
+
formData.append("generalId", generalId);
|
|
484
|
+
formData.append("file", new Blob([fileContent]), fileName);
|
|
485
|
+
const url = `${this.baseUrl}/UploadFile.ashx?access_token=${this.token}`;
|
|
486
|
+
try {
|
|
487
|
+
const response = await fetch(url, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
body: formData,
|
|
490
|
+
});
|
|
491
|
+
if (!response.ok) {
|
|
492
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
493
|
+
}
|
|
494
|
+
return response.text();
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
console.error("Error uploading file:", error);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
460
501
|
}
|