@testomatio/mcp 2.1.2 → 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.
@@ -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';
@@ -13,9 +14,11 @@ 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';
15
16
  import { ATTACHMENT_TOOLS } from './definitions/attachments.js';
17
+ import { withListOptions } from './list-projection.js';
16
18
 
17
- export const TOOL_DEFINITIONS = [
19
+ export const TOOL_DEFINITIONS = withListOptions([
18
20
  ...SYSTEM_TOOLS,
21
+ ...PROJECT_TOOLS,
19
22
  ...TESTS_TOOLS,
20
23
  ...SUITES_TOOLS,
21
24
  ...RUNS_TOOLS,
@@ -30,4 +33,4 @@ export const TOOL_DEFINITIONS = [
30
33
  ...ATTACHMENT_TOOLS,
31
34
  ...PLANS_TOOLS,
32
35
  ...REQUIREMENTS_TOOLS,
33
- ];
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,14 +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';
6
7
  import { attachmentMethods } from './registry/attachments.js';
7
8
  import { issueMethods } from './registry/issues.js';
8
9
  import { listingMethods } from './registry/listings.js';
9
10
  import { payloadMethods } from './registry/payloads.js';
10
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
+
11
38
  function formatJson(payload) {
12
- return JSON.stringify(payload, null, 2);
39
+ return JSON.stringify(payload);
13
40
  }
14
41
 
15
42
  export class ToolRegistry {
@@ -23,7 +50,7 @@ export class ToolRegistry {
23
50
  }
24
51
 
25
52
  asText(payload) {
26
- return textResponse(formatJson(payload));
53
+ return textResponse(formatJson(withPagination(payload)));
27
54
  }
28
55
 
29
56
  buildHandlers() {
@@ -35,6 +62,7 @@ export class ToolRegistry {
35
62
  baseUrl: this.config.baseUrl,
36
63
  apiVersion: 'v2',
37
64
  }),
65
+ tql_help: async () => textResponse(TQL_FULL_REFERENCE),
38
66
  };
39
67
 
40
68
  this.registerEntityCrudHandlers(handlers);