targetprocess-mcp-server 2.2.2 → 2.2.3
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/build/index.js +25 -55
- package/build/tp.js +14 -5
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
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";
|
|
@@ -1242,23 +1240,43 @@ server.registerTool('add_test_cases_to_test_plan', {
|
|
|
1242
1240
|
});
|
|
1243
1241
|
server.registerTool('add_attached_file_to_card', {
|
|
1244
1242
|
title: 'Attach a file to a TP card',
|
|
1245
|
-
description:
|
|
1243
|
+
description: `Upload and attach a file to a Targetprocess entity (UserStory, Bug, Feature, etc.) by its ID.
|
|
1244
|
+
1) fileContent + fileName — provide base64-encoded file content and a filename (use this when the file was uploaded directly into the chat)
|
|
1245
|
+
WORKFLOW for chat-uploaded files: run \`base64 <path>\` via Bash to get the encoded content, then call this tool with fileContent + fileName.`,
|
|
1246
1246
|
inputSchema: {
|
|
1247
1247
|
generalId: z.string()
|
|
1248
1248
|
.min(5)
|
|
1249
1249
|
.max(6)
|
|
1250
1250
|
.describe('TP entity ID to attach the file to (e.g. 145789)'),
|
|
1251
1251
|
filePath: z.string()
|
|
1252
|
-
.
|
|
1252
|
+
.optional()
|
|
1253
|
+
.describe('Absolute path to a local file (mode 1). Mutually exclusive with fileContent/fileName.'),
|
|
1254
|
+
fileContent: z.string()
|
|
1255
|
+
.optional()
|
|
1256
|
+
.describe('Base64-encoded file content (mode 2). Must be paired with fileName.'),
|
|
1257
|
+
fileName: z.string()
|
|
1258
|
+
.optional()
|
|
1259
|
+
.describe('Filename including extension, required when using fileContent (e.g. screenshot.png).'),
|
|
1253
1260
|
},
|
|
1254
|
-
}, async ({ generalId, filePath }) => {
|
|
1261
|
+
}, async ({ generalId, filePath, fileContent, fileName }) => {
|
|
1255
1262
|
try {
|
|
1256
|
-
|
|
1263
|
+
if (!filePath && (!fileContent || !fileName)) {
|
|
1264
|
+
return {
|
|
1265
|
+
content: [{
|
|
1266
|
+
type: 'text',
|
|
1267
|
+
text: 'Provide either filePath, or both fileContent and fileName.'
|
|
1268
|
+
}]
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const source = filePath
|
|
1272
|
+
? { filePath }
|
|
1273
|
+
: { fileContent: fileContent, fileName: fileName };
|
|
1274
|
+
const result = await tp.addAttachedFile(generalId, source);
|
|
1257
1275
|
if (!result) {
|
|
1258
1276
|
return {
|
|
1259
1277
|
content: [{
|
|
1260
1278
|
type: 'text',
|
|
1261
|
-
text: `Failed to attach file
|
|
1279
|
+
text: `Failed to attach file to card id: ${generalId}`
|
|
1262
1280
|
}]
|
|
1263
1281
|
};
|
|
1264
1282
|
}
|
|
@@ -1278,54 +1296,6 @@ server.registerTool('add_attached_file_to_card', {
|
|
|
1278
1296
|
};
|
|
1279
1297
|
}
|
|
1280
1298
|
});
|
|
1281
|
-
server.registerTool('find_file_on_disk', {
|
|
1282
|
-
title: 'Find a file on disk by name',
|
|
1283
|
-
description: `Search for a file by name on the local filesystem.
|
|
1284
|
-
First tries an exact (case-insensitive) match.
|
|
1285
|
-
If nothing is found, retries using the first half of the filename (without extension) as a wildcard — e.g. "screenshot_bug" becomes "*screensho*".
|
|
1286
|
-
Returns a list of matching absolute paths.`,
|
|
1287
|
-
inputSchema: {
|
|
1288
|
-
fileName: z.string()
|
|
1289
|
-
.describe('Filename to search for, with or without extension (e.g. "screenshot.png" or "report")'),
|
|
1290
|
-
searchDir: z.string()
|
|
1291
|
-
.optional()
|
|
1292
|
-
.describe('Directory to search in — defaults to the user home directory'),
|
|
1293
|
-
},
|
|
1294
|
-
}, async ({ fileName, searchDir }) => {
|
|
1295
|
-
const dir = searchDir || homedir();
|
|
1296
|
-
const runFind = (pattern) => {
|
|
1297
|
-
try {
|
|
1298
|
-
const out = execSync(`find "${dir}" -iname "${pattern}" 2>/dev/null`, {
|
|
1299
|
-
encoding: 'utf8',
|
|
1300
|
-
timeout: 15000,
|
|
1301
|
-
});
|
|
1302
|
-
return out.trim().split('\n').filter(Boolean);
|
|
1303
|
-
}
|
|
1304
|
-
catch {
|
|
1305
|
-
return [];
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
// Exact match
|
|
1309
|
-
let files = runFind(fileName);
|
|
1310
|
-
if (files.length > 0) {
|
|
1311
|
-
return {
|
|
1312
|
-
content: [{ type: 'text', text: JSON.stringify({ match: 'exact', files }) }]
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
// Partial match: first half of name without extension
|
|
1316
|
-
const dotIndex = fileName.lastIndexOf('.');
|
|
1317
|
-
const nameOnly = dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
|
|
1318
|
-
const half = nameOnly.slice(0, Math.ceil(nameOnly.length / 2));
|
|
1319
|
-
files = runFind(`*${half}*`);
|
|
1320
|
-
if (files.length > 0) {
|
|
1321
|
-
return {
|
|
1322
|
-
content: [{ type: 'text', text: JSON.stringify({ match: 'partial', keyword: half, files }) }]
|
|
1323
|
-
};
|
|
1324
|
-
}
|
|
1325
|
-
return {
|
|
1326
|
-
content: [{ type: 'text', text: `No files found matching "${fileName}" or partial keyword "${half}" in ${dir}` }]
|
|
1327
|
-
};
|
|
1328
|
-
});
|
|
1329
1299
|
async function main() {
|
|
1330
1300
|
const transport = new StdioServerTransport();
|
|
1331
1301
|
await server.connect(transport);
|
package/build/tp.js
CHANGED
|
@@ -493,13 +493,22 @@ export class TpClient {
|
|
|
493
493
|
param: { "format": "json", }
|
|
494
494
|
});
|
|
495
495
|
}
|
|
496
|
-
async addAttachedFile(generalId,
|
|
497
|
-
|
|
498
|
-
|
|
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
|
+
}
|
|
499
507
|
const formData = new FormData();
|
|
500
508
|
formData.append("generalId", generalId);
|
|
501
|
-
formData.append("file",
|
|
509
|
+
formData.append("file", blob, fileName);
|
|
502
510
|
const url = `${this.baseUrl}/UploadFile.ashx?access_token=${this.token}`;
|
|
511
|
+
console.error(JSON.stringify({ "UPLOAD_URL": url.replace(this.token, "***") }, null, 2));
|
|
503
512
|
try {
|
|
504
513
|
const response = await fetch(url, {
|
|
505
514
|
method: "POST",
|
|
@@ -508,7 +517,7 @@ export class TpClient {
|
|
|
508
517
|
if (!response.ok) {
|
|
509
518
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
510
519
|
}
|
|
511
|
-
return response.text();
|
|
520
|
+
return await response.text();
|
|
512
521
|
}
|
|
513
522
|
catch (error) {
|
|
514
523
|
console.error("Error uploading file:", error);
|