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 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
- > requires `TP_PROJECT_ID`, `TP_TEAM_ID`
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
- > requires `TP_PROJECT_ID`, `TP_TEAM_ID`
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 tp.searchContainsDescriptionText({ text: keyword, entityType });
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.Items || [];
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
  }
package/package.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "engines": {
26
26
  "node": ">=20.x"
27
27
  },
28
- "version": "2.1.8",
28
+ "version": "2.2.1",
29
29
  "description": "MCP server for Tartget Process",
30
30
  "main": "build/index.js",
31
31
  "keywords": [