@salesforce/mcp 0.9.0 → 0.10.0
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/lib/index.js +2 -2
- package/lib/shared/auth.js +0 -5
- package/lib/tools/testing/sf-test-agents.d.ts +4 -1
- package/lib/tools/testing/sf-test-agents.js +17 -6
- package/lib/tools/testing/sf-test-apex.d.ts +22 -4
- package/lib/tools/testing/sf-test-apex.js +55 -9
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -167,8 +167,8 @@ You can also use special values to control access to orgs:
|
|
|
167
167
|
// ************************
|
|
168
168
|
if (all || enabledToolsets.has('testing')) {
|
|
169
169
|
this.logToStderr('Registering testing tools');
|
|
170
|
-
testing.
|
|
171
|
-
testing.
|
|
170
|
+
testing.registerToolTestApex(server);
|
|
171
|
+
testing.registerToolTestAgent(server);
|
|
172
172
|
}
|
|
173
173
|
// ************************
|
|
174
174
|
// METADATA TOOLS
|
package/lib/shared/auth.js
CHANGED
|
@@ -102,11 +102,6 @@ export async function getAllAllowedOrgs() {
|
|
|
102
102
|
const sanitizedOrgs = sanitizeOrgs(allOrgs);
|
|
103
103
|
// Filter out orgs that are not in ORG_ALLOWLIST
|
|
104
104
|
const allowedOrgs = await filterAllowedOrgs(sanitizedOrgs, orgAllowList);
|
|
105
|
-
// If no orgs are found, stop the server
|
|
106
|
-
if (allowedOrgs.length === 0) {
|
|
107
|
-
console.error('No orgs found that match the allowed orgs configuration. Check MCP Server startup config.');
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
105
|
return allowedOrgs;
|
|
111
106
|
}
|
|
112
107
|
// Function to filter orgs based on ORG_ALLOWLIST configuration
|
|
@@ -4,7 +4,9 @@ declare const runAgentTestsParam: z.ZodObject<{
|
|
|
4
4
|
agentApiName: z.ZodString;
|
|
5
5
|
usernameOrAlias: z.ZodString;
|
|
6
6
|
directory: z.ZodEffects<z.ZodString, string, string>;
|
|
7
|
+
async: z.ZodDefault<z.ZodBoolean>;
|
|
7
8
|
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
async: boolean;
|
|
8
10
|
directory: string;
|
|
9
11
|
usernameOrAlias: string;
|
|
10
12
|
agentApiName: string;
|
|
@@ -12,7 +14,8 @@ declare const runAgentTestsParam: z.ZodObject<{
|
|
|
12
14
|
directory: string;
|
|
13
15
|
usernameOrAlias: string;
|
|
14
16
|
agentApiName: string;
|
|
17
|
+
async?: boolean | undefined;
|
|
15
18
|
}>;
|
|
16
19
|
export type AgentRunTests = z.infer<typeof runAgentTestsParam>;
|
|
17
|
-
export declare const
|
|
20
|
+
export declare const registerToolTestAgent: (server: SfMcpServer) => void;
|
|
18
21
|
export {};
|
|
@@ -21,11 +21,15 @@ import { textResponse } from '../../shared/utils.js';
|
|
|
21
21
|
import { getConnection } from '../../shared/auth.js';
|
|
22
22
|
const runAgentTestsParam = z.object({
|
|
23
23
|
agentApiName: z.string().describe(`Agent test to run
|
|
24
|
-
if unsure, list all files matching the pattern
|
|
24
|
+
if unsure, list all files matching the pattern **/aiEvaluationDefinitions/*.aiEvaluationDefinition-meta.xml
|
|
25
25
|
only one test can be executed at a time
|
|
26
26
|
`),
|
|
27
27
|
usernameOrAlias: usernameOrAliasParam,
|
|
28
28
|
directory: directoryParam,
|
|
29
|
+
async: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.default(false)
|
|
32
|
+
.describe('Whether to wait for the tests to finish (false) or quickly return only the test id (true)'),
|
|
29
33
|
});
|
|
30
34
|
/*
|
|
31
35
|
* Run Agent tests in a Salesforce org.
|
|
@@ -38,7 +42,7 @@ const runAgentTestsParam = z.object({
|
|
|
38
42
|
* Returns:
|
|
39
43
|
* - textResponse: Test result.
|
|
40
44
|
*/
|
|
41
|
-
export const
|
|
45
|
+
export const registerToolTestAgent = (server) => {
|
|
42
46
|
server.tool('sf-test-agents', `Run Agent tests in an org.
|
|
43
47
|
|
|
44
48
|
AGENT INSTRUCTIONS:
|
|
@@ -50,10 +54,11 @@ this should be chosen when a file in the 'aiEvaluationDefinitions' directory is
|
|
|
50
54
|
EXAMPLE USAGE:
|
|
51
55
|
Run tests for the X agent
|
|
52
56
|
Run this test
|
|
57
|
+
start myAgentTest and don't wait for results
|
|
53
58
|
`, runAgentTestsParam.shape, {
|
|
54
59
|
title: 'Run Agent Tests',
|
|
55
60
|
openWorldHint: false,
|
|
56
|
-
}, async ({ usernameOrAlias, agentApiName, directory }) => {
|
|
61
|
+
}, async ({ usernameOrAlias, agentApiName, directory, async }) => {
|
|
57
62
|
if (!usernameOrAlias)
|
|
58
63
|
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
59
64
|
// needed for org allowlist to work
|
|
@@ -61,9 +66,15 @@ Run this test
|
|
|
61
66
|
const connection = await getConnection(usernameOrAlias);
|
|
62
67
|
try {
|
|
63
68
|
const agentTester = new AgentTester(connection);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
if (async) {
|
|
70
|
+
const startResult = await agentTester.start(agentApiName);
|
|
71
|
+
return textResponse(`Test Run: ${JSON.stringify(startResult)}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const test = await agentTester.start(agentApiName);
|
|
75
|
+
const result = await agentTester.poll(test.runId, { timeout: Duration.minutes(10) });
|
|
76
|
+
return textResponse(`Test result: ${JSON.stringify(result)}`);
|
|
77
|
+
}
|
|
67
78
|
}
|
|
68
79
|
catch (e) {
|
|
69
80
|
return textResponse(`Failed to run Agent Tests: ${e instanceof Error ? e.message : 'Unknown error'}`, true);
|
|
@@ -3,20 +3,38 @@ import { TestLevel } from '@salesforce/apex-node';
|
|
|
3
3
|
import { SfMcpServer } from '../../sf-mcp-server.js';
|
|
4
4
|
declare const runApexTestsParam: z.ZodObject<{
|
|
5
5
|
testLevel: z.ZodEnum<[TestLevel.RunLocalTests, TestLevel.RunAllTestsInOrg, TestLevel.RunSpecifiedTests]>;
|
|
6
|
-
classNames: z.ZodArray<z.ZodString, "many"
|
|
6
|
+
classNames: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
7
|
+
methodNames: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
8
|
+
async: z.ZodDefault<z.ZodBoolean>;
|
|
9
|
+
suiteName: z.ZodOptional<z.ZodString>;
|
|
10
|
+
testRunId: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
11
|
+
verbose: z.ZodDefault<z.ZodBoolean>;
|
|
12
|
+
codeCoverage: z.ZodDefault<z.ZodBoolean>;
|
|
7
13
|
usernameOrAlias: z.ZodString;
|
|
8
14
|
directory: z.ZodEffects<z.ZodString, string, string>;
|
|
9
15
|
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
async: boolean;
|
|
17
|
+
codeCoverage: boolean;
|
|
10
18
|
testLevel: TestLevel;
|
|
11
19
|
directory: string;
|
|
12
20
|
usernameOrAlias: string;
|
|
13
|
-
|
|
21
|
+
verbose: boolean;
|
|
22
|
+
classNames?: string[] | undefined;
|
|
23
|
+
methodNames?: string[] | undefined;
|
|
24
|
+
suiteName?: string | undefined;
|
|
25
|
+
testRunId?: string | undefined;
|
|
14
26
|
}, {
|
|
15
27
|
testLevel: TestLevel;
|
|
16
28
|
directory: string;
|
|
17
29
|
usernameOrAlias: string;
|
|
18
|
-
|
|
30
|
+
async?: boolean | undefined;
|
|
31
|
+
codeCoverage?: boolean | undefined;
|
|
32
|
+
classNames?: string[] | undefined;
|
|
33
|
+
methodNames?: string[] | undefined;
|
|
34
|
+
suiteName?: string | undefined;
|
|
35
|
+
testRunId?: string | undefined;
|
|
36
|
+
verbose?: boolean | undefined;
|
|
19
37
|
}>;
|
|
20
38
|
export type ApexRunTests = z.infer<typeof runApexTestsParam>;
|
|
21
|
-
export declare const
|
|
39
|
+
export declare const registerToolTestApex: (server: SfMcpServer) => void;
|
|
22
40
|
export {};
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { TestService } from '@salesforce/apex-node';
|
|
18
|
+
import { Duration, ensureArray } from '@salesforce/kit';
|
|
18
19
|
import { directoryParam, usernameOrAliasParam } from '../../shared/params.js';
|
|
19
20
|
import { textResponse } from '../../shared/utils.js';
|
|
20
21
|
import { getConnection } from '../../shared/auth.js';
|
|
@@ -28,10 +29,31 @@ RunLocalTests="Run all tests in the org, except the ones that originate from ins
|
|
|
28
29
|
RunAllTestsInOrg="Run all tests in the org, including tests of managed packages"
|
|
29
30
|
RunSpecifiedTests="Run the Apex tests I specify, these will be specified in the classNames parameter"
|
|
30
31
|
`),
|
|
31
|
-
classNames: z
|
|
32
|
+
classNames: z
|
|
33
|
+
.array(z.string())
|
|
34
|
+
.describe(`Apex tests classes to run.
|
|
32
35
|
if Running all tests, all tests should be listed
|
|
33
|
-
|
|
34
|
-
`)
|
|
36
|
+
Run the tests, find apex classes matching the pattern **/classes/*.cls, that include the @isTest decorator in the file and then join their test names together with ','
|
|
37
|
+
`)
|
|
38
|
+
.optional(),
|
|
39
|
+
methodNames: z
|
|
40
|
+
.array(z.string())
|
|
41
|
+
.describe('Specific test method names, functions inside of an apex test class, must be joined with the Apex tests name')
|
|
42
|
+
.optional(),
|
|
43
|
+
async: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.default(false)
|
|
46
|
+
.describe('Weather to wait for the test to finish (false) or enque the Apex tests and return the test run id (true)'),
|
|
47
|
+
suiteName: z.string().describe('a suite of apex test classes to run').optional(),
|
|
48
|
+
testRunId: z.string().default('an id of an in-progress, or completed apex test run').optional(),
|
|
49
|
+
verbose: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.default(false)
|
|
52
|
+
.describe('If a user wants more test information in the context, or information about passing tests'),
|
|
53
|
+
codeCoverage: z
|
|
54
|
+
.boolean()
|
|
55
|
+
.default(false)
|
|
56
|
+
.describe('set to true if a user wants codecoverage calculated by the server'),
|
|
35
57
|
usernameOrAlias: usernameOrAliasParam,
|
|
36
58
|
directory: directoryParam,
|
|
37
59
|
});
|
|
@@ -47,7 +69,7 @@ RunSpecifiedTests="Run the Apex tests I specify, these will be specified in the
|
|
|
47
69
|
* Returns:
|
|
48
70
|
* - textResponse: Test result.
|
|
49
71
|
*/
|
|
50
|
-
export const
|
|
72
|
+
export const registerToolTestApex = (server) => {
|
|
51
73
|
server.tool('sf-test-apex', `Run Apex tests in an org.
|
|
52
74
|
|
|
53
75
|
AGENT INSTRUCTIONS:
|
|
@@ -58,13 +80,20 @@ this should be chosen when a file in the 'classes' directory is mentioned
|
|
|
58
80
|
|
|
59
81
|
EXAMPLE USAGE:
|
|
60
82
|
Run tests A, B, C.
|
|
61
|
-
Run the
|
|
83
|
+
Run the myTestMethod in this file
|
|
84
|
+
Run this test and include success and failures
|
|
62
85
|
Run all tests in the org.
|
|
86
|
+
Test the "mySuite" suite asynchronously. I’ll check results later.
|
|
87
|
+
Run tests for this file and include coverage
|
|
88
|
+
What are the results for 707XXXXXXXXXXXX
|
|
63
89
|
`, runApexTestsParam.shape, {
|
|
64
90
|
title: 'Apex Tests',
|
|
65
91
|
openWorldHint: false,
|
|
66
|
-
}, async ({ testLevel, usernameOrAlias, classNames, directory }) => {
|
|
67
|
-
if (
|
|
92
|
+
}, async ({ testLevel, usernameOrAlias, classNames, directory, methodNames, suiteName, async, testRunId, verbose, codeCoverage, }) => {
|
|
93
|
+
if ((ensureArray(suiteName).length > 1 ||
|
|
94
|
+
ensureArray(methodNames).length > 1 ||
|
|
95
|
+
ensureArray(classNames).length > 1) &&
|
|
96
|
+
testLevel !== "RunSpecifiedTests" /* TestLevel.RunSpecifiedTests */) {
|
|
68
97
|
return textResponse("You can't specify which tests to run without setting testLevel='RunSpecifiedTests'", true);
|
|
69
98
|
}
|
|
70
99
|
if (!usernameOrAlias)
|
|
@@ -74,8 +103,25 @@ Run all tests in the org.
|
|
|
74
103
|
const connection = await getConnection(usernameOrAlias);
|
|
75
104
|
try {
|
|
76
105
|
const testService = new TestService(connection);
|
|
77
|
-
|
|
78
|
-
|
|
106
|
+
let result;
|
|
107
|
+
if (testRunId) {
|
|
108
|
+
// we just need to get the test results
|
|
109
|
+
result = await testService.reportAsyncResults(testRunId, codeCoverage);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// we need to run tests
|
|
113
|
+
const payload = await testService.buildAsyncPayload(testLevel, methodNames?.join(','), classNames?.join(','), suiteName);
|
|
114
|
+
result = await testService.runTestAsynchronous(payload, codeCoverage, async, undefined, undefined, Duration.minutes(10));
|
|
115
|
+
if (async) {
|
|
116
|
+
return textResponse(`Test Run Id: ${JSON.stringify(result)}`);
|
|
117
|
+
}
|
|
118
|
+
// the user waited for the full results, we know they're TestResult
|
|
119
|
+
result = result;
|
|
120
|
+
}
|
|
121
|
+
if (!verbose) {
|
|
122
|
+
// aka concise, filter out passing tests
|
|
123
|
+
result.tests = result.tests.filter((test) => test.outcome === "Fail" /* ApexTestResultOutcome.Fail */);
|
|
124
|
+
}
|
|
79
125
|
return textResponse(`Test result: ${JSON.stringify(result)}`);
|
|
80
126
|
}
|
|
81
127
|
catch (e) {
|