@salesforce/mcp 0.0.1 → 0.2.6

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.
Files changed (35) hide show
  1. package/README.md +240 -3
  2. package/lib/index.d.ts +1 -0
  3. package/lib/index.js +70 -4
  4. package/lib/shared/auth.d.ts +20 -0
  5. package/lib/shared/auth.js +186 -0
  6. package/lib/shared/params.d.ts +5 -0
  7. package/lib/shared/params.js +48 -0
  8. package/lib/shared/types.d.ts +34 -0
  9. package/lib/shared/types.js +17 -0
  10. package/lib/shared/utils.d.ts +13 -0
  11. package/lib/shared/utils.js +155 -0
  12. package/lib/tools/core/index.d.ts +1 -0
  13. package/lib/tools/core/index.js +17 -0
  14. package/lib/tools/core/sf-get-username.d.ts +17 -0
  15. package/lib/tools/core/sf-get-username.js +105 -0
  16. package/lib/tools/data/index.d.ts +1 -0
  17. package/lib/tools/data/index.js +17 -0
  18. package/lib/tools/data/sf-query-org.d.ts +17 -0
  19. package/lib/tools/data/sf-query-org.js +50 -0
  20. package/lib/tools/metadata/index.d.ts +2 -0
  21. package/lib/tools/metadata/index.js +18 -0
  22. package/lib/tools/metadata/sf-deploy-metadata.d.ts +27 -0
  23. package/lib/tools/metadata/sf-deploy-metadata.js +150 -0
  24. package/lib/tools/metadata/sf-retrieve-metadata.d.ts +2 -0
  25. package/lib/tools/metadata/sf-retrieve-metadata.js +122 -0
  26. package/lib/tools/orgs/index.d.ts +1 -0
  27. package/lib/tools/orgs/index.js +17 -0
  28. package/lib/tools/orgs/sf-list-all-orgs.d.ts +2 -0
  29. package/lib/tools/orgs/sf-list-all-orgs.js +49 -0
  30. package/lib/tools/users/index.d.ts +1 -0
  31. package/lib/tools/users/index.js +17 -0
  32. package/lib/tools/users/sf-assign-permission-set.d.ts +20 -0
  33. package/lib/tools/users/sf-assign-permission-set.js +84 -0
  34. package/lib/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +15 -8
