@testomatio/mcp 2.1.1 → 2.2.0-beta
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 +37 -6
- package/package.json +1 -1
- package/src/api/testomatio-client.js +2 -6
- package/src/cli/main.js +4 -0
- package/src/config/constants.js +4 -0
- package/src/config/load-config.js +10 -1
- package/src/index.js +8 -1
- package/src/mcp/configs/attachments-config.js +7 -0
- package/src/mcp/configs/entity-crud-config.js +0 -6
- package/src/mcp/definitions/attachments.js +70 -0
- package/src/mcp/definitions/issues.js +0 -37
- package/src/mcp/definitions/plans.js +0 -39
- package/src/mcp/definitions/projects.js +12 -0
- package/src/mcp/definitions/requirements.js +0 -27
- package/src/mcp/definitions/runs.js +0 -23
- package/src/mcp/definitions/suites.js +10 -37
- package/src/mcp/definitions/system.js +8 -0
- package/src/mcp/definitions/testruns.js +14 -123
- package/src/mcp/definitions/tests.js +28 -27
- package/src/mcp/definitions/tql-reference.js +31 -51
- package/src/mcp/list-projection.js +140 -0
- package/src/mcp/registry/attachments.js +40 -0
- package/src/mcp/registry/handlers.js +73 -20
- package/src/mcp/registry/issues.js +0 -4
- package/src/mcp/registry/listings.js +0 -37
- package/src/mcp/tool-definitions.js +7 -2
- package/src/mcp/tool-profiles.js +37 -0
- package/src/mcp/tool-registry.js +33 -2
|
@@ -11,14 +11,6 @@ export const listingMethods = {
|
|
|
11
11
|
});
|
|
12
12
|
},
|
|
13
13
|
|
|
14
|
-
searchTests({ page, per_page: perPage, tql } = {}) {
|
|
15
|
-
return this.listTests({
|
|
16
|
-
page,
|
|
17
|
-
per_page: perPage,
|
|
18
|
-
tql,
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
|
|
22
14
|
listSuites({ page, per_page: perPage, file_type: fileType, tag, labels, search_text: searchText } = {}) {
|
|
23
15
|
return this.apiClient.list('suites', {
|
|
24
16
|
page,
|
|
@@ -30,13 +22,6 @@ export const listingMethods = {
|
|
|
30
22
|
});
|
|
31
23
|
},
|
|
32
24
|
|
|
33
|
-
searchSuites({ search_text: searchText, ...rest } = {}) {
|
|
34
|
-
return this.listSuites({
|
|
35
|
-
...rest,
|
|
36
|
-
search_text: searchText,
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
|
|
40
25
|
listRuns({
|
|
41
26
|
page,
|
|
42
27
|
per_page: perPage,
|
|
@@ -49,10 +34,6 @@ export const listingMethods = {
|
|
|
49
34
|
});
|
|
50
35
|
},
|
|
51
36
|
|
|
52
|
-
searchRuns({ page, per_page: perPage, tql } = {}) {
|
|
53
|
-
return this.listRuns({ page, per_page: perPage, tql });
|
|
54
|
-
},
|
|
55
|
-
|
|
56
37
|
listTestruns({
|
|
57
38
|
page,
|
|
58
39
|
per_page: perPage,
|
|
@@ -95,16 +76,6 @@ export const listingMethods = {
|
|
|
95
76
|
});
|
|
96
77
|
},
|
|
97
78
|
|
|
98
|
-
searchTestruns({ page, per_page: perPage, run_id: runId, filter_search: filterSearch, ...rest } = {}) {
|
|
99
|
-
return this.listTestruns({
|
|
100
|
-
page,
|
|
101
|
-
per_page: perPage,
|
|
102
|
-
run_id: runId,
|
|
103
|
-
filter_search: filterSearch,
|
|
104
|
-
...rest,
|
|
105
|
-
});
|
|
106
|
-
},
|
|
107
|
-
|
|
108
79
|
listRungroups({ page, per_page: perPage } = {}) {
|
|
109
80
|
return this.apiClient.list('rungroups', { page, per_page: perPage });
|
|
110
81
|
},
|
|
@@ -132,18 +103,10 @@ export const listingMethods = {
|
|
|
132
103
|
});
|
|
133
104
|
},
|
|
134
105
|
|
|
135
|
-
searchPlans({ page, per_page: perPage, search_text: searchText, ...rest } = {}) {
|
|
136
|
-
return this.listPlans({ page, per_page: perPage, search_text: searchText, ...rest });
|
|
137
|
-
},
|
|
138
|
-
|
|
139
106
|
listRequirements({ page, per_page: perPage, source, scope } = {}) {
|
|
140
107
|
return this.apiClient.list('requirements', { page, per_page: perPage, source, scope });
|
|
141
108
|
},
|
|
142
109
|
|
|
143
|
-
searchRequirements({ page, per_page: perPage, source, scope } = {}) {
|
|
144
|
-
return this.listRequirements({ page, per_page: perPage, source, scope });
|
|
145
|
-
},
|
|
146
|
-
|
|
147
110
|
listMilestones({ page, per_page: perPage, type, status } = {}) {
|
|
148
111
|
return this.apiClient.list('milestones', { page, per_page: perPage, type, status });
|
|
149
112
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SYSTEM_TOOLS } from './definitions/system.js';
|
|
2
|
+
import { PROJECT_TOOLS } from './definitions/projects.js';
|
|
2
3
|
import { TESTS_TOOLS } from './definitions/tests.js';
|
|
3
4
|
import { SUITES_TOOLS } from './definitions/suites.js';
|
|
4
5
|
import { RUNS_TOOLS } from './definitions/runs.js';
|
|
@@ -12,9 +13,12 @@ import { MILESTONES_TOOLS } from './definitions/milestones.js';
|
|
|
12
13
|
import { ISSUES_TOOLS } from './definitions/issues.js';
|
|
13
14
|
import { PLANS_TOOLS } from './definitions/plans.js';
|
|
14
15
|
import { REQUIREMENTS_TOOLS } from './definitions/requirements.js';
|
|
16
|
+
import { ATTACHMENT_TOOLS } from './definitions/attachments.js';
|
|
17
|
+
import { withListOptions } from './list-projection.js';
|
|
15
18
|
|
|
16
|
-
export const TOOL_DEFINITIONS = [
|
|
19
|
+
export const TOOL_DEFINITIONS = withListOptions([
|
|
17
20
|
...SYSTEM_TOOLS,
|
|
21
|
+
...PROJECT_TOOLS,
|
|
18
22
|
...TESTS_TOOLS,
|
|
19
23
|
...SUITES_TOOLS,
|
|
20
24
|
...RUNS_TOOLS,
|
|
@@ -26,6 +30,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
26
30
|
...TAGS_TOOLS,
|
|
27
31
|
...MILESTONES_TOOLS,
|
|
28
32
|
...ISSUES_TOOLS,
|
|
33
|
+
...ATTACHMENT_TOOLS,
|
|
29
34
|
...PLANS_TOOLS,
|
|
30
35
|
...REQUIREMENTS_TOOLS,
|
|
31
|
-
];
|
|
36
|
+
]);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const RARE_ENTITIES = new Set(['steps', 'snippets', 'labels', 'rungroups']);
|
|
2
|
+
|
|
3
|
+
function entityOf(name) {
|
|
4
|
+
if (name === 'system_ping') return 'system';
|
|
5
|
+
return name
|
|
6
|
+
.split('_attachments_')[0]
|
|
7
|
+
.split('_issues_')[0]
|
|
8
|
+
.replace(/_(list|get|create|update|delete|search)$/, '');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isAttachment(name) {
|
|
12
|
+
return name.includes('_attachments_');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isReadOp(name) {
|
|
16
|
+
return name === 'system_ping' || /_(list|get)$/.test(name) || name.endsWith('_issues_list');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether a tool is visible under the given profile. Unknown profiles fall back to full
|
|
21
|
+
*
|
|
22
|
+
* @param {string} name tool name
|
|
23
|
+
* @param {string} profile 'full' | 'core' | 'read'
|
|
24
|
+
*/
|
|
25
|
+
export function isToolInProfile(name, profile) {
|
|
26
|
+
if (!profile || profile === 'full') return true;
|
|
27
|
+
if (isAttachment(name)) return false;
|
|
28
|
+
const coreEntity = !RARE_ENTITIES.has(entityOf(name));
|
|
29
|
+
if (profile === 'core') return coreEntity;
|
|
30
|
+
if (profile === 'read') return coreEntity && isReadOp(name);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function selectTools(allTools, profile) {
|
|
35
|
+
if (!profile || profile === 'full') return allTools;
|
|
36
|
+
return allTools.filter((tool) => tool && isToolInProfile(tool.name, profile));
|
|
37
|
+
}
|
package/src/mcp/tool-registry.js
CHANGED
|
@@ -2,13 +2,41 @@ import { DEFAULT_TOOL_RESPONSE } from '../config/constants.js';
|
|
|
2
2
|
import { ApiError, NotImplementedToolError } from '../core/errors.js';
|
|
3
3
|
import { textResponse } from '../helpers/mcp-response.js';
|
|
4
4
|
import { TOOL_DEFINITIONS } from './tool-definitions.js';
|
|
5
|
+
import { TQL_FULL_REFERENCE } from './definitions/tql-reference.js';
|
|
5
6
|
import { handlerMethods } from './registry/handlers.js';
|
|
7
|
+
import { attachmentMethods } from './registry/attachments.js';
|
|
6
8
|
import { issueMethods } from './registry/issues.js';
|
|
7
9
|
import { listingMethods } from './registry/listings.js';
|
|
8
10
|
import { payloadMethods } from './registry/payloads.js';
|
|
9
11
|
|
|
12
|
+
function withPagination(payload) {
|
|
13
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
14
|
+
return payload;
|
|
15
|
+
}
|
|
16
|
+
const meta = payload.meta;
|
|
17
|
+
if (!Array.isArray(payload.data) || !meta || typeof meta !== 'object') {
|
|
18
|
+
return payload;
|
|
19
|
+
}
|
|
20
|
+
const { total, page, per_page: perPage } = meta;
|
|
21
|
+
if (
|
|
22
|
+
typeof total !== 'number' ||
|
|
23
|
+
typeof page !== 'number' ||
|
|
24
|
+
typeof perPage !== 'number' ||
|
|
25
|
+
perPage <= 0
|
|
26
|
+
) {
|
|
27
|
+
return payload;
|
|
28
|
+
}
|
|
29
|
+
if (page * perPage < total) {
|
|
30
|
+
return {
|
|
31
|
+
...payload,
|
|
32
|
+
_note: `Showing ${payload.data.length} of ${total} (page ${page}). More available — refine the filter or request the next page.`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return payload;
|
|
36
|
+
}
|
|
37
|
+
|
|
10
38
|
function formatJson(payload) {
|
|
11
|
-
return JSON.stringify(payload
|
|
39
|
+
return JSON.stringify(payload);
|
|
12
40
|
}
|
|
13
41
|
|
|
14
42
|
export class ToolRegistry {
|
|
@@ -22,7 +50,7 @@ export class ToolRegistry {
|
|
|
22
50
|
}
|
|
23
51
|
|
|
24
52
|
asText(payload) {
|
|
25
|
-
return textResponse(formatJson(payload));
|
|
53
|
+
return textResponse(formatJson(withPagination(payload)));
|
|
26
54
|
}
|
|
27
55
|
|
|
28
56
|
buildHandlers() {
|
|
@@ -34,10 +62,12 @@ export class ToolRegistry {
|
|
|
34
62
|
baseUrl: this.config.baseUrl,
|
|
35
63
|
apiVersion: 'v2',
|
|
36
64
|
}),
|
|
65
|
+
tql_help: async () => textResponse(TQL_FULL_REFERENCE),
|
|
37
66
|
};
|
|
38
67
|
|
|
39
68
|
this.registerEntityCrudHandlers(handlers);
|
|
40
69
|
this.registerScopedIssueHandlers(handlers);
|
|
70
|
+
this.registerScopedAttachmentHandlers(handlers);
|
|
41
71
|
this.registerGlobalHandlers(handlers);
|
|
42
72
|
for (const registerHandlers of this.handlerRegistrars) {
|
|
43
73
|
registerHandlers.call(this, handlers);
|
|
@@ -80,6 +110,7 @@ export class ToolRegistry {
|
|
|
80
110
|
Object.assign(
|
|
81
111
|
ToolRegistry.prototype,
|
|
82
112
|
handlerMethods,
|
|
113
|
+
attachmentMethods,
|
|
83
114
|
listingMethods,
|
|
84
115
|
issueMethods,
|
|
85
116
|
payloadMethods
|