targetprocess-mcp-server 2.2.1 → 2.2.3-a
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 +3 -0
- package/build/index.js +45 -88
- package/build/tp.js +31 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,9 @@ Cards — Write
|
|
|
65
65
|
- `create_user_story` — Create a new user story (title, optional description, optional featureId, optional releaseId, optional projectId, optional teamId)
|
|
66
66
|
> [!NOTE]
|
|
67
67
|
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
68
|
+
- `create_feature` — Create a new feature (title, optional description, optional epicId, optional releaseId, optional projectId, optional teamId)
|
|
69
|
+
> [!NOTE]
|
|
70
|
+
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
68
71
|
- `create_test_plan` — Create a test plan linked to a UserStory, Bug, or Feature (title, resourceId, optional resourceType, optional description/startDate/endDate)
|
|
69
72
|
> [!NOTE]
|
|
70
73
|
> requires `TP_PROJECT_ID`,
|
package/build/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from "child_process";
|
|
3
|
-
import { homedir } from "os";
|
|
4
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
3
|
import { z } from "zod";
|
|
6
4
|
import { JSDOM } from "jsdom";
|
|
7
5
|
import { TpClient } from "./tp.js";
|
|
8
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { config } from "./config.js";
|
|
9
8
|
const server = new McpServer({
|
|
10
9
|
name: "tp",
|
|
11
10
|
version: "1.0.0"
|
|
@@ -376,6 +375,7 @@ server.registerTool('search_tp_cards', {
|
|
|
376
375
|
title: item.Name,
|
|
377
376
|
id: item.Id,
|
|
378
377
|
description: descriptionText,
|
|
378
|
+
url: `${config.tp.url}/entity/${item.Id}`,
|
|
379
379
|
};
|
|
380
380
|
});
|
|
381
381
|
return {
|
|
@@ -751,6 +751,49 @@ server.registerTool('create_user_story', {
|
|
|
751
751
|
}],
|
|
752
752
|
};
|
|
753
753
|
});
|
|
754
|
+
server.registerTool('create_feature', {
|
|
755
|
+
title: 'Create a new feature',
|
|
756
|
+
description: `Create a new Feature in Targetprocess.`,
|
|
757
|
+
inputSchema: {
|
|
758
|
+
title: z.string()
|
|
759
|
+
.describe('Feature title'),
|
|
760
|
+
description: z.string()
|
|
761
|
+
.optional()
|
|
762
|
+
.describe('Optional feature description (when provided, format as HTML)'),
|
|
763
|
+
epicId: z.string()
|
|
764
|
+
.min(5)
|
|
765
|
+
.max(6)
|
|
766
|
+
.optional()
|
|
767
|
+
.describe('Optional Epic ID to link this feature to (e.g. 145636)'),
|
|
768
|
+
releaseId: z.string()
|
|
769
|
+
.min(5)
|
|
770
|
+
.max(6)
|
|
771
|
+
.optional()
|
|
772
|
+
.describe('Optional Release ID to link this feature to (e.g. 145200)'),
|
|
773
|
+
projectId: z.string()
|
|
774
|
+
.optional()
|
|
775
|
+
.describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
|
|
776
|
+
teamId: z.string()
|
|
777
|
+
.optional()
|
|
778
|
+
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
779
|
+
},
|
|
780
|
+
}, async ({ title, description, epicId, releaseId, projectId, teamId }) => {
|
|
781
|
+
const featureResponse = await tp.createFeature({ title, description, epicId, releaseId, projectId, teamId });
|
|
782
|
+
if (!featureResponse) {
|
|
783
|
+
return {
|
|
784
|
+
content: [{
|
|
785
|
+
type: 'text',
|
|
786
|
+
text: `Failed to create feature "${title}"\n JSON: ${JSON.stringify(featureResponse, null, 2)}`
|
|
787
|
+
}]
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
content: [{
|
|
792
|
+
type: 'text',
|
|
793
|
+
text: JSON.stringify(featureResponse)
|
|
794
|
+
}],
|
|
795
|
+
};
|
|
796
|
+
});
|
|
754
797
|
server.registerTool('create_test_plan', {
|
|
755
798
|
title: 'Create a new test plan linked to a TP card',
|
|
756
799
|
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.`,
|
|
@@ -1197,92 +1240,6 @@ server.registerTool('add_test_cases_to_test_plan', {
|
|
|
1197
1240
|
}]
|
|
1198
1241
|
};
|
|
1199
1242
|
});
|
|
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
|
-
});
|
|
1286
1243
|
async function main() {
|
|
1287
1244
|
const transport = new StdioServerTransport();
|
|
1288
1245
|
await server.connect(transport);
|
package/build/tp.js
CHANGED
|
@@ -170,6 +170,23 @@ export class TpClient {
|
|
|
170
170
|
param: { "format": "json" },
|
|
171
171
|
}, userStory);
|
|
172
172
|
}
|
|
173
|
+
async createFeature({ title, description, epicId, releaseId, projectId, teamId }) {
|
|
174
|
+
const feature = {
|
|
175
|
+
"Name": title,
|
|
176
|
+
"Project": { "Id": projectId || config.tp.projectId },
|
|
177
|
+
"assignedTeams": [{ "team": { "id": teamId || config.tp.teamId } }],
|
|
178
|
+
};
|
|
179
|
+
if (description)
|
|
180
|
+
feature["Description"] = description;
|
|
181
|
+
if (epicId)
|
|
182
|
+
feature["Epic"] = { "Id": epicId };
|
|
183
|
+
if (releaseId)
|
|
184
|
+
feature["Release"] = { "Id": releaseId };
|
|
185
|
+
return this.post({
|
|
186
|
+
pathParam: { "Features": '' },
|
|
187
|
+
param: { "format": "json" },
|
|
188
|
+
}, feature);
|
|
189
|
+
}
|
|
173
190
|
async createBugBasedOnUserStory(title, userStoryId, bugContent) {
|
|
174
191
|
const bug = {
|
|
175
192
|
"Name": title,
|
|
@@ -476,13 +493,22 @@ export class TpClient {
|
|
|
476
493
|
param: { "format": "json", }
|
|
477
494
|
});
|
|
478
495
|
}
|
|
479
|
-
async addAttachedFile(generalId,
|
|
480
|
-
|
|
481
|
-
|
|
496
|
+
async addAttachedFile(generalId, source) {
|
|
497
|
+
let blob;
|
|
498
|
+
let fileName;
|
|
499
|
+
if ("filePath" in source) {
|
|
500
|
+
blob = new Blob([readFileSync(source.filePath)]);
|
|
501
|
+
fileName = basename(source.filePath);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
blob = new Blob([Buffer.from(source.fileContent, "base64")]);
|
|
505
|
+
fileName = source.fileName;
|
|
506
|
+
}
|
|
482
507
|
const formData = new FormData();
|
|
483
508
|
formData.append("generalId", generalId);
|
|
484
|
-
formData.append("file",
|
|
509
|
+
formData.append("file", blob, fileName);
|
|
485
510
|
const url = `${this.baseUrl}/UploadFile.ashx?access_token=${this.token}`;
|
|
511
|
+
console.error(JSON.stringify({ "UPLOAD_URL": url.replace(this.token, "***") }, null, 2));
|
|
486
512
|
try {
|
|
487
513
|
const response = await fetch(url, {
|
|
488
514
|
method: "POST",
|
|
@@ -491,7 +517,7 @@ export class TpClient {
|
|
|
491
517
|
if (!response.ok) {
|
|
492
518
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
493
519
|
}
|
|
494
|
-
return response.text();
|
|
520
|
+
return await response.text();
|
|
495
521
|
}
|
|
496
522
|
catch (error) {
|
|
497
523
|
console.error("Error uploading file:", error);
|