@@ -0,0 +1,155 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /* eslint-disable no-console */
17
+ import { sep } from 'node:path';
18
+ import { EOL } from 'node:os';
19
+ import { parseArgs } from 'node:util';
20
+ const usageMessage = `Usage: sf-mcp-server [OPTIONS]
21
+
22
+ FLAGS:
23
+ -o, --orgs - Org username(s) to allow access to.
24
+ -t, --toolsets - List of toolsets to enable.
25
+
26
+ FLAG DESCRIPTIONS:
27
+ -o, --orgs=<string> Org usernames to allow access to.
28
+
29
+ Use this flag to specify the org usernames to allow the MCP server to access to.
30
+ If you need to pass more than one username/alias, pass all of them as a comma-separated string.
31
+
32
+ Special values:
33
+ DEFAULT_TARGET_ORG - Allow access to default orgs (global and local)
34
+ DEFAULT_TARGET_DEV_HUB - Allow access to default dev hubs (global and local)
35
+ ALLOW_ALL_ORGS - Allow access to all authenticated orgs (use with caution)
36
+
37
+ -t, --toolsets=<string> List of toolsets to enable.
38
+
39
+ Specify the toolsets to enable, possible values:
40
+ * all (default)
41
+ * orgs
42
+ * data
43
+ * users
44
+ * metadata
45
+
46
+ EXAMPLES:
47
+ // Start the server with all toolsets enabled and access only to the default org in the project:
48
+ sf-mcp-server --orgs DEFAULT_TARGET_ORG
49
+
50
+ // Allow access to the default org and "my-alias" one with only "data" tools;
51
+ sf-mcp-server --orgs DEFAULT_TARGET_DEV_HUB,my-alias --toolsets data
52
+
53
+ // Allow acccess to 3 specific orgs and enable all toolsets:
54
+ sf-mcp-server --orgs test-org@example.com,my-dev-hub,my-alias
55
+
56
+ Documentation:
57
+ See: https://github.com/salesforcecli/mcp`;
58
+ export function parseStartupArguments() {
59
+ const options = {
60
+ toolsets: { type: 'string', short: 't', default: 'all' },
61
+ orgs: { type: 'string', short: 'o' },
62
+ };
63
+ // TODO: strict false is to ignore flags pass at testing startup. Revisit this
64
+ const { values } = parseArgs({
65
+ args: process.argv,
66
+ options,
67
+ allowPositionals: true,
68
+ strict: false,
69
+ });
70
+ return { values };
71
+ }
72
+ export function buildOrgAllowList(orgs) {
73
+ // Fail if `--orgs` wasn't specified
74
+ if (!orgs) {
75
+ console.error(`Missing --orgs flag${EOL}${EOL}${usageMessage}`);
76
+ process.exit(1);
77
+ }
78
+ // Fail if `--orgs` was specified without a value
79
+ if (orgs === 'boolean') {
80
+ console.error(usageMessage);
81
+ process.exit(1);
82
+ }
83
+ const allOrgs = orgs.split(',').map((toolset) => toolset.trim());
84
+ const allowedOrgs = new Set();
85
+ if (allOrgs.includes('ALLOW_ALL_ORGS')) {
86
+ console.warn('WARNING: ALLOW_ALL_ORGS is set. This allows access to all authenticated orgs. Use with caution.');
87
+ // TODO Add telemetry
88
+ return new Set(['ALLOW_ALL_ORGS']);
89
+ }
90
+ // Process other arguments
91
+ for (const arg of allOrgs) {
92
+ if (arg === 'DEFAULT_TARGET_ORG' || arg === 'DEFAULT_TARGET_DEV_HUB' || arg.includes('@') || !arg.startsWith('-')) {
93
+ allowedOrgs.add(arg);
94
+ }
95
+ else {
96
+ console.error(`Invalid flag value: ${arg + EOL + EOL + usageMessage}`);
97
+ process.exit(1); // Stop the server
98
+ }
99
+ }
100
+ return allowedOrgs;
101
+ }
102
+ // TODO: break into two helpers? One for errors and one for success?
103
+ export function textResponse(text, isError = false) {
104
+ if (text === '')
105
+ throw new Error('textResponse error: "text" cannot be empty');
106
+ return {
107
+ isError,
108
+ content: [
109
+ {
110
+ type: 'text',
111
+ text,
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ /**
117
+ * Gets the enabled toolsets based on user input and validates against available toolsets
118
+ *
119
+ * @param availableToolsets - The list of available toolsets
120
+ * @param toolsetsInput - The comma-separated list of toolsets
121
+ * @returns A Set of enabled toolsets
122
+ */
123
+ export function getEnabledToolsets(availableToolsets, toolsetsInput) {
124
+ const availableToolsetsSet = new Set(availableToolsets);
125
+ const passedToolsets = toolsetsInput.split(',').map((toolset) => toolset.trim());
126
+ // Check if any passed toolset is not in the available list
127
+ for (const toolset of passedToolsets) {
128
+ if (!availableToolsetsSet.has(toolset)) {
129
+ console.error(`Passed toolset "${toolset}" is not in the allowed toolset list. Available toolsets are "all (default), ${Array.from(availableToolsetsSet)
130
+ .filter((t) => t !== 'all')
131
+ .join(', ')}"`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+ const enabledToolsets = new Set(passedToolsets.filter((toolset) => availableToolsetsSet.has(toolset)));
136
+ console.error('Enabling toolsets:', Array.from(enabledToolsets).join(', '));
137
+ return enabledToolsets;
138
+ }
139
+ export function sanitizePath(path) {
140
+ // Decode URL-encoded sequences
141
+ const decodedPath = decodeURIComponent(path);
142
+ // Normalize Unicode characters
143
+ const normalizedPath = decodedPath.normalize();
144
+ // Check for various traversal patterns
145
+ const hasTraversal = normalizedPath.includes('..') ||
146
+ normalizedPath.includes('\\..') ||
147
+ normalizedPath.includes('../') ||
148
+ normalizedPath.includes('..\\') ||
149
+ normalizedPath.includes('\u2025') || // Unicode horizontal ellipsis
150
+ normalizedPath.includes('\u2026'); // Unicode vertical ellipsis
151
+ // Ensure path is absolute
152
+ const isAbsolute = path.startsWith(sep);
153
+ return !hasTraversal && isAbsolute;
154
+ }
155
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ export * from './sf-get-username.js';
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export * from './sf-get-username.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ export declare const getUsernameParamsSchema: z.ZodObject<{
4
+ defaultTargetOrg: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
5
+ defaultDevHub: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
6
+ directory: z.ZodEffects<z.ZodString, string, string>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ defaultTargetOrg: boolean;
9
+ defaultDevHub: boolean;
10
+ directory: string;
11
+ }, {
12
+ directory: string;
13
+ defaultTargetOrg?: boolean | undefined;
14
+ defaultDevHub?: boolean | undefined;
15
+ }>;
16
+ export type GetUsernameParamsSchema = z.infer<typeof getUsernameParamsSchema>;
17
+ export declare const registerToolGetUsername: (server: McpServer) => void;
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { textResponse } from '../../shared/utils.js';
18
+ import { getDefaultTargetOrg, getDefaultTargetDevHub, suggestUsername } from '../../shared/auth.js';
19
+ import { directoryParam } from '../../shared/params.js';
20
+ /*
21
+ * Get username for Salesforce org
22
+ *
23
+ * Intelligently determines the appropriate username or alias for Salesforce operations.
24
+ *
25
+ * Parameters:
26
+ * - defaultTargetOrg: Force lookup of default target org (optional)
27
+ * - defaultDevHub: Force lookup of default dev hub (optional)
28
+ * - directory: The directory to run this tool from
29
+ *
30
+ * Returns:
31
+ * - textResponse: Username/alias and org configuration
32
+ */
33
+ export const getUsernameParamsSchema = z.object({
34
+ defaultTargetOrg: z.boolean().optional().default(false).describe(`Try to find default org
35
+ AGENT INSTRUCTIONS:
36
+ ONLY SET TO TRUE when the user explicitly asks for the default org or default target org.
37
+ Leave it as false when the user is vague and says something like "for my org" or "for my-alias".
38
+
39
+ USAGE EXAMPLE:
40
+ Get username for my default org
41
+ ...for my default target org`),
42
+ defaultDevHub: z.boolean().optional().default(false).describe(`Try to find default dev hub
43
+ AGENT INSTRUCTIONS:
44
+ ONLY SET TO TRUE when the user explicitly asks for the default dev hub or default target devhub.
45
+ Leave it as false when the user is vague and says something like "for my org" or "for my-alias".
46
+
47
+ USAGE EXAMPLE:
48
+ Get username for my default dev hub
49
+ ...for my default target dev hub
50
+ ...for my default devhub`),
51
+ directory: directoryParam,
52
+ });
53
+ export const registerToolGetUsername = (server) => {
54
+ server.tool('sf-get-username', `Intelligently determines the appropriate username or alias for Salesforce operations.
55
+
56
+ AGENT/LLM INSTRUCTIONS:
57
+ Use this tool when uncertain which username/org a user wants for Salesforce operations.
58
+ This tool handles three distinct scenarios:
59
+
60
+ 1. When defaultTargetOrg=true: Fetches the default target org configuration
61
+ - Use when user says "for my default org" or "for my default target org"
62
+
63
+ 2. When defaultDevHub=true: Fetches the default dev hub configuration
64
+ - Use when user says "for my default dev hub" or "for my default target dev hub"
65
+
66
+ 3. When both are false (default): Uses suggestUsername to intelligently determine the appropriate org
67
+ - Use when user is vague and says something like "for my org" or doesn't specify
68
+
69
+ EXAMPLE USAGE:
70
+ - When user says "Do X for my org" → defaultTargetOrg=false, defaultDevHub=false
71
+ - When user says "For my default org" → defaultTargetOrg=true
72
+ - When user says "For my default dev hub" → defaultDevHub=true`, getUsernameParamsSchema.shape, async ({ defaultTargetOrg, defaultDevHub, directory }) => {
73
+ try {
74
+ process.chdir(directory);
75
+ const generateResponse = (defaultFromConfig) => textResponse(`ALWAYS notify the user the following 3 (maybe 4) pieces of information:
76
+ 1. If it is default target-org or target-dev-hub ('.key' on the config)
77
+ 2. The value of '.location' on the config
78
+ 3. The value of '.value' on the config
79
+ 4. IF '.cached' IS TRUE, tell then we are using a cached value and if they have changed it, restart the MCP Server
80
+
81
+ - Full config: ${JSON.stringify(defaultFromConfig, null, 2)}
82
+
83
+ UNLESS THE USER SPECIFIES OTHERWISE, use this username for the "usernameOrAlias" parameter in future Tool calls.`);
84
+ // Case 1: User explicitly asked for default target org
85
+ if (defaultTargetOrg)
86
+ return generateResponse(await getDefaultTargetOrg());
87
+ // Case 2: User explicitly asked for default dev hub
88
+ if (defaultDevHub)
89
+ return generateResponse(await getDefaultTargetDevHub());
90
+ // Case 3: User was vague, so suggest a username
91
+ const { aliasForReference, suggestedUsername, reasoning } = await suggestUsername();
92
+ if (!suggestedUsername) {
93
+ return textResponse("No suggested username found. Please specify a username or alias explicitly. Also check the MCP server's startup args for allowlisting orgs.", true);
94
+ }
95
+ return textResponse(`
96
+ YOU MUST inform the user that we are going to use "${suggestedUsername}" ${aliasForReference ? `(Alias: ${aliasForReference}) ` : ''}for the "usernameOrAlias" parameter.
97
+ YOU MUST explain the reasoning for selecting this org, which is: "${reasoning}"
98
+ UNLESS THE USER SPECIFIES OTHERWISE, use this username for the "usernameOrAlias" parameter in future Tool calls.`);
99
+ }
100
+ catch (error) {
101
+ return textResponse(`Failed to determine appropriate username: ${error instanceof Error ? error.message : 'Unknown error'}`, true);
102
+ }
103
+ });
104
+ };
105
+ //# sourceMappingURL=sf-get-username.js.map
@@ -0,0 +1 @@
1
+ export * from './sf-query-org.js';
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export * from './sf-query-org.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ export declare const queryOrgParamsSchema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ usernameOrAlias: z.ZodString;
6
+ directory: z.ZodEffects<z.ZodString, string, string>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ query: string;
9
+ directory: string;
10
+ usernameOrAlias: string;
11
+ }, {
12
+ query: string;
13
+ directory: string;
14
+ usernameOrAlias: string;
15
+ }>;
16
+ export type QueryOrgOptions = z.infer<typeof queryOrgParamsSchema>;
17
+ export declare const registerToolQueryOrg: (server: McpServer) => void;
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /*
17
+ * Query Salesforce org
18
+ *
19
+ * Run a SOQL query against a Salesforce org.
20
+ *
21
+ * Parameters:
22
+ * - query: SOQL query to run (required)
23
+ * - usernameOrAlias: username or alias for the Salesforce org to run the query against
24
+ *
25
+ * Returns:
26
+ * - textResponse: SOQL query results
27
+ */
28
+ import { z } from 'zod';
29
+ import { getConnection } from '../../shared/auth.js';
30
+ import { textResponse } from '../../shared/utils.js';
31
+ import { directoryParam, usernameOrAliasParam } from '../../shared/params.js';
32
+ export const queryOrgParamsSchema = z.object({
33
+ query: z.string().describe('SOQL query to run'),
34
+ usernameOrAlias: usernameOrAliasParam,
35
+ directory: directoryParam,
36
+ });
37
+ export const registerToolQueryOrg = (server) => {
38
+ server.tool('sf-query-org', 'Run a SOQL query against a Salesforce org.', queryOrgParamsSchema.shape, async ({ query, usernameOrAlias, directory }) => {
39
+ try {
40
+ process.chdir(directory);
41
+ const connection = await getConnection(usernameOrAlias);
42
+ const result = await connection.query(query);
43
+ return textResponse(`SOQL query results:\n\n${JSON.stringify(result, null, 2)}`);
44
+ }
45
+ catch (error) {
46
+ return textResponse(`Failed to query org: ${error instanceof Error ? error.message : 'Unknown error'}`, true);
47
+ }
48
+ });
49
+ };
50
+ //# sourceMappingURL=sf-query-org.js.map
@@ -0,0 +1,2 @@
1
+ export * from './sf-deploy-metadata.js';
2
+ export * from './sf-retrieve-metadata.js';
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export * from './sf-deploy-metadata.js';
17
+ export * from './sf-retrieve-metadata.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ declare const deployMetadataParams: z.ZodObject<{
4
+ sourceDir: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
5
+ manifest: z.ZodOptional<z.ZodString>;
6
+ apexTestLevel: z.ZodOptional<z.ZodEnum<["NoTestRun", "RunLocalTests", "RunAllTestsInOrg"]>>;
7
+ apexTests: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
8
+ usernameOrAlias: z.ZodString;
9
+ directory: z.ZodEffects<z.ZodString, string, string>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ directory: string;
12
+ usernameOrAlias: string;
13
+ manifest?: string | undefined;
14
+ sourceDir?: string[] | undefined;
15
+ apexTestLevel?: "RunLocalTests" | "RunAllTestsInOrg" | "NoTestRun" | undefined;
16
+ apexTests?: string[] | undefined;
17
+ }, {
18
+ directory: string;
19
+ usernameOrAlias: string;
20
+ manifest?: string | undefined;
21
+ sourceDir?: string[] | undefined;
22
+ apexTestLevel?: "RunLocalTests" | "RunAllTestsInOrg" | "NoTestRun" | undefined;
23
+ apexTests?: string[] | undefined;
24
+ }>;
25
+ export type DeployMetadata = z.infer<typeof deployMetadataParams>;
26
+ export declare const registerToolDeployMetadata: (server: McpServer) => void;
27
+ export {};
@@ -0,0 +1,150 @@
1
+ /*
2
+ * Copyright 2025, Salesforce, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { z } from 'zod';
17
+ import { Org, SfProject } from '@salesforce/core';
18
+ import { SourceTracking } from '@salesforce/source-tracking';
19
+ import { ComponentSet, ComponentSetBuilder } from '@salesforce/source-deploy-retrieve';
20
+ import { ensureString } from '@salesforce/ts-types';
21
+ import { Duration } from '@salesforce/kit';
22
+ import { directoryParam, usernameOrAliasParam } from '../../shared/params.js';
23
+ import { textResponse } from '../../shared/utils.js';
24
+ import { getConnection } from '../../shared/auth.js';
25
+ const deployMetadataParams = z.object({
26
+ sourceDir: z
27
+ .array(z.string())
28
+ .describe('Path to the local source files to deploy. Leave this unset if the user is vague about what to deploy.')
29
+ .optional(),
30
+ manifest: z.string().describe('Full file path for manifest (XML file) of components to deploy.').optional(),
31
+ // `RunSpecifiedTests` is excluded on purpose because the tool sets this level when Apex tests to run are passed in.
32
+ //
33
+ // Can be left unset to let the org decide which test level to use:
34
+ // https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_deploy_running_tests.htm
35
+ apexTestLevel: z
36
+ .enum(['NoTestRun', 'RunLocalTests', 'RunAllTestsInOrg'])
37
+ .optional()
38
+ .describe(`Apex test level to use during deployment.
39
+
40
+ AGENT INSTRUCTIONS
41
+ Set this only if the user specifically ask to run apex tests in some of these ways:
42
+
43
+ NoTestRun="No tests are run"
44
+ RunLocalTests="Run all tests in the org, except the ones that originate from installed managed and unlocked packages."
45
+ RunAllTestsInOrg="Run all tests in the org, including tests of managed packages"
46
+
47
+ Don't set this param is "apexTests" is also set.
48
+ `),
49
+ apexTests: z
50
+ .array(z.string())
51
+ .describe(`Apex tests classes to run.
52
+
53
+ Set this param if the user ask an Apex test to be run during deployment.
54
+ `)
55
+ .optional(),
56
+ usernameOrAlias: usernameOrAliasParam,
57
+ directory: directoryParam,
58
+ });
59
+ /*
60
+ * Deploy metadata to a Salesforce org.
61
+ *
62
+ * Parameters:
63
+ * - sourceDir: Path to the local source files to deploy.
64
+ * - manifest: Full file path for manifest (XML file) of components to deploy.
65
+ * - apexTestLevel: Apex test level to use during deployment.
66
+ * - apexTests: Apex tests classes to run.
67
+ * - usernameOrAlias: Username or alias of the Salesforce org to deploy to.
68
+ * - directory: Directory of the local project.
69
+ *
70
+ * Returns:
71
+ * - textResponse: Deploy result.
72
+ */
73
+ export const registerToolDeployMetadata = (server) => {
74
+ server.tool('sf-deploy-metadata', `Deploy metadata to an org from your local project.
75
+
76
+ AGENT INSTRUCTIONS:
77
+ If the user doesn't specify what to deploy exactly ("deploy my changes"), leave the "sourceDir" and "manifest" params empty so the tool calculates which files to deploy.
78
+
79
+ EXAMPLE USAGE:
80
+ Deploy changes to my org
81
+ Deploy this file to my org
82
+ Deploy the manifest
83
+ Deploy X metadata to my org
84
+ Deploy X to my org and run A,B and C apex tests.
85
+ `, deployMetadataParams.shape, async ({ sourceDir, usernameOrAlias, apexTests, apexTestLevel, directory, manifest }) => {
86
+ if (apexTests && apexTestLevel) {
87
+ return textResponse("You can't specify both `apexTests` and `apexTestLevel` parameters.", true);
88
+ }
89
+ if (sourceDir && manifest) {
90
+ return textResponse("You can't specify both `sourceDir` and `manifest` parameters.", true);
91
+ }
92
+ // needed for org allowlist to work
93
+ process.chdir(directory);
94
+ const connection = await getConnection(usernameOrAlias);
95
+ const project = await SfProject.resolve(directory);
96
+ const org = await Org.create({ connection });
97
+ if (!sourceDir && !manifest && !(await org.tracksSource())) {
98
+ return textResponse('This org does not have source-tracking enabled or does not support source-tracking. You should specify the files or a manifest to deploy.', true);
99
+ }
100
+ try {
101
+ const stl = await SourceTracking.create({
102
+ org,
103
+ project,
104
+ subscribeSDREvents: true,
105
+ });
106
+ const componentSet = await buildDeployComponentSet(connection, project, stl, sourceDir, manifest);
107
+ if (componentSet.size === 0) {
108
+ // STL found no changes
109
+ return textResponse('No local changes to deploy were found.');
110
+ }
111
+ const deploy = await componentSet.deploy({
112
+ usernameOrConnection: connection,
113
+ apiOptions: {
114
+ ...(apexTests ? { runTests: apexTests, testLevel: 'RunSpecifiedTests' } : {}),
115
+ ...(apexTestLevel ? { testLevel: apexTestLevel } : {}),
116
+ },
117
+ });
118
+ // polling freq. is set dynamically by SDR based no the component set size.
119
+ const result = await deploy.pollStatus({
120
+ timeout: Duration.minutes(10),
121
+ });
122
+ return textResponse(`Deploy result: ${JSON.stringify(result.response)}`, !result.response.success);
123
+ }
124
+ catch (error) {
125
+ return textResponse(`Failed to deploy metadata: ${error instanceof Error ? error.message : 'Unknown error'}`, true);
126
+ }
127
+ });
128
+ };
129
+ async function buildDeployComponentSet(connection, project, stl, sourceDir, manifestPath) {
130
+ if (sourceDir || manifestPath) {
131
+ return ComponentSetBuilder.build({
132
+ apiversion: connection.getApiVersion(),
133
+ sourceapiversion: ensureString((await project.resolveProjectConfig()).sourceApiVersion),
134
+ sourcepath: sourceDir,
135
+ ...(manifestPath
136
+ ? {
137
+ manifest: {
138
+ manifestPath,
139
+ directoryPaths: project.getUniquePackageDirectories().map((pDir) => pDir.fullPath),
140
+ },
141
+ }
142
+ : {}),
143
+ projectDir: stl?.projectPath,
144
+ });
145
+ }
146
+ // No specific metadata requested to deploy, build component set from STL.
147
+ const cs = (await stl.localChangesAsComponentSet(false))[0] ?? new ComponentSet(undefined, stl.registry);
148
+ return cs;
149
+ }
150
+ //# sourceMappingURL=sf-deploy-metadata.js.map
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const registerToolRetrieveMetadata: (server: McpServer) => void;