@salesforce/mcp 0.20.1 → 0.21.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.d.ts CHANGED
@@ -5,6 +5,7 @@ export default class McpServerCommand extends Command {
5
5
  static flags: {
6
6
  orgs: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
7
7
  toolsets: import("@oclif/core/interfaces").OptionFlag<(import("@salesforce/mcp-provider-api/src/enums.js").Toolset | "all")[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ tools: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  version: import("@oclif/core/interfaces").BooleanFlag<void>;
9
10
  'no-telemetry': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
package/lib/index.js CHANGED
@@ -73,12 +73,21 @@ You can also use special values to control access to orgs:
73
73
  }),
74
74
  toolsets: Flags.option({
75
75
  options: ['all', ...TOOLSETS],
76
- char: 't',
77
- summary: 'Toolset to enable',
76
+ summary: 'Toolset(s) to enable. Set to "all" to enable every toolset',
78
77
  multiple: true,
79
78
  delimiter: ',',
80
- exclusive: ['dynamic-toolsets'],
79
+ exclusive: ['dynamic-tools'],
81
80
  })(),
81
+ // It would be nice if we could get these as an Flags.option
82
+ // Since the tools need `services` passed in I am not sure we
83
+ // can get the list of tools this early. We could build a json
84
+ // manifest (pre-commit) that could be read from for tool names
85
+ tools: Flags.string({
86
+ summary: 'Tool(s) to enable',
87
+ multiple: true,
88
+ delimiter: ',',
89
+ exclusive: ['dynamic-tools'],
90
+ }),
82
91
  version: Flags.version(),
83
92
  'no-telemetry': Flags.boolean({
84
93
  summary: 'Disable telemetry',
@@ -98,19 +107,23 @@ You can also use special values to control access to orgs:
98
107
  static examples = [
99
108
  {
100
109
  description: 'Start the server with all toolsets enabled and access only to the default org in the project',
101
- command: '<%= config.bin %> --orgs DEFAULT_TARGET_ORG',
110
+ command: '<%= config.bin %> --toolsets all --orgs DEFAULT_TARGET_ORG',
102
111
  },
103
112
  {
104
- description: 'Allow access to the default org and "my-alias" one with only "data" tools',
113
+ description: 'Allow access to the default target org and "my-alias" with only the "data" toolset',
105
114
  command: '<%= config.bin %> --orgs DEFAULT_TARGET_DEV_HUB,my-alias --toolsets data',
106
115
  },
107
116
  {
108
117
  description: 'Allow access to 3 specific orgs and enable all toolsets',
109
- command: '<%= config.bin %> --orgs test-org@example.com,my-dev-hub,my-alias',
118
+ command: '<%= config.bin %> --toolsets all --orgs test-org@example.com,my-dev-hub,my-alias',
119
+ },
120
+ {
121
+ description: 'Start the server with the "data" toolset and also the "create_scratch_org" tool',
122
+ command: '<%= config.bin %> --orgs DEFAULT_TARGET_ORG --toolsets data --tools create_scratch_org',
110
123
  },
111
124
  {
112
- description: 'Allow tools that are not generally available (GA) to be registered with the server',
113
- command: '<%= config.bin %> --orgs DEFAULT_TARGET_ORG --allow-non-ga-tools',
125
+ description: 'Allow tools that are not generally available (NON-GA) to be registered with the server',
126
+ command: '<%= config.bin %> --toolsets all --orgs DEFAULT_TARGET_ORG --allow-non-ga-tools',
114
127
  },
115
128
  ];
116
129
  telemetry;
@@ -149,7 +162,7 @@ You can also use special values to control access to orgs:
149
162
  debug: flags.debug,
150
163
  },
151
164
  });
152
- await registerToolsets(flags.toolsets ?? ['all'], flags['dynamic-tools'] ?? false, flags['allow-non-ga-tools'] ?? false, server, services);
165
+ await registerToolsets(flags.toolsets ?? [], flags.tools ?? [], flags['dynamic-tools'] ?? false, flags['allow-non-ga-tools'] ?? false, server, services);
153
166
  const transport = new StdioServerTransport();
