targetprocess-mcp-server 2.2.3 → 2.2.4

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
@@ -46,6 +46,7 @@ Features
46
46
  - `get_not_covered_user_stories_in_feature` — Get user stories in a feature not yet covered by tests, includes `covered` field based on "Test Automation" custom field (id)
47
47
 
48
48
  Cards — Read
49
+ - `get_card_status` — Get EntityState, TeamState, and assigned teams for a card (id, optional resourceType: UserStory | Bug | Feature, default: UserStory)
49
50
  - `get_bug_content` — Fetch full content of a bug by ID (id)
50
51
  - `get_user_story_content` — Fetch full content of a user story by ID (id)
51
52
  - `get_bug_comments` — Get comments on a bug (id, optional results)
package/build/index.js CHANGED
@@ -4,6 +4,7 @@ import { z } from "zod";
4
4
  import { JSDOM } from "jsdom";
5
5
  import { TpClient } from "./tp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
+ import { config } from "./config.js";
7
8
  const server = new McpServer({
8
9
  name: "tp",
9
10
  version: "1.0.0"
@@ -374,6 +375,7 @@ server.registerTool('search_tp_cards', {
374
375
  title: item.Name,
375
376
  id: item.Id,
376
377
  description: descriptionText,
378
+ url: `${config.tp.url}/entity/${item.Id}`,
377
379
  };
378
380
  });
379
381
  return {
@@ -598,11 +600,13 @@ server.registerTool('create_bug_based_on_card', {
598
600
  title: 'Create a new bug card based on provided card id',
599
601
  description: `Create a new bug card based on provided card id that summarizes the problem in concise, descriptive manner answering questions What? Where? When?, and content explaining what happened in detail.
600
602
  NOTE: this tool requires a user story or bug card as a reference (i.e. card ID).
601
- CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
603
+ CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
602
604
  1) IF you already have user story or bug card content, proceed to step 3 skipping step 2;
603
605
  2) ELSE call "get_user_story_content" tool or "get_bug_content" tool to get user story or bug card content;
604
606
  3) format the new bug inside html <div> tags with Issue Description, Steps to Reproduce, Expected Behavior, Actual Behavior sections (note: section titles should be wrapped in <h3> tags, e.g. <h3>Issue Description</h3>);
605
- 4) add a comment to the card with created bug Id and its Title`,
607
+ 4) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
608
+ 5) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
609
+ 6) add a comment to the card with created bug Id and its Title`,
606
610
  inputSchema: {
607
611
  title: z.string()
608
612
  .describe('Bug card title that summarizes the problem in concise, descriptive, and actionable manner, enabling a developer to understand the issue without opening the report'),
@@ -635,10 +639,10 @@ server.registerTool('create_bug_based_on_card', {
635
639
  .describe('Where the bug was found, defaults to "Manual QA"'),
636
640
  projectId: z.string()
637
641
  .optional()
638
- .describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
642
+ .describe('Optional Project ID — if user gave a project name, resolve it via "get_projects" first; defaults to TP_PROJECT_ID from config'),
639
643
  teamId: z.string()
640
644
  .optional()
641
- .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
645
+ .describe('Optional Team ID — if user gave a team name, resolve it via "get_teams" first; defaults to TP_TEAM_ID from config'),
642
646
  },
643
647
  }, async ({ title, card, bugContent, origin, projectId, teamId }) => {
644
648
  const bugResponse = await tp.createBug({ title, card, bugContent, origin, projectId, teamId });
@@ -662,7 +666,9 @@ server.registerTool('create_bug', {
662
666
  description: `Create a new bug card that summarizes the problem in concise, descriptive manner answering questions "What? Where? When?" and content explaining what happened in detail.
663
667
  NOTE: this tool does not require a user story or bug card reference.
664
668
  CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
665
- 1) format the new bug inside html <div> tags with Issue Description, Steps to Reproduce, Expected Behavior, Actual Behavior sections (note: section titles should be wrapped in <h3> tags, e.g. <h3>Issue Description</h3>);`,
669
+ 1) format the new bug inside html <div> tags with Issue Description, Steps to Reproduce, Expected Behavior, Actual Behavior sections (note: section titles should be wrapped in <h3> tags, e.g. <h3>Issue Description</h3>, step to reproduce should be wrapped in <ol>);
670
+ 2) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
671
+ 3) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;`,
666
672
  inputSchema: {
667
673
  title: z.string()
668
674
  .describe('Bug card title that summarizes the problem in concise, descriptive, and actionable manner, enabling a developer to understand the issue without opening the report'),
@@ -684,10 +690,10 @@ server.registerTool('create_bug', {
684
690
  .describe('Where the bug was found, defaults to "Manual QA"'),
685
691
  projectId: z.string()
686
692
  .optional()
687
- .describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
693
+ .describe('Optional Project ID — if user gave a project name, resolve it via "get_projects" first; defaults to TP_PROJECT_ID from config'),
688
694
  teamId: z.string()
689
695
  .optional()
690
- .describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
696
+ .describe('Optional Team ID — if user gave a team name, resolve it via "get_teams" first; defaults to TP_TEAM_ID from config'),
691
697
  },
692
698
  }, async ({ title, bugContent, origin, projectId, teamId }) => {
693
699
  const bugResponse = await tp.createBugOnly({ title, bugContent, origin, projectId, teamId });
@@ -1238,63 +1244,44 @@ server.registerTool('add_test_cases_to_test_plan', {
1238
1244
  }]
1239
1245
  };
1240
1246
  });
