@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.
@@ -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
+ }
@@ -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, null, 2);
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