@testomatio/mcp 2.2.0-beta → 2.2.0-beta.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
@@ -273,6 +273,7 @@ NODE_EXTRA_CA_CERTS=/path/to/company-root-ca.pem testomatio-mcp --token <TOKEN>
273
273
 
274
274
  - **Run Status** - Use `runs_update` with `status_event` for transitions (finish, launch, rerun, etc.)
275
275
  - **Search/Filter** - No dedicated `/search` endpoints; filtering is done via the `*_list` tools (`tql` for tests and runs, OpenAPI-aligned filters for other entities)
276
+ - **Slim List Responses** - List tools request compact API responses by default and omit heavy entity fields and null values. Pass `verbose: true` to return full objects, or `fields: ["id", "title", "description"]` to return only selected fields. Both options disable the backend `slim=true` request so heavy fields remain available when requested.
276
277
  - **TQL** - Use `tql` as the single search/filter input for `tests_list` and `runs_list`
277
278
  - **TQL Syntax** - For user-facing syntax details and more examples, see the official TQL docs: https://docs.testomat.io/advanced/tql/
278
279
  - **TQL Scope** - TQL parameter descriptions keep the documented field whitelist in-band; call `tql_help` for syntax details and additional examples
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/mcp",
3
- "version": "2.2.0-beta",
3
+ "version": "2.2.0-beta.1",
4
4
  "description": "Model Context Protocol server for Testomatio API",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -4,7 +4,7 @@ import { ConfigurationError } from './core/errors.js';
4
4
  import { createLogger } from './core/logger.js';
5
5
  import { TestomatioMCPServer } from './mcp/server.js';
6
6
  import { TOOL_DEFINITIONS } from './mcp/tool-definitions.js';
7
- import { slimList, withListOptions } from './mcp/list-projection.js';
7
+ import { backendSlimQuery, slimList, withListOptions } from './mcp/list-projection.js';
8
8
  import { selectTools } from './mcp/tool-profiles.js';