154
167
  await server.connect(transport);
155
168
  console.error(`✅ Salesforce MCP Server v${this.config.version} running on stdio`);
@@ -13,8 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { EnableToolsMcpTool } from './tools/sf-enable-tools.js';
17
- import { ListToolsMcpTool } from './tools/sf-list-tools.js';
16
+ import { EnableToolsMcpTool } from './tools/enable_tools.js';
17
+ import { ListToolsMcpTool } from './tools/list_tools.js';
18
18
  export function createDynamicServerTools(server) {
19
19
  return [new EnableToolsMcpTool(server), new ListToolsMcpTool()];
20
20
  }
package/lib/registry.js CHANGED
@@ -18,6 +18,7 @@ import { CodeAnalyzerMcpProvider } from '@salesforce/mcp-provider-code-analyzer'
18
18
  import { LwcExpertsMcpProvider } from '@salesforce/mcp-provider-lwc-experts';
19
19
  import { AuraExpertsMcpProvider } from '@salesforce/mcp-provider-aura-experts';
20
20
  import { MobileWebMcpProvider } from '@salesforce/mcp-provider-mobile-web';
21
+ import { DevOpsMcpProvider } from '@salesforce/mcp-provider-devops';
21
22
  /** -------- ADD McpProvider INSTANCES HERE ------------------------------------------------------------------------- */
22
23
  export const MCP_PROVIDER_REGISTRY = [
23
24
  new DxCoreMcpProvider(),
@@ -25,6 +26,7 @@ export const MCP_PROVIDER_REGISTRY = [
25
26
  new LwcExpertsMcpProvider(),
26
27
  new AuraExpertsMcpProvider(),
27
28
  new MobileWebMcpProvider(),
29
+ new DevOpsMcpProvider(),
28
30
  // Add new instances here
29
31
  ];
30
32
  //# sourceMappingURL=registry.js.map
@@ -32,7 +32,7 @@ export class EnableToolsMcpTool extends McpTool {
32
32
  return [Toolset.CORE];
33
33
  }
34
34
  getName() {
35
- return 'sf-enable-tools';
35
+ return 'enable_tools';
36
36
  }
37
37
  getConfig() {
38
38
  return {
@@ -40,7 +40,7 @@ export class EnableToolsMcpTool extends McpTool {
40
40
  description: `Enable one or more of the tools the Salesforce MCP server provides.
41
41
 
42
42
  AGENT INSTRUCTIONS:
43
- Use sf-list-all-tools first to learn what tools are available for enabling.
43
+ Use list_tools first to learn what tools are available for enabling.
44
44
  Once you have enabled the tool, you MUST invoke that tool to accomplish the user's original request - DO NOT USE A DIFFERENT TOOL OR THE COMMAND LINE.`,
45
45
  inputSchema: enableToolsParamsSchema.shape,
46
46
  outputSchema: undefined,
@@ -77,4 +77,4 @@ Once you have enabled the tool, you MUST invoke that tool to accomplish the user
77
77
  };
78
78
  }
79
79
  }
80
- //# sourceMappingURL=sf-enable-tools.js.map
80
+ //# sourceMappingURL=enable_tools.js.map
@@ -23,7 +23,7 @@ export class ListToolsMcpTool extends McpTool {
23
23
  return ReleaseState.GA;
24
24
  }
25
25
  getName() {
26
- return 'sf-list-tools';
26
+ return 'list_tools';
27
27
  }
28
28
  getConfig() {
29
29
  return {
@@ -37,9 +37,9 @@ ONLY use this tool if:
37
37
  2. You genuinely don't know what tools are available for a specific task
38
38
  3. You need to discover new tools for an unfamiliar use case
39
39
 
40
- If you find one or more tools you want to enable, call sf-enable-tools with all the tool names.
40
+ If you find one or more tools you want to enable, call enable_tools with all the tool names.
41
41
  Once you have enabled a tool, you MUST invoke the tool to accomplish the user's original request - DO NOT USE A DIFFERENT TOOL OR THE COMMAND LINE.
42
- Once a tool has been enabled, you do not need to call sf-list-tools again - instead, invoke the desired tool directly.`,
42
+ Once a tool has been enabled, you do not need to call list_tools again - instead, invoke the desired tool directly.`,
43
43
  inputSchema: undefined,
44
44
  outputSchema: undefined,
45
45
  annotations: {
@@ -60,4 +60,4 @@ Once a tool has been enabled, you do not need to call sf-list-tools again - inst
60
60
  };
61
61
  }
62
62
  }
63
- //# sourceMappingURL=sf-list-tools.js.map
63
+ //# sourceMappingURL=list_tools.js.map
package/lib/utils/auth.js CHANGED
@@ -100,34 +100,22 @@ export async function filterAllowedOrgs(orgs, allowList) {
100
100
  return false;
101
101
  });
