@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
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(
|
|
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) {
|