@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 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.registerToolRunApexTest(server);
171
- testing.registerToolRunAgentTest(server);
170
+ testing.registerToolTestApex(server);
171
+ testing.registerToolTestAgent(server);
172
172
  }
173
173
  // ************************
174
174
  // METADATA TOOLS
@@ -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 registerToolRunAgentTest: (server: SfMcpServer) => void;
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 *.aiEvaluationDefinition-meta.xml
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 registerToolRunAgentTest = (server) => {
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
- const test = await agentTester.start(agentApiName);
65
- const result = await agentTester.poll(test.runId, { timeout: Duration.minutes(10) });
66
- return textResponse(`Test result: ${JSON.stringify(result)}`);
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
- classNames: string[];
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
- classNames: string[];
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 registerToolRunApexTest: (server: SfMcpServer) => void;
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.array(z.string()).describe(`Apex tests classes to run.
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
- if unsure, find apex classes matching the pattern **/classes/*.cls, that include the @isTest decorator in the file and then join their test names together with ','
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 registerToolRunApexTest = (server) => {
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 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 ','
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 (testLevel !== "RunSpecifiedTests" /* TestLevel.RunSpecifiedTests */ && classNames?.length && classNames?.length >= 1) {
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
- const payload = await testService.buildAsyncPayload(testLevel, classNames.join(','));
78
- const result = (await testService.runTestAsynchronous(payload, false));
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) {