102
102
  }
103
- const defaultOrgMaps = {
104
- [OrgConfigProperties.TARGET_ORG]: new Map(),
105
- [OrgConfigProperties.TARGET_DEV_HUB]: new Map(),
106
- };
107
103
  // Helper function to get default config for a property
108
104
  // Values are cached based on ConfigInfo path after first retrieval
109
105
  // This is to prevent manipulation of the config file after server start
110
106
  async function getDefaultConfig(property) {
111
- // If the directory changes, the singleton instance of ConfigAggregator is not updated.
112
- // It continues to use the old local or global config.
107
+ // If the directory changes, the ConfigAggregator singleton does not update.
108
+ // It continues to use the old local or global config instead.
109
+ // We call clearInstance on the singleton to read the new config.
113
110
  await ConfigAggregator.clearInstance();
114
111
  const aggregator = await ConfigAggregator.create();
115
112
  const config = aggregator.getInfo(property);
116
113
  const { value, path, key, location } = config;
117
114
  if (!value || typeof value !== 'string' || !path)
118
115
  return undefined;
119
- // Create a typed config object with the necessary properties
116
+ // Return an typed object with only the necessary properties
120
117
  // This reduces assertions and lowers context returned to the LLM
121
- const typedConfig = { key, location, value, path };
122
- if (defaultOrgMaps[property].has(path)) {
123
- // If the cache has the config's path set, use the cached config
124
- const cachedConfig = defaultOrgMaps[property].get(path);
125
- return cachedConfig;
126
- }
127
- else {
128
- defaultOrgMaps[property].set(path, typedConfig);
129
- return typedConfig;
130
- }
118
+ return { key, location, value, path };
131
119
  }
