@salesforce/mcp 0.7.0 → 0.8.2
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 +9 -0
- package/lib/index.js +0 -2
- package/lib/shared/utils.d.ts +1 -1
- package/lib/shared/utils.js +12 -12
- package/lib/tools/core/sf-get-username.js +5 -1
- package/lib/tools/core/sf-resume.js +4 -1
- package/lib/tools/data/sf-query-org.js +5 -1
- package/lib/tools/metadata/sf-deploy-metadata.js +5 -1
- package/lib/tools/metadata/sf-retrieve-metadata.js +5 -1
- package/lib/tools/orgs/sf-list-all-orgs.js +5 -1
- package/lib/tools/testing/index.d.ts +2 -2
- package/lib/tools/testing/index.js +2 -2
- package/lib/tools/testing/{sf-run-agent-tests.js → sf-test-agents.js} +6 -3
- package/lib/tools/testing/{sf-run-apex-tests.js → sf-test-apex.js} +8 -5
- package/lib/tools/users/sf-assign-permission-set.js +4 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- /package/lib/tools/testing/{sf-run-agent-tests.d.ts → sf-test-agents.d.ts} +0 -0
- /package/lib/tools/testing/{sf-run-apex-tests.d.ts → sf-test-apex.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -158,6 +158,7 @@ These are the available toolsets:
|
|
|
158
158
|
- `data` - [Tools to manage the data in your org, such as listing all accounts.](README.md#data-toolset)
|
|
159
159
|
- `users` - [Tools to manage org users, such as assigning a permission set.](README.md#users-toolset)
|
|
160
160
|
- `metadata` - [Tools to deploy and retrieve metadata to and from your org and your DX project.](README.md#metadata-toolset)
|
|
161
|
+
- `testing` - [Tools to test your code and features](README.md#testing-toolset)
|
|
161
162
|
|
|
162
163
|
This example shows how to enable the `data`, `orgs`, and `metadata` toolsets when configuring the MCP server for VS Code:
|
|
163
164
|
|
|
@@ -203,6 +204,14 @@ Includes these tools:
|
|
|
203
204
|
|
|
204
205
|
- `sf-deploy-metadata` - Deploys metadata from your DX project to an org.
|
|
205
206
|
- `sf-retrieve-metadata` - Retrieves metadata from your org to your DX project.
|
|
207
|
+
-
|
|
208
|
+
|
|
209
|
+
#### Testing Toolset
|
|
210
|
+
|
|
211
|
+
Includes these tools:
|
|
212
|
+
|
|
213
|
+
- `sf-test-agents` - Executes agent tests in your org.
|
|
214
|
+
- `sf-test-apex` - Executes apex tests in your org
|
|
206
215
|
|
|
207
216
|
## Configure Other Clients to Use the Salesforce DX MCP Server
|
|
208
217
|
|
package/lib/index.js
CHANGED
|
@@ -129,8 +129,6 @@ You can also use special values to control access to orgs:
|
|
|
129
129
|
tools: {},
|
|
130
130
|
},
|
|
131
131
|
}, { telemetry: this.telemetry });
|
|
132
|
-
// // TODO: Should we add annotations to our tools? https://modelcontextprotocol.io/docs/concepts/tools#tool-definition-structure
|
|
133
|
-
// // TODO: Move tool names into a shared file, that way if we reference them in multiple places, we can update them in one place
|
|
134
132
|
const enabledToolsets = new Set(flags.toolsets);
|
|
135
133
|
const all = enabledToolsets.has('all');
|
|
136
134
|
// ************************
|
package/lib/shared/utils.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export declare function textResponse(text: string, isError?: boolean): ToolTextR
|
|
|
8
8
|
* @returns A Set of enabled toolsets
|
|
9
9
|
*/
|
|
10
10
|
export declare function getEnabledToolsets(availableToolsets: string[], toolsetsInput: string): Set<string>;
|
|
11
|
-
export declare function sanitizePath(
|
|
11
|
+
export declare function sanitizePath(projectPath: string): boolean;
|
package/lib/shared/utils.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
/* eslint-disable no-console */
|
|
17
|
-
import
|
|
17
|
+
import path from 'node:path';
|
|
18
18
|
// TODO: break into two helpers? One for errors and one for success?
|
|
19
19
|
export function textResponse(text, isError = false) {
|
|
20
20
|
if (text === '')
|
|
@@ -52,20 +52,20 @@ export function getEnabledToolsets(availableToolsets, toolsetsInput) {
|
|
|
52
52
|
console.error('Enabling toolsets:', Array.from(enabledToolsets).join(', '));
|
|
53
53
|
return enabledToolsets;
|
|
54
54
|
}
|
|
55
|
-
export function sanitizePath(
|
|
55
|
+
export function sanitizePath(projectPath) {
|
|
56
56
|
// Decode URL-encoded sequences
|
|
57
|
-
const
|
|
57
|
+
const decodedProjectPath = decodeURIComponent(projectPath);
|
|
58
58
|
// Normalize Unicode characters
|
|
59
|
-
const
|
|
59
|
+
const normalizedProjectPath = decodedProjectPath.normalize();
|
|
60
60
|
// Check for various traversal patterns
|
|
61
|
-
const hasTraversal =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
const isAbsolute = path.startsWith(
|
|
61
|
+
const hasTraversal = normalizedProjectPath.includes('..') ||
|
|
62
|
+
normalizedProjectPath.includes('\u2025') || // Unicode horizontal ellipsis
|
|
63
|
+
normalizedProjectPath.includes('\u2026'); // Unicode vertical ellipsis
|
|
64
|
+
// `path.isAbsolute` doesn't cover Windows's drive-relative path:
|
|
65
|
+
// https://github.com/nodejs/node/issues/56766
|
|
66
|
+
//
|
|
67
|
+
// we can assume it's a drive-relative path if it's starts with `\`.
|
|
68
|
+
const isAbsolute = path.isAbsolute(projectPath) && (process.platform === 'win32' ? !projectPath.startsWith('\\') : true);
|
|
69
69
|
return !hasTraversal && isAbsolute;
|
|
70
70
|
}
|
|
71
71
|
//# sourceMappingURL=utils.js.map
|
|
@@ -69,7 +69,11 @@ This tool handles three distinct scenarios:
|
|
|
69
69
|
EXAMPLE USAGE:
|
|
70
70
|
- When user says "Do X for my org" → defaultTargetOrg=false, defaultDevHub=false
|
|
71
71
|
- When user says "For my default org" → defaultTargetOrg=true
|
|
72
|
-
- When user says "For my default dev hub" → defaultDevHub=true`, getUsernameParamsSchema.shape,
|
|
72
|
+
- When user says "For my default dev hub" → defaultDevHub=true`, getUsernameParamsSchema.shape, {
|
|
73
|
+
title: 'Get Username',
|
|
74
|
+
readOnlyHint: true,
|
|
75
|
+
openWorldHint: false,
|
|
76
|
+
}, async ({ defaultTargetOrg, defaultDevHub, directory }) => {
|
|
73
77
|
try {
|
|
74
78
|
process.chdir(directory);
|
|
75
79
|
const generateResponse = (defaultFromConfig) => textResponse(`ALWAYS notify the user the following 3 (maybe 4) pieces of information:
|
|
@@ -63,7 +63,10 @@ Resume the deployment to my org
|
|
|
63
63
|
Resume scratch org creation
|
|
64
64
|
Resume job 2SR1234567890
|
|
65
65
|
Resume agent tests
|
|
66
|
-
`, resumeParamsSchema.shape,
|
|
66
|
+
`, resumeParamsSchema.shape, {
|
|
67
|
+
title: 'Resume',
|
|
68
|
+
openWorldHint: false,
|
|
69
|
+
}, async ({ jobId, wait, usernameOrAlias, directory }) => {
|
|
67
70
|
if (!jobId) {
|
|
68
71
|
return textResponse('The jobId parameter is required.', true);
|
|
69
72
|
}
|
|
@@ -35,7 +35,11 @@ export const queryOrgParamsSchema = z.object({
|
|
|
35
35
|
directory: directoryParam,
|
|
36
36
|
});
|
|
37
37
|
export const registerToolQueryOrg = (server) => {
|
|
38
|
-
server.tool('sf-query-org', 'Run a SOQL query against a Salesforce org.', queryOrgParamsSchema.shape,
|
|
38
|
+
server.tool('sf-query-org', 'Run a SOQL query against a Salesforce org.', queryOrgParamsSchema.shape, {
|
|
39
|
+
title: 'Query Org',
|
|
40
|
+
openWorldHint: false,
|
|
41
|
+
readOnlyHint: true,
|
|
42
|
+
}, async ({ query, usernameOrAlias, directory }) => {
|
|
39
43
|
try {
|
|
40
44
|
if (!usernameOrAlias)
|
|
41
45
|
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
@@ -82,7 +82,11 @@ Deploy this file to my org
|
|
|
82
82
|
Deploy the manifest
|
|
83
83
|
Deploy X metadata to my org
|
|
84
84
|
Deploy X to my org and run A,B and C apex tests.
|
|
85
|
-
`, deployMetadataParams.shape,
|
|
85
|
+
`, deployMetadataParams.shape, {
|
|
86
|
+
title: 'Deploy Metadata',
|
|
87
|
+
destructiveHint: true,
|
|
88
|
+
openWorldHint: false,
|
|
89
|
+
}, async ({ sourceDir, usernameOrAlias, apexTests, apexTestLevel, directory, manifest }) => {
|
|
86
90
|
if (apexTests && apexTestLevel) {
|
|
87
91
|
return textResponse("You can't specify both `apexTests` and `apexTestLevel` parameters.", true);
|
|
88
92
|
}
|
|
@@ -55,7 +55,11 @@ Retrieve changes from my org
|
|
|
55
55
|
Retrieve this file from my org
|
|
56
56
|
Retrieve the metadata in the manifest
|
|
57
57
|
Retrieve X metadata from my org
|
|
58
|
-
`, retrieveMetadataParams.shape,
|
|
58
|
+
`, retrieveMetadataParams.shape, {
|
|
59
|
+
title: 'Retrieve Metadata',
|
|
60
|
+
openWorldHint: false,
|
|
61
|
+
destructiveHint: true,
|
|
62
|
+
}, async ({ sourceDir, usernameOrAlias, directory, manifest }) => {
|
|
59
63
|
if (sourceDir && manifest) {
|
|
60
64
|
return textResponse("You can't specify both `sourceDir` and `manifest` parameters.", true);
|
|
61
65
|
}
|
|
@@ -41,7 +41,11 @@ Example usage:
|
|
|
41
41
|
Can you list all Salesforce orgs for me
|
|
42
42
|
List all Salesforce orgs
|
|
43
43
|
List all orgs
|
|
44
|
-
`, listAllOrgsParamsSchema.shape,
|
|
44
|
+
`, listAllOrgsParamsSchema.shape, {
|
|
45
|
+
title: 'List All Orgs',
|
|
46
|
+
readOnlyHint: true,
|
|
47
|
+
openWorldHint: false,
|
|
48
|
+
}, async ({ directory }) => {
|
|
45
49
|
try {
|
|
46
50
|
process.chdir(directory);
|
|
47
51
|
const orgs = await getAllAllowedOrgs();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './sf-
|
|
2
|
-
export * from './sf-
|
|
1
|
+
export * from './sf-test-apex.js';
|
|
2
|
+
export * from './sf-test-agents.js';
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
export * from './sf-
|
|
17
|
-
export * from './sf-
|
|
16
|
+
export * from './sf-test-apex.js';
|
|
17
|
+
export * from './sf-test-agents.js';
|
|
18
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -39,7 +39,7 @@ const runAgentTestsParam = z.object({
|
|
|
39
39
|
* - textResponse: Test result.
|
|
40
40
|
*/
|
|
41
41
|
export const registerToolRunAgentTest = (server) => {
|
|
42
|
-
server.tool('sf-
|
|
42
|
+
server.tool('sf-test-agents', `Run Agent tests in an org.
|
|
43
43
|
|
|
44
44
|
AGENT INSTRUCTIONS:
|
|
45
45
|
If the user doesn't specify what to test, take context from the currently open file
|
|
@@ -50,7 +50,10 @@ this should be chosen when a file in the 'aiEvaluationDefinitions' directory is
|
|
|
50
50
|
EXAMPLE USAGE:
|
|
51
51
|
Run tests for the X agent
|
|
52
52
|
Run this test
|
|
53
|
-
`, runAgentTestsParam.shape,
|
|
53
|
+
`, runAgentTestsParam.shape, {
|
|
54
|
+
title: 'Run Agent Tests',
|
|
55
|
+
openWorldHint: false,
|
|
56
|
+
}, async ({ usernameOrAlias, agentApiName, directory }) => {
|
|
54
57
|
if (!usernameOrAlias)
|
|
55
58
|
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
56
59
|
// needed for org allowlist to work
|
|
@@ -67,4 +70,4 @@ Run this test
|
|
|
67
70
|
}
|
|
68
71
|
});
|
|
69
72
|
};
|
|
70
|
-
//# sourceMappingURL=sf-
|
|
73
|
+
//# sourceMappingURL=sf-test-agents.js.map
|
|
@@ -30,7 +30,7 @@ RunSpecifiedTests="Run the Apex tests I specify, these will be specified in the
|
|
|
30
30
|
`),
|
|
31
31
|
classNames: z.array(z.string()).describe(`Apex tests classes to run.
|
|
32
32
|
if Running all tests, all tests should be listed
|
|
33
|
-
if unsure, find apex classes matching the pattern
|
|
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
34
|
`),
|
|
35
35
|
usernameOrAlias: usernameOrAliasParam,
|
|
36
36
|
directory: directoryParam,
|
|
@@ -48,7 +48,7 @@ RunSpecifiedTests="Run the Apex tests I specify, these will be specified in the
|
|
|
48
48
|
* - textResponse: Test result.
|
|
49
49
|
*/
|
|
50
50
|
export const registerToolRunApexTest = (server) => {
|
|
51
|
-
server.tool('sf-
|
|
51
|
+
server.tool('sf-test-apex', `Run Apex tests in an org.
|
|
52
52
|
|
|
53
53
|
AGENT INSTRUCTIONS:
|
|
54
54
|
If the user doesn't specify what to test, take context from the currently open file
|
|
@@ -58,9 +58,12 @@ this should be chosen when a file in the 'classes' directory is mentioned
|
|
|
58
58
|
|
|
59
59
|
EXAMPLE USAGE:
|
|
60
60
|
Run tests A, B, C.
|
|
61
|
-
Run the tests, find apex classes matching the pattern
|
|
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 ','
|
|
62
62
|
Run all tests in the org.
|
|
63
|
-
`, runApexTestsParam.shape,
|
|
63
|
+
`, runApexTestsParam.shape, {
|
|
64
|
+
title: 'Apex Tests',
|
|
65
|
+
openWorldHint: false,
|
|
66
|
+
}, async ({ testLevel, usernameOrAlias, classNames, directory }) => {
|
|
64
67
|
if (testLevel !== "RunSpecifiedTests" /* TestLevel.RunSpecifiedTests */ && classNames?.length && classNames?.length >= 1) {
|
|
65
68
|
return textResponse("You can't specify which tests to run without setting testLevel='RunSpecifiedTests'", true);
|
|
66
69
|
}
|
|
@@ -80,4 +83,4 @@ Run all tests in the org.
|
|
|
80
83
|
}
|
|
81
84
|
});
|
|
82
85
|
};
|
|
83
|
-
//# sourceMappingURL=sf-
|
|
86
|
+
//# sourceMappingURL=sf-test-apex.js.map
|
|
@@ -57,7 +57,10 @@ Set the permission set MyPermSet on behalf of my-alias.`),
|
|
|
57
57
|
directory: directoryParam,
|
|
58
58
|
});
|
|
59
59
|
export const registerToolAssignPermissionSet = (server) => {
|
|
60
|
-
server.tool('sf-assign-permission-set', 'Assign a permission set to one or more org users.', assignPermissionSetParamsSchema.shape,
|
|
60
|
+
server.tool('sf-assign-permission-set', 'Assign a permission set to one or more org users.', assignPermissionSetParamsSchema.shape, {
|
|
61
|
+
title: 'Assign Permission Set',
|
|
62
|
+
openWorldHint: false,
|
|
63
|
+
}, async ({ permissionSetName, usernameOrAlias, onBehalfOf, directory }) => {
|
|
61
64
|
try {
|
|
62
65
|
if (!usernameOrAlias)
|
|
63
66
|
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|