9
9
  import {
10
10
  ANALYTICS_STATS_TQL_INPUT_DESCRIPTION,
@@ -22,6 +22,7 @@ export {
22
22
  ConfigurationError,
23
23
  TOOL_DEFINITIONS,
24
24
  slimList,
25
+ backendSlimQuery,
25
26
  withListOptions,
26
27
  selectTools,
27
28
  };
@@ -96,6 +96,14 @@ export function slimList(payload, { verbose = false, fields, entity } = {}) {
96
96
  return withViewMarker(applyToEnvelope(payload, (item) => stripHeavy(item, heavyFields)));
97
97
  }
98
98
 
99
+ /**
100
+ * Ask the API to omit heavy list fields only when MCP does not need them for a
101
+ * full or custom projection.
102
+ */
103
+ export function backendSlimQuery({ verbose = false, fields } = {}) {
104
+ return !verbose && !(Array.isArray(fields) && fields.length) ? { slim: true } : {};
105
+ }
106
+
99
107
  const LIST_OPTION_PROPERTIES = {
100
108
  verbose: {
101
109
  type: 'boolean',
@@ -4,9 +4,9 @@ import path from 'node:path';
4
4
  import { ATTACHMENT_RESOURCE_KEYS } from '../configs/attachments-config.js';
5
5
 
6
6
  export const attachmentMethods = {
7
- listAttachmentsForKey({ resourceKey, resourceId }) {
7
+ listAttachmentsForKey({ resourceKey, resourceId, slim }) {
8
8
  this.assertSupportedAttachmentResourceKey(resourceKey);
9
- return this.apiClient.list('attachments', { [resourceKey]: resourceId });
9
+ return this.apiClient.list('attachments', { [resourceKey]: resourceId, slim });
10
10
  },
11
11
 
12
12
  async uploadAttachmentForKey({ resourceKey, resourceId, filePath }) {
@@ -1,7 +1,7 @@
1
1
  import { ENTITY_CRUD_CONFIGS } from '../configs/entity-crud-config.js';
2
2
  import { ATTACHMENT_SCOPED_TOOL_CONFIGS } from '../configs/attachments-config.js';
3
3
  import { ISSUE_SCOPED_TOOL_CONFIGS } from '../configs/issues-config.js';
4
- import { slimList } from '../list-projection.js';
4
+ import { backendSlimQuery, slimList } from '../list-projection.js';
5
5
 
6
6
  export const handlerMethods = {
7
7
  registerEntityCrudHandlers(handlers) {
@@ -10,6 +10,7 @@ export const handlerMethods = {
10
10
 
11
11
  handlers[`${toolPrefix}_list`] = async (args = {}) => {
12
12
  const { verbose, fields, ...listArgs } = args;
13
+ Object.assign(listArgs, backendSlimQuery({ verbose, fields }));
13
14
  return this.asText(
14
15
  slimList(await this[listMethod](listArgs), {
15
16
  verbose,
@@ -43,6 +44,7 @@ export const handlerMethods = {
43
44
  page: args.page,
44
45
  per_page: args.per_page,
45
46
  source: args.source,
47
+ ...backendSlimQuery(args),
46
48
  }),
47
49
  { verbose: args.verbose, fields: args.fields, entity: 'issues' }
48
50
  )
@@ -71,6 +73,7 @@ export const handlerMethods = {
71
73
  await this.listAttachmentsForKey({
72
74
  resourceKey,
73
75
  resourceId: this.pickRequiredArg(args, resourceKey),
76
+ ...backendSlimQuery(args),
74
77
  }),
75
78
  { verbose: args.verbose, fields: args.fields, entity: 'attachments' }
76
79
  )
@@ -100,11 +103,14 @@ export const handlerMethods = {
100
103
  handlers.project_info = async () => this.asText(await this.apiClient.get('info'));
101
104
 
102
105
  handlers.tags_list = async (args = {}) =>
103
- this.asText(slimList(await this.listTags(), { ...args, entity: 'tags' }));
106
+ this.asText(
107
+ slimList(await this.listTags(backendSlimQuery(args)), { ...args, entity: 'tags' })
108
+ );
104
109
  handlers.tags_get = async ({ tag_id: tagId }) => this.asText(await this.getTagByTitle(tagId));
105
110
 
106
111
  handlers.milestones_list = async (args = {}) => {
107
112
  const { verbose, fields, ...listArgs } = args;
113
+ Object.assign(listArgs, backendSlimQuery({ verbose, fields }));
108
114
  return this.asText(
109
115
  slimList(await this.listMilestones(listArgs), { verbose, fields, entity: 'milestones' })
110
116
  );
@@ -114,6 +120,7 @@ export const handlerMethods = {
114
120
 
115
121
  handlers.issues_list = async (args = {}) => {
116
122
  const { verbose, fields, ...listArgs } = args;
123
+ Object.assign(listArgs, backendSlimQuery({ verbose, fields }));
117
124
  return this.asText(
118
125
  slimList(await this.listIssues(listArgs), { verbose, fields, entity: 'issues' })
119
126
  );
@@ -1,13 +1,14 @@
1
1
  import { ISSUE_RESOURCE_KEYS } from '../configs/issues-config.js';
2
2
 
3
3
  export const issueMethods = {
4
- listIssues({ page, per_page: perPage, source, ...resourceQuery } = {}) {
4
+ listIssues({ page, per_page: perPage, source, slim, ...resourceQuery } = {}) {
5
5
  this.validateIssueResourceQuery(resourceQuery, { allowEmpty: true, allowMany: false });
6
6
  return this.listIssuesForResource({
7
7
  ...resourceQuery,
8
8
  page,
9
9
  per_page: perPage,
10
10
  source,
11
+ slim,
11
12
  });
12
13
  },
13
14
 
@@ -20,22 +21,24 @@ export const issueMethods = {
20
21
  });
21
22
  },
22
23
 
23
- listIssuesForResource({ page, per_page: perPage, source, ...resourceQuery } = {}) {
24
+ listIssuesForResource({ page, per_page: perPage, source, slim, ...resourceQuery } = {}) {
24
25
  return this.apiClient.list('issues', {
25
26
  ...resourceQuery,
26
27
  page,
27
28
  per_page: perPage,
28
29
  source,
30
+ slim,
29
31
  });
30
32
  },
31
33
 
32
- listIssuesForKey({ resourceKey, resourceId, page, per_page: perPage, source }) {
34
+ listIssuesForKey({ resourceKey, resourceId, page, per_page: perPage, source, slim }) {
33
35
  this.assertSupportedIssueResourceKey(resourceKey);
34
36
  return this.listIssuesForResource({
35
37
  [resourceKey]: resourceId,
36
38
  page,
37
39
  per_page: perPage,
38
40
  source,
41
+ slim,
39
42
  });
40
43
  },
41
44
 
@@ -3,15 +3,17 @@ export const listingMethods = {
3
3
  page,
4
4
  per_page: perPage,
5
5
  tql,
6
+ slim,
6
7
  } = {}) {
7
8
  return this.apiClient.list('tests', {
8
9
  page,
9
10
  per_page: perPage,
10
11
  tql,
12
+ slim,
11
13
  });
12
14
  },
13
15
 
14
- listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText } = {}) {
16
+ listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText, slim } = {}) {
15
17
  return this.apiClient.list('suites', {
16
18
  page,
17
19
  per_page: perPage,
@@ -19,6 +21,7 @@ export const listingMethods = {
19
21
  tag,
20
22
  labels,
21
23
  search_text: searchText,
24
+ slim,
22
25
  });
23
26
  },
24
27
 
@@ -26,11 +29,13 @@ export const listingMethods = {
26
29
  page,
27
30
  per_page: perPage,
28
31
  tql,
32
+ slim,
29
33
  } = {}) {
30
34
  return this.apiClient.list('runs', {
31
35
  page,
32
36
  per_page: perPage,
33
37
  tql,
38
+ slim,
34
39
  });
35
40
  },
36
41
 
@@ -53,6 +58,7 @@ export const listingMethods = {
53
58
  envs,
54
59
  rungroups,
55
60
  defects,
61
+ slim,
56
62
  } = {}) {
57
63
  return this.apiClient.list('testruns', {
58
64
  page,
@@ -73,26 +79,27 @@ export const listingMethods = {
73
79
  envs: Array.isArray(envs) ? envs.join(',') : envs,
74
80
  rungroups: Array.isArray(rungroups) ? rungroups.join(',') : rungroups,
75
81
  defects,
82
+ slim,
76
83
  });
77
84
  },
78
85
 
79
- listRungroups({ page, per_page: perPage } = {}) {
80
- return this.apiClient.list('rungroups', { page, per_page: perPage });
86
+ listRungroups({ page, per_page: perPage, slim } = {}) {
87
+ return this.apiClient.list('rungroups', { page, per_page: perPage, slim });
81
88
  },
82
89
 
83
- listSteps({ page, per_page: perPage } = {}) {
84
- return this.apiClient.list('steps', { page, per_page: perPage });
90
+ listSteps({ page, per_page: perPage, slim } = {}) {
91
+ return this.apiClient.list('steps', { page, per_page: perPage, slim });
85
92
  },
86
93
 
87
- listSnippets({ page, per_page: perPage } = {}) {
88
- return this.apiClient.list('snippets', { page, per_page: perPage });
94
+ listSnippets({ page, per_page: perPage, slim } = {}) {
95
+ return this.apiClient.list('snippets', { page, per_page: perPage, slim });
89
96
  },
90
97
 
91
- listLabels({ page, per_page: perPage } = {}) {
92
- return this.apiClient.list('labels', { page, per_page: perPage });
98
+ listLabels({ page, per_page: perPage, slim } = {}) {
99
+ return this.apiClient.list('labels', { page, per_page: perPage, slim });
93
100
  },
94
101
 
95
- listPlans({ page, per_page: perPage, kind, hidden, labels, search_text: searchText } = {}) {
102
+ listPlans({ page, per_page: perPage, kind, hidden, labels, search_text: searchText, slim } = {}) {
96
103
  return this.apiClient.list('plans', {
97
104
  page,
98
105
  per_page: perPage,
@@ -100,19 +107,20 @@ export const listingMethods = {
100
107
  hidden,
101
108
  'labels[]': labels,
102
109
  search_text: searchText,
110
+ slim,
103
111
  });
104
112
  },
105
113
 
106
- listRequirements({ page, per_page: perPage, source, scope } = {}) {
107
- return this.apiClient.list('requirements', { page, per_page: perPage, source, scope });
114
+ listRequirements({ page, per_page: perPage, source, scope, slim } = {}) {
115
+ return this.apiClient.list('requirements', { page, per_page: perPage, source, scope, slim });
108
116
  },
109
117
 
110
- listMilestones({ page, per_page: perPage, type, status } = {}) {
111
- return this.apiClient.list('milestones', { page, per_page: perPage, type, status });
118
+ listMilestones({ page, per_page: perPage, type, status, slim } = {}) {
119
+ return this.apiClient.list('milestones', { page, per_page: perPage, type, status, slim });
112
120
  },
113
121
 
114
- listTags() {
115
- return this.apiClient.list('tags');
122
+ listTags({ slim } = {}) {
123
+ return this.apiClient.list('tags', { slim });
116
124
  },
117
125
 
118
126
  getTagByTitle(tagId) {