132
120
  export async function getDefaultTargetOrg() {
133
121
  return getDefaultConfig(OrgConfigProperties.TARGET_ORG);
@@ -1,4 +1,4 @@
1
1
  import { Toolset } from '@salesforce/mcp-provider-api';
2
2
  import { SfMcpServer } from '../sf-mcp-server.js';
3
3
  import { Services } from '../services.js';
4
- export declare function registerToolsets(toolsets: Array<Toolset | 'all'>, useDynamicTools: boolean, allowNonGaTools: boolean, server: SfMcpServer, services: Services): Promise<void>;
4
+ export declare function registerToolsets(toolsets: Array<Toolset | 'all'>, tools: string[], useDynamicTools: boolean, allowNonGaTools: boolean, server: SfMcpServer, services: Services): Promise<void>;
@@ -13,14 +13,26 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { EOL } from 'node:os';
16
17
  import { ux } from '@oclif/core';
17
18
  import { MCP_PROVIDER_API_VERSION, ReleaseState, Toolset, TOOLSETS, } from '@salesforce/mcp-provider-api';
18
19
  import { MCP_PROVIDER_REGISTRY } from '../registry.js';
19
20
  import { addTool, isToolRegistered } from '../utils/tools.js';
20
21
  import { createDynamicServerTools } from '../main-server-provider.js';
21
- export async function registerToolsets(toolsets, useDynamicTools, allowNonGaTools, server, services) {
22
+ export async function registerToolsets(toolsets, tools, useDynamicTools, allowNonGaTools, server, services) {
23
+ // If no toolsets, tools, or dynamic tools flag was passed, throw an error
24
+ // NOTE: In the future we will also want to check for Personas here
25
+ if (!toolsets.length && !tools.length && !useDynamicTools) {
26
+ throw new Error('Tool registration error. Start server with one of the following flags: --toolsets, --tools, --dynamic-tools');
27
+ }
22
28
  if (useDynamicTools) {
29
+ // If --dynamic-tools flag was passed, register the tools needed to handle dynamic tool registration
23
30
  const dynamicTools = createDynamicServerTools(server);
31
+ // This should always be true because using `--dynamic-tools` and `--toolsets` is blocked.
32
+ // If that doesn't change after GA, this can be just `toolsets.push('all')`
33
+ const isAllToolsetEnabled = toolsets.includes('all');
34
+ if (!isAllToolsetEnabled)
35
+ toolsets.push('all');
24
36
  ux.stderr('Registering dynamic tools.');
25
37
  // eslint-disable-next-line no-await-in-loop
26
38
  await registerTools(dynamicTools, server, useDynamicTools, allowNonGaTools);
@@ -30,16 +42,37 @@ export async function registerToolsets(toolsets, useDynamicTools, allowNonGaTool
30
42
  }
31
43
  const toolsetsToEnable = toolsets.includes('all')
32
44
  ? new Set(TOOLSETS)
45
+ // CORE toolset is always enabled
33
46
  : new Set([Toolset.CORE, ...toolsets]);
34
- const newToolRegistry = await createToolRegistryFromProviders(MCP_PROVIDER_REGISTRY, services);
47
+ const toolsetRegistry = await createToolRegistryFromProviders(MCP_PROVIDER_REGISTRY, services);
48
+ ux.stderr('REGISTERING TOOLSETS (--toolsets)');
35
49
  for (const toolset of TOOLSETS) {
36
50
  if (toolsetsToEnable.has(toolset)) {
37
- ux.stderr(`Registering tools from the '${toolset}' toolset.`);
51
+ ux.stderr(`Registering toolset: '${toolset}'`);
38
52
  // eslint-disable-next-line no-await-in-loop
39
- await registerTools(newToolRegistry[toolset], server, useDynamicTools, allowNonGaTools);
53
+ await registerTools(toolsetRegistry[toolset], server, useDynamicTools, allowNonGaTools);
40
54
  }
41
55
  else {
42
- ux.stderr(`Skipping registration of the tools from the '${toolset}' toolset.`);
56
+ ux.stderr(`!! Skipping toolset: '${toolset}'`);
57
+ }
58
+ }
59
+ if (tools.length > 0) {
60
+ ux.stderr('REGISTERING TOOLS (--tools)');
61
+ // Build an array of available McpTools
62
+ const toolRegistry = Object.values(toolsetRegistry).flat();
63
+ // NOTE: This validation could be removed it we implemented Flags.option
64
+ const existingToolNames = new Set(toolRegistry.map(tool => tool.getName()));
65
+ // Validate that all requested tools exist
66
+ const invalidTools = tools.filter(toolName => !existingToolNames.has(toolName));
67
+ if (invalidTools.length > 0)
68
+ throw new Error(`Invalid tool names provided to --tools: "${invalidTools.join('", "')}"
69
+ Valid tools include:
70
+ - ${Array.from(existingToolNames).join(`${EOL}- `)}`);
71
+ for (const tool of toolRegistry) {
72
+ if (tools.includes(tool.getName())) {
73
+ // eslint-disable-next-line no-await-in-loop
74
+ await registerTools([tool], server, useDynamicTools, allowNonGaTools);
75
+ }
43
76
  }
44
77
  }
45
78
  }
@@ -61,7 +94,7 @@ async function registerTools(tools, server, useDynamicTools, allowNonGaTools) {
61
94
  registeredTool.disable();
62
95
  }
63
96
  else {
64
- ux.stderr(`* Registering tool '${tool.getName()}'.`);
97
+ ux.stderr(`-> Registering tool: '${tool.getName()}'`);
65
98
  }
66
99
  // eslint-disable-next-line no-await-in-loop
67
100
  await addTool(registeredTool, tool.getName());
@@ -91,10 +124,8 @@ async function createToolRegistryFromProviders(providers, services) {
91
124
  */
92
125
  function validateMcpProviderVersion(provider) {
93
126
  if (provider.getVersion().major !== MCP_PROVIDER_API_VERSION.major) {
94
- throw new Error(`The version '${provider
95
- .getVersion()
96
- .toString()}' for '${provider.getName()}' is incompatible with this MCP Server.\n` +
97
- `Expected the major version to be '${MCP_PROVIDER_API_VERSION.major}'.`);
127
+ throw new Error(`The version '${provider.getVersion().toString()}' for '${provider.getName()}' is incompatible with this MCP Server.
128
+ Expected the major version to be '${MCP_PROVIDER_API_VERSION.major}'.`);
98
129
  }
99
130
  }
100
131
  //# sourceMappingURL=registry-utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/mcp",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "description": "MCP Server for interacting with Salesforce instances",
5
5
  "bin": {
6
6
  "sf-mcp-server": "bin/run.js"
@@ -22,8 +22,6 @@
22
22
  "lint-fix": "yarn sf-lint --fix",
23
23
  "package": "yarn pack",
24
24
  "postpack": "sf-clean --ignore-signing-artifacts",
25
- "prepack": "sf-prepack",
26
- "prepare": "sf-install",
27
25
  "start": "yarn build && npm link && mcp-inspector sf-mcp-server",
28
26
  "test": "wireit",
29
27
  "test:only": "wireit"
@@ -42,18 +40,19 @@
42
40
  "package.json"
43
41
  ],
44
42
  "dependencies": {
45
- "@modelcontextprotocol/sdk": "^1.16.0",
43
+ "@modelcontextprotocol/sdk": "^1.18.0",
46
44
  "@oclif/core": "^4.5.1",
47
45
  "@salesforce/agents": "^0.15.4",
48
46
  "@salesforce/apex-node": "^8.2.1",
49
- "@salesforce/core": "^8.18.0",
47
+ "@salesforce/core": "^8.23.1",
50
48
  "@salesforce/kit": "^3.1.6",
51
- "@salesforce/mcp-provider-api": "0.2.2",
52
- "@salesforce/mcp-provider-dx-core": "0.2.4",
53
- "@salesforce/mcp-provider-code-analyzer": "0.0.2",
54
- "@salesforce/mcp-provider-lwc-experts": "0.1.1",
55
- "@salesforce/mcp-provider-aura-experts": "0.1.1",
56
- "@salesforce/mcp-provider-mobile-web": "0.0.3",
49
+ "@salesforce/mcp-provider-api": "0.4.0",
50
+ "@salesforce/mcp-provider-dx-core": "0.3.3",
51
+ "@salesforce/mcp-provider-code-analyzer": "0.1.0",
52
+ "@salesforce/mcp-provider-lwc-experts": "0.5.0",
53
+ "@salesforce/mcp-provider-aura-experts": "0.3.0",
54
+ "@salesforce/mcp-provider-mobile-web": "0.1.1",
55
+ "@salesforce/mcp-provider-devops": "0.1.1",
57
56
  "@salesforce/source-deploy-retrieve": "^12.22.0",
58
57
  "@salesforce/source-tracking": "^7.4.8",
59
58
  "@salesforce/telemetry": "^6.1.0",