1241
- server.registerTool('add_attached_file_to_card', {
1242
- title: 'Attach a file to a TP card',
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.`,
1247
+ server.registerTool('get_card_status', {
1248
+ title: 'Get card status',
1249
+ description: 'Get the EntityState, TeamState, and assigned teams for a TP card (UserStory, Bug, or Feature) by ID',
1246
1250
  inputSchema: {
1247
- generalId: z.string()
1251
+ id: z.string()
1248
1252
  .min(5)
1249
1253
  .max(6)
1250
- .describe('TP entity ID to attach the file to (e.g. 145789)'),
1251
- filePath: z.string()
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()
1254
+ .describe('TP card ID (e.g. 146055)'),
1255
+ resourceType: z.enum(['UserStory', 'Bug', 'Feature'])
1256
+ .default('UserStory')
1258
1257
  .optional()
1259
- .describe('Filename including extension, required when using fileContent (e.g. screenshot.png).'),
1258
+ .describe('Type of the TP card — UserStory, Bug, or Feature (default: UserStory)'),
1260
1259
  },
1261
- }, async ({ generalId, filePath, fileContent, fileName }) => {
1262
- try {
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);
1275
- if (!result) {
1276
- return {
1277
- content: [{
1278
- type: 'text',
1279
- text: `Failed to attach file to card id: ${generalId}`
1280
- }]
1281
- };
1282
- }
1260
+ }, async ({ id, resourceType = 'UserStory' }) => {
1261
+ const response = await tp.getCardStatus(id, resourceType);
1262
+ if (!response) {
1283
1263
  return {
1284
1264
  content: [{
1285
1265
  type: 'text',
1286
- text: result
1287
- }]
1266
+ text: `Failed to get card status for ${resourceType} id: ${id}`
1267
+ }],
1288
1268
  };
1289
1269
  }
1290
- catch (error) {
1270
+ const items = response.items || [];
1271
+ if (items.length === 0) {
1291
1272
  return {
1292
1273
  content: [{
1293
1274
  type: 'text',
1294
- text: `Error attaching file to card ${generalId}: ${error}`
1295
- }]
1275
+ text: `No status data found for ${resourceType} id: ${id}`,
1276
+ }],
1296
1277
  };
1297
1278
  }
1279
+ return {
1280
+ content: [{
1281
+ type: 'text',
1282
+ text: JSON.stringify(items[0])
1283
+ }],
1284
+ };
1298
1285
  });
1299
1286
  async function main() {
1300
1287
  const transport = new StdioServerTransport();
package/build/tp.js CHANGED
@@ -15,8 +15,8 @@ export class TpClient {
15
15
  }
16
16
  params(params) {
17
17
  let _url = this.baseUrl + (params.apiVersion || this.v1);
18
- for (const [key, value] of Object.entries(params.pathParam)) {
19
- _url += value ? `/${key}/${value}` : `/${key}`;
18
+ for (const segment of params.pathParam) {
19
+ _url += `/${segment}`;
20
20
  }
21
21
  let _urlParams = [];
22
22
  for (const [key, value] of Object.entries(params.param)) {
@@ -83,21 +83,21 @@ export class TpClient {
83
83
  }
84
84
  async getUserStory(userStoryId) {
85
85
  const response = await this.get({
86
- pathParam: { "userStories": userStoryId },
86
+ pathParam: ["userStories", userStoryId],
87
87
  param: { "format": "json" },
88
88
  });
89
89
  return response;
90
90
  }
91
91
  async getBug(bugId) {
92
92
  const response = await this.get({
93
- pathParam: { "bugs": bugId },
93
+ pathParam: ["bugs", bugId],
94
94
  param: { "format": "json" }
95
95
  });
96
96
  return response;
97
97
  }
98
98
  async getFeature(featureId) {
99
99
  const response = await this.get({
100
- pathParam: { "features": featureId },
100
+ pathParam: ["features", featureId],
101
101
  param: { "format": "json" }
102
102
  });
103
103
  return response;
@@ -126,7 +126,7 @@ export class TpClient {
126
126
  };
127
127
  }
128
128
  return this.post({
129
- pathParam: { "bugs": '' },
129
+ pathParam: ["bugs"],
130
130
  param: { "format": "json" },
131
131
  }, bug);
132
132
  }
@@ -149,7 +149,7 @@ export class TpClient {
149
149
  "Description": bugContent,
150
150
  };
151
151
  return this.post({
152
- pathParam: { "bugs": '' },
152
+ pathParam: ["bugs"],
153
153
  param: { "format": "json" },
154
154
  }, bug);
155
155
  }
@@ -166,7 +166,7 @@ export class TpClient {
166
166
  if (releaseId)
167
167
  userStory["Release"] = { "Id": releaseId };
168
168
  return this.post({
169
- pathParam: { "UserStories": '' },
169
+ pathParam: ["UserStories"],
170
170
  param: { "format": "json" },
171
171
  }, userStory);
172
172
  }
@@ -183,7 +183,7 @@ export class TpClient {
183
183
  if (releaseId)
184
184
  feature["Release"] = { "Id": releaseId };
185
185
  return this.post({
186
- pathParam: { "Features": '' },
186
+ pathParam: ["Features"],
187
187
  param: { "format": "json" },
188
188
  }, feature);
189
189
  }
@@ -209,7 +209,7 @@ export class TpClient {
209
209
  "Description": bugContent,
210
210
  };
211
211
  return this.post({
212
- pathParam: { "bugs": '' },
212
+ pathParam: ["bugs"],
213
213
  param: { "format": "json" },
214
214
  }, bug);
215
215
  }
@@ -223,7 +223,7 @@ export class TpClient {
223
223
  }],
224
224
  };
225
225
  return this.post({
226
- pathParam: { "testCases": '' },
226
+ pathParam: ["testCases"],
227
227
  param: { "format": "json" },
228
228
  }, testCase);
229
229
  }
@@ -260,7 +260,7 @@ export class TpClient {
260
260
  if (options?.endDate)
261
261
  testPlan["EndDate"] = options.endDate;
262
262
  return this.post({
263
- pathParam: { "testPlans": '' },
263
+ pathParam: ["testPlans"],
264
264
  param: { "format": "json" },
265
265
  }, testPlan);
266
266
  }
@@ -275,7 +275,7 @@ export class TpClient {
275
275
  },
276
276
  };
277
277
  return this.post({
278
- pathParam: { "comments": '' },
278
+ pathParam: ["comments"],
279
279
  param: { "format": "json" },
280
280
  }, commentData);
281
281
  }
@@ -286,16 +286,13 @@ export class TpClient {
286
286
  "TestCase": { "Id": testCaseId },
287
287
  };
288
288
  return this.post({
289
- pathParam: { "testSteps": '' },
289
+ pathParam: ["testSteps"],
290
290
  param: { "format": "json" },
291
291
  }, testStepData);
292
292
  }
293
293
  async getBugComments(bugId, results = 25) {
294
294
  const response = await this.get({
295
- pathParam: {
296
- "Bugs": bugId,
297
- "Comments": "",
298
- },
295
+ pathParam: ["Bugs", bugId, "Comments"],
299
296
  param: {
300
297
  "format": "json",
301
298
  "take": results,
@@ -305,10 +302,7 @@ export class TpClient {
305
302
  }
306
303
  async getUserStoryComments(userStoryId, results = 25) {
307
304
  const response = await this.get({
308
- pathParam: {
309
- "UserStories": userStoryId,
310
- "Comments": "",
311
- },
305
+ pathParam: ["UserStories", userStoryId, "Comments"],
312
306
  param: {
313
307
  "format": "json",
314
308
  "take": results,
@@ -318,7 +312,7 @@ export class TpClient {
318
312
  }
319
313
  async searchContainsNameText({ text, entityType }) {
320
314
  return this.get({
321
- pathParam: { [entityType]: '' },
315
+ pathParam: [entityType],
322
316
  param: {
323
317
  "format": "json",
324
318
  "take": "25",
@@ -329,7 +323,7 @@ export class TpClient {
329
323
  }
330
324
  async searchContainsDescriptionText({ text, entityType }) {
331
325
  return this.get({
332
- pathParam: { [entityType]: '' },
326
+ pathParam: [entityType],
333
327
  param: {
334
328
  "where": `Description contains '${text}' and EntityState.Name eq 'Done'`,
335
329
  "format": "json",
@@ -339,7 +333,7 @@ export class TpClient {
339
333
  }
340
334
  async getCurrentReleases() {
341
335
  return this.get({
342
- pathParam: { "Releases": '' },
336
+ pathParam: ["Releases"],
343
337
  param: {
344
338
  "format": "json",
345
339
  "where": `IsCurrent eq 'true'`,
@@ -349,7 +343,7 @@ export class TpClient {
349
343
  async getReleaseUserStories({ name, results = 50, withDescription = false }) {
350
344
  const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
351
345
  return this.get({
352
- pathParam: { "UserStories": '' },
346
+ pathParam: ["UserStories"],
353
347
  param: {
354
348
  "format": "json",
355
349
  "take": results,
@@ -361,11 +355,11 @@ export class TpClient {
361
355
  async getReleaseOpenUserStories({ name, results = 100, withDescription = false }) {
362
356
  const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
363
357
  return this.get({
364
- pathParam: { "UserStories": '' },
358
+ pathParam: ["UserStories"],
365
359
  param: {
366
360
  "format": "json",
367
361
  "take": results,
368
- "where": `Release.Name eq '${name}' and EntityState.Name ne 'Closed' and EntityState.Name ne 'Done' and EntityState.Name ne 'Passed Dev01  QA' and EntityState.Name ne 'Ready to Deploy to prod'`,
362
+ "where": `Release.Name eq '${name}' and EntityState.Name ne 'Closed' and EntityState.Name ne 'Done' and EntityState.Name ne 'Passed Dev01 QA' and EntityState.Name ne 'Ready to Deploy to prod'`,
369
363
  "include": includeFilter,
370
364
  }
371
365
  });
@@ -373,11 +367,11 @@ export class TpClient {
373
367
  async getReleaseOpenBugs({ name, results = 200, withDescription = false }) {
374
368
  const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
375
369
  return this.get({
376
- pathParam: { "Bugs": '' },
370
+ pathParam: ["Bugs"],
377
371
  param: {
378
372
  "format": "json",
379
373
  "take": results,
380
- "where": `Release.Name eq '${name}' and EntityState.Name ne 'Closed' and EntityState.Name ne 'Done' and EntityState.Name ne 'Passed Dev01  QA' and EntityState.Name ne 'Ready to Deploy to prod'`,
374
+ "where": `Release.Name eq '${name}' and EntityState.Name ne 'Closed' and EntityState.Name ne 'Done' and EntityState.Name ne 'Passed Dev01 QA' and EntityState.Name ne 'Ready to Deploy to prod'`,
381
375
  "include": includeFilter,
382
376
  }
383
377
  });
@@ -385,7 +379,7 @@ export class TpClient {
385
379
  async getReleaseBugs({ name, results = 100, withDescription = false }) {
386
380
  const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
387
381
  return this.get({
388
- pathParam: { "Bugs": '' },
382
+ pathParam: ["Bugs"],
389
383
  param: {
390
384
  "format": "json",
391
385
  "take": results,
@@ -397,7 +391,7 @@ export class TpClient {
397
391
  async getReleaseFeatures({ name, results = 50, withDescription = false }) {
398
392
  const includeFilter = withDescription ? "[Name, Description, Id]" : "[Name, Id]";
399
393
  return this.get({
400
- pathParam: { "Features": '' },
394
+ pathParam: ["Features"],
401
395
  param: {
402
396
  "format": "json",
403
397
  "take": results,
@@ -408,7 +402,7 @@ export class TpClient {
408
402
  }
409
403
  async getFeatureUserStories(featureId) {
410
404
  return this.get({
411
- pathParam: { "features": '' },
405
+ pathParam: ["features"],
412
406
  param: {
413
407
  "format": "json",
414
408
  "where": `(id==${featureId})`,
@@ -419,7 +413,7 @@ export class TpClient {
419
413
  }
420
414
  async getUserStoriesIdsByFeatureId(featureId) {
421
415
  return this.get({
422
- pathParam: { "userstories": '' },
416
+ pathParam: ["userstories"],
423
417
  param: {
424
418
  "format": "json",
425
419
  "where": `(Feature.Id==${featureId})`,
@@ -430,7 +424,7 @@ export class TpClient {
430
424
  }
431
425
  async getUserStoryTestPlan(userStoryId) {
432
426
  return this.get({
433
- pathParam: { "userStories": userStoryId },
427
+ pathParam: ["userStories", userStoryId],
434
428
  param: {
435
429
  "format": "json",
436
430
  "select": `{id,storyName:name,linkedtestplan}`,
@@ -439,18 +433,9 @@ export class TpClient {
439
433
  });
440
434
  }
441
435
  async getCardTestPlan(cardId, resourceType = 'UserStory') {
442
- let requestPath = "";
443
- if (resourceType === 'UserStory') {
444
- requestPath = "userStories";
445
- }
446
- else if (resourceType === 'Bug') {
447
- requestPath = "bugs";
448
- }
449
- else if (resourceType === 'Feature') {
450
- requestPath = "features";
451
- }
436
+ const pathMap = { UserStory: "userStories", Bug: "bugs", Feature: "features" };
452
437
  return this.get({
453
- pathParam: { [requestPath]: cardId },
438
+ pathParam: [pathMap[resourceType], cardId],
454
439
  param: {
455
440
  "format": "json",
456
441
  "select": `{id,linkedtestplan}`,
@@ -459,38 +444,44 @@ export class TpClient {
459
444
  }
460
445
  async getTestPlanTestCases(testPlanId) {
461
446
  return this.get({
462
- pathParam: {
463
- "testPlans": testPlanId,
464
- "testcases": "",
465
- },
447
+ pathParam: ["testPlans", testPlanId, "testcases"],
466
448
  param: { "format": "json" },
467
449
  });
468
450
  }
469
451
  async getTestCaseSteps(testCaseId) {
470
452
  return this.get({
471
- pathParam: {
472
- "testCases": testCaseId,
473
- "teststeps": "",
474
- },
475
- param: { "format": "json", },
453
+ pathParam: ["testCases", testCaseId, "teststeps"],
454
+ param: { "format": "json" },
476
455
  });
477
456
  }
478
457
  async getProjects() {
479
458
  return this.get({
480
- pathParam: { "Projects": '' },
459
+ pathParam: ["Projects"],
481
460
  param: { "format": "json" },
482
461
  });
483
462
  }
484
463
  async getTeams() {
485
464
  return this.get({
486
- pathParam: { "Teams": '' },
465
+ pathParam: ["Teams"],
487
466
  param: { "format": "json" },
488
467
  });
489
468
  }
469
+ async getCardStatus(cardId, resourceType = 'UserStory') {
470
+ const pathMap = { UserStory: 'userStory', Bug: 'bug', Feature: 'feature' };
471
+ return this.get({
472
+ pathParam: [pathMap[resourceType]],
473
+ param: {
474
+ "select": `{Project:{Project.Id},EntityState:{EntityState.Id,EntityState.Name,EntityState.NextStates,EntityState.Workflow.Id as WorkflowId},TeamState:{ResponsibleTeam.Id,Team:{ResponsibleTeam.Team.Id,ResponsibleTeam.Team.Name},EntityState:{ResponsibleTeam.EntityState.Id,ResponsibleTeam.EntityState.Name,ResponsibleTeam.EntityState.Workflow.Id as WorkflowId}},AssignedTeams.Select({TeamAssignmentId:Id,Id:Team.Id,Name:Team.Name}) as Teams}`,
475
+ "where": `(id=${cardId})`,
476
+ "take": "1",
477
+ },
478
+ apiVersion: this.v2
479
+ });
480
+ }
490
481
  async getContext() {
491
482
  return this.get({
492
- pathParam: { "Context": '' },
493
- param: { "format": "json", }
483
+ pathParam: ["Context"],
484
+ param: { "format": "json" }
494
485
  });
495
486
  }
496
487
  async addAttachedFile(generalId, source) {
package/package.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "engines": {
26
26
  "node": ">=20.x"
27
27
  },
28
- "version": "2.2.3",
28
+ "version": "2.2.4",
29
29
  "description": "MCP server for Tartget Process",
30
30
  "main": "build/index.js",
31
31
  "keywords": [