targetprocess-mcp-server 2.2.0 → 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/build/index.js +88 -0
- package/build/tp.js +24 -0
- package/package.json +1 -1
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";
|
|
@@ -1195,6 +1197,92 @@ server.registerTool('add_test_cases_to_test_plan', {
|
|
|
1195
1197
|
}]
|
|
1196
1198
|
};
|
|
1197
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
|
+
});
|
|
1198
1286
|
async function main() {
|
|
1199
1287
|
const transport = new StdioServerTransport();
|
|
1200
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;
|
|
@@ -474,4 +476,26 @@ export class TpClient {
|
|
|
474
476
|
param: { "format": "json", }
|
|
475
477
|
});
|
|
476
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
|
+
}
|
|
477
501
|
}
|