@salesforce/mcp 0.11.4 → 0.13.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/README.md CHANGED
@@ -174,6 +174,12 @@ This example shows how to enable the `data`, `orgs`, and `metadata` toolsets whe
174
174
  }
175
175
  ```
176
176
 
177
+ #### Dynamic Tools (Experimental)
178
+
179
+ The `--dynamic-tools` flag enables dynamic tool discovery and loading. When this flag is set, the MCP server starts with a minimal set of core tools and will load new tools as the need arises. This is useful for reducing initial context size and improving LLM performance.
180
+
181
+ **NOTE:** This feature works in VSCode and Cline but may not work in other environments.
182
+
177
183
  #### Core Toolset (always enabled)
178
184
 
179
185
  Includes this tool:
package/lib/index.d.ts CHANGED
@@ -4,10 +4,11 @@ export default class McpServerCommand extends Command {
4
4
  static description: string;
5
5
  static flags: {
6
6
  orgs: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
7
- toolsets: import("@oclif/core/interfaces").OptionFlag<("data" | "experimental" | "metadata" | "all" | "users" | "orgs" | "testing")[], import("@oclif/core/interfaces").CustomOptions>;
7
+ toolsets: import("@oclif/core/interfaces").OptionFlag<("data" | "experimental" | "metadata" | "all" | "users" | "orgs" | "testing")[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  version: import("@oclif/core/interfaces").BooleanFlag<void>;
9
9
  'no-telemetry': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ 'dynamic-tools': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  static examples: {
13
14
  description: string;
package/lib/index.js CHANGED
@@ -22,10 +22,11 @@ import * as data from './tools/data/index.js';
22
22
  import * as users from './tools/users/index.js';
23
23
  import * as testing from './tools/testing/index.js';
24
24
  import * as metadata from './tools/metadata/index.js';
25
+ import * as dynamic from './tools/dynamic/index.js';
25
26
  import Cache from './shared/cache.js';
26
27
  import { Telemetry } from './telemetry.js';
27
28
  import { SfMcpServer } from './sf-mcp-server.js';
28
- const TOOLSETS = ['all', 'testing', 'orgs', 'data', 'users', 'metadata', 'experimental'];
29
+ import { determineToolsetsToEnable, TOOLSETS } from './shared/tools.js';
29
30
  /**
30
31
  * Sanitizes an array of org usernames by replacing specific orgs with a placeholder.
31
32
  * Special values (DEFAULT_TARGET_ORG, DEFAULT_TARGET_DEV_HUB, ALLOW_ALL_ORGS) are preserved.
@@ -76,12 +77,12 @@ You can also use special values to control access to orgs:
76
77
  },
77
78
  }),
78
79
  toolsets: Flags.option({
79
- options: TOOLSETS,
80
+ options: ['all', ...TOOLSETS],
80
81
  char: 't',
81
82
  summary: 'Toolset to enable',
82
83
  multiple: true,
83
84
  delimiter: ',',
84
- default: ['all'],
85
+ exclusive: ['dynamic-toolsets'],
85
86
  })(),
86
87
  version: Flags.version(),
87
88
  'no-telemetry': Flags.boolean({
@@ -90,6 +91,11 @@ You can also use special values to control access to orgs:
90
91
  debug: Flags.boolean({
91
92
  summary: 'Enable debug logging',
92
93
  }),
94
+ 'dynamic-tools': Flags.boolean({
95
+ summary: 'Enable dynamic toolsets',
96
+ char: 'd',
97
+ exclusive: ['toolsets'],
98
+ }),
93
99
  };
94
100
  static examples = [
95
101
  {
@@ -110,7 +116,7 @@ You can also use special values to control access to orgs:
110
116
  const { flags } = await this.parse(McpServerCommand);
111
117
  if (!flags['no-telemetry']) {
112
118
  this.telemetry = new Telemetry(this.config, {
113
- toolsets: flags.toolsets.join(', '),
119
+ toolsets: (flags.toolsets ?? []).join(', '),
114
120
  orgs: sanitizeOrgInput(flags.orgs),
115
121
  });
116
122
  await this.telemetry.start();
@@ -119,7 +125,7 @@ You can also use special values to control access to orgs:
119
125
  this.telemetry?.stop();
120
126
  });
121
127
  }
122
- Cache.getInstance().set('allowedOrgs', new Set(flags.orgs));
128
+ await Cache.safeSet('allowedOrgs', new Set(flags.orgs));
123
129
  this.logToStderr(`Allowed orgs:\n${flags.orgs.map((org) => `- ${org}`).join('\n')}`);
124
130
  const server = new SfMcpServer({
125
131
  name: 'sf-mcp-server',
@@ -128,20 +134,34 @@ You can also use special values to control access to orgs:
128
134
  resources: {},
129
135
  tools: {},
130
136
  },
131
- }, { telemetry: this.telemetry });
132
- const enabledToolsets = new Set(flags.toolsets);
133
- const all = enabledToolsets.has('all');
137
+ }, {
138
+ telemetry: this.telemetry,
139
+ dynamicTools: flags['dynamic-tools'] ?? false,
140
+ });
141
+ const toolsetsToEnable = determineToolsetsToEnable(flags.toolsets ?? ['all'], flags['dynamic-tools'] ?? false);
134
142
  // ************************
135
143
  // CORE TOOLS (always on)
144
+ // If you're adding a new tool to the core toolset, you MUST add it to the `CORE_TOOLS` array in shared/tools.ts
145
+ // otherwise SfMcpServer will not register it.
146
+ //
147
+ // Long term, we will want to consider a more elegant solution for registering core tools.
136
148
  // ************************
137
149
  this.logToStderr('Registering core tools');
138
150
  // get username
139
151
  core.registerToolGetUsername(server);
140
152
  core.registerToolResume(server);
153
+ // DYNAMIC TOOLSETS
154
+ // ************************
155
+ if (toolsetsToEnable.dynamic) {
156
+ this.logToStderr('Registering dynamic tools');
157
+ // Individual tool management
158
+ dynamic.registerToolEnableTool(server);
159
+ dynamic.registerToolListTools(server);
160
+ }
141
161
  // ************************
142
162
  // ORG TOOLS
143
163
  // ************************
144
- if (all || enabledToolsets.has('orgs')) {
164
+ if (toolsetsToEnable.orgs) {
145
165
  this.logToStderr('Registering org tools');
146
166
  // list all orgs
147
167
  orgs.registerToolListAllOrgs(server);
@@ -149,7 +169,7 @@ You can also use special values to control access to orgs:
149
169
  // ************************
150
170
  // DATA TOOLS
151
171
  // ************************
152
- if (all || enabledToolsets.has('data')) {
172
+ if (toolsetsToEnable.data) {
153
173
  this.logToStderr('Registering data tools');
154
174
  // query org
155
175
  data.registerToolQueryOrg(server);
@@ -157,7 +177,7 @@ You can also use special values to control access to orgs:
157
177
  // ************************
158
178
  // USER TOOLS
159
179
  // ************************
160
- if (all || enabledToolsets.has('users')) {
180
+ if (toolsetsToEnable.users) {
161
181
  this.logToStderr('Registering user tools');
162
182
  // assign permission set
163
183
  users.registerToolAssignPermissionSet(server);
@@ -165,7 +185,7 @@ You can also use special values to control access to orgs:
165
185
  // ************************
166
186
  // testing TOOLS
167
187
  // ************************
168
- if (all || enabledToolsets.has('testing')) {
188
+ if (toolsetsToEnable.testing) {
169
189
  this.logToStderr('Registering testing tools');
170
190
  testing.registerToolTestApex(server);
171
191
  testing.registerToolTestAgent(server);
@@ -173,7 +193,7 @@ You can also use special values to control access to orgs:
173
193
  // ************************
174
194
  // METADATA TOOLS
175
195
  // ************************
176
- if (all || enabledToolsets.has('metadata')) {
196
+ if (toolsetsToEnable.metadata) {
177
197
  this.logToStderr('Registering metadata tools');
178
198
  // deploy metadata
179
199
  metadata.registerToolDeployMetadata(server);
@@ -186,8 +206,9 @@ You can also use special values to control access to orgs:
186
206
  // This toolset needs to be explicitly enabled ('all' will not include it)
187
207
  // Tools don't need to be in an 'experimental' directory, only registered here
188
208
  // ************************
189
- if (enabledToolsets.has('experimental')) {
209
+ if (toolsetsToEnable.experimental) {
190
210
  this.logToStderr('Registering experimental tools');
211
+ orgs.registerToolOrgOpen(server);
191
212
  // Add any experimental tools here
192
213
  }
193
214
  const transport = new StdioServerTransport();
@@ -15,6 +15,8 @@ export type SfMcpServerOptions = ServerOptions & {
15
15
  telemetry?: Telemetry;
16
16
  /** Optional rate limiting configuration */
17
17
  rateLimit?: Partial<RateLimitConfig>;
18
+ /** Enable dynamic tool loading */
19
+ dynamicTools?: boolean;
18
20
  };
19
21
  /**
20
22
  * A server implementation that extends the base MCP server with telemetry and rate limiting capabilities.
@@ -28,6 +30,7 @@ export declare class SfMcpServer extends McpServer implements ToolMethodSignatur
28
30
  private logger;
29
31
  /** Optional telemetry instance for tracking server events */
30
32
  private telemetry?;
33
+ private dynamicTools;
31
34
  /** Rate limiter for controlling tool call frequency */
32
35
  private rateLimiter?;
33
36
  /**
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
17
  import { Logger } from '@salesforce/core';
18
+ import { addTool, CORE_TOOLS } from './shared/tools.js';
18
19
  import { createRateLimiter } from './shared/rate-limiter.js';
19
20
  /**
20
21
  * A server implementation that extends the base MCP server with telemetry and rate limiting capabilities.
@@ -28,6 +29,7 @@ export class SfMcpServer extends McpServer {
28
29
  logger = Logger.childFromRoot('mcp-server');
29
30
  /** Optional telemetry instance for tracking server events */
30
31
  telemetry;
32
+ dynamicTools;
31
33
  /** Rate limiter for controlling tool call frequency */
32
34
  rateLimiter;
33
35
  /**
@@ -39,6 +41,7 @@ export class SfMcpServer extends McpServer {
39
41
  constructor(serverInfo, options) {
40
42
  super(serverInfo, options);
41
43
  this.telemetry = options?.telemetry;
44
+ this.dynamicTools = options?.dynamicTools ?? false;
42
45
  // Initialize rate limiter if configuration is provided
43
46
  if (options?.rateLimit !== undefined) {
44
47
  this.rateLimiter = createRateLimiter(options.rateLimit);
@@ -112,7 +115,16 @@ export class SfMcpServer extends McpServer {
112
115
  return result;
113
116
  };
114
117
  // @ts-expect-error because we no longer know what the type of rest is
115
- return super.tool(name, ...rest.slice(0, -1), wrappedCb);
118
+ const tool = super.tool(name, ...rest.slice(0, -1), wrappedCb);
119
+ if (this.dynamicTools) {
120
+ // Only disable the tool if it's not a core tool
121
+ if (!CORE_TOOLS.includes(name))
122
+ tool.disable();
123
+ addTool(tool, name).catch((error) => {
124
+ this.logger.error(`Failed to add tool ${name}:`, error);
125
+ });
126
+ }
127
+ return tool;
116
128
  };
117
129
  }
118
130
  //# sourceMappingURL=sf-mcp-server.js.map
@@ -95,7 +95,7 @@ export async function getAllAllowedOrgs() {
95
95
  const url = new URL(import.meta.url);
96
96
  const params = url.searchParams.get('orgs');
97
97
  const paramOrg = params ? params : undefined;
98
- const orgAllowList = paramOrg ? new Set([paramOrg]) : Cache.getInstance().get('allowedOrgs') ?? new Set();
98
+ const orgAllowList = paramOrg ? new Set([paramOrg]) : (await Cache.safeGet('allowedOrgs')) ?? new Set();
99
99
  // Get all orgs on the user's machine
100
100
  const allOrgs = await AuthInfo.listAllAuthorizations();
101
101
  // Sanitize the orgs to remove sensitive data
@@ -1,14 +1,38 @@
1
+ import { ToolInfo } from './types.js';
1
2
  type CacheContents = {
2
3
  allowedOrgs: Set<string>;
4
+ tools: ToolInfo[];
3
5
  };
4
6
  type ValueOf<T> = T[keyof T];
5
7
  /**
6
- * A simple cache for storing values that need to be accessed globally.
8
+ * A thread-safe cache providing generic Map operations with mutex protection.
9
+ * Offers atomic read, write, and update operations for concurrent access.
7
10
  */
8
11
  export default class Cache extends Map<keyof CacheContents, ValueOf<CacheContents>> {
9
12
  private static instance;
10
- constructor();
13
+ private static mutex;
14
+ private constructor();
15
+ /**
16
+ * Get the singleton instance of the Cache
17
+ * Creates a new instance if one doesn't exist
18
+ *
19
+ * @returns The singleton Cache instance
20
+ */
11
21
  static getInstance(): Cache;
12
- get(_key: 'allowedOrgs'): Set<string>;
22
+ /**
23
+ * Thread-safe atomic update operation
24
+ * Allows safe read-modify-write operations with mutex protection
25
+ */
26
+ static safeUpdate<K extends keyof CacheContents>(key: K, updateFn: (currentValue: CacheContents[K]) => CacheContents[K]): Promise<CacheContents[K]>;
27
+ /**
28
+ * Thread-safe atomic read operation
29
+ */
30
+ static safeGet<K extends keyof CacheContents>(key: K): Promise<CacheContents[K]>;
31
+ /**
32
+ * Thread-safe atomic write operation
33
+ */
34
+ static safeSet<K extends keyof CacheContents>(key: K, value: CacheContents[K]): Promise<void>;
35
+ get<K extends keyof CacheContents>(key: K): CacheContents[K];
36
+ private initialize;
13
37
  }
14
38
  export {};
@@ -13,23 +13,63 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ import { Mutex } from '@salesforce/core';
16
17
  /**
17
- * A simple cache for storing values that need to be accessed globally.
18
+ * A thread-safe cache providing generic Map operations with mutex protection.
19
+ * Offers atomic read, write, and update operations for concurrent access.
18
20
  */
19
21
  export default class Cache extends Map {
20
22
  static instance;
23
+ // Mutex for thread-safe cache operations
24
+ static mutex = new Mutex();
21
25
  constructor() {
22
26
  super();
23
- this.set('allowedOrgs', new Set());
27
+ this.initialize();
24
28
  }
29
+ /**
30
+ * Get the singleton instance of the Cache
31
+ * Creates a new instance if one doesn't exist
32
+ *
33
+ * @returns The singleton Cache instance
34
+ */
25
35
  static getInstance() {
26
- if (!Cache.instance) {
27
- Cache.instance = new Cache();
28
- }
29
- return Cache.instance;
36
+ return (Cache.instance ??= new Cache());
37
+ }
38
+ /**
39
+ * Thread-safe atomic update operation
40
+ * Allows safe read-modify-write operations with mutex protection
41
+ */
42
+ static async safeUpdate(key, updateFn) {
43
+ const cache = Cache.getInstance();
44
+ return Cache.mutex.lock(() => {
45
+ const currentValue = cache.get(key);
46
+ const newValue = updateFn(currentValue);
47
+ cache.set(key, newValue);
48
+ return newValue;
49
+ });
50
+ }
51
+ /**
52
+ * Thread-safe atomic read operation
53
+ */
54
+ static async safeGet(key) {
55
+ const cache = Cache.getInstance();
56
+ return Cache.mutex.lock(() => cache.get(key));
57
+ }
58
+ /**
59
+ * Thread-safe atomic write operation
60
+ */
61
+ static async safeSet(key, value) {
62
+ const cache = Cache.getInstance();
63
+ return Cache.mutex.lock(() => {
64
+ cache.set(key, value);
65
+ });
30
66
  }
31
67
  get(key) {
32
68
  return super.get(key);
33
69
  }
70
+ initialize() {
71
+ this.set('allowedOrgs', new Set());
72
+ this.set('tools', []);
73
+ }
34
74
  }
35
75
  //# sourceMappingURL=cache.js.map
@@ -0,0 +1,64 @@
1
+ import { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const TOOLSETS: readonly ["orgs", "data", "users", "metadata", "testing", "experimental"];
3
+ type Toolset = (typeof TOOLSETS)[number];
4
+ export declare const CORE_TOOLS: string[];
5
+ /**
6
+ * Determines which toolsets should be enabled based on the provided toolsets array and dynamic tools flag.
7
+ *
8
+ * @param {Array<Toolset | 'all'>} toolsets - Array of toolsets to enable. Can include 'all' to enable all non-experimental toolsets.
9
+ * @param {boolean} dynamicTools - Flag indicating whether dynamic tools should be enabled. When true, only core and dynamic toolsets are enabled.
10
+ * @returns {Record<Toolset | 'dynamic' | 'core', boolean>} Object mapping each toolset to a boolean indicating whether it should be enabled.
11
+ *
12
+ * @example
13
+ * // Enable all toolsets except experimental
14
+ * determineToolsetsToEnable(['all'], false)
15
+ * // Returns: { core: true, data: true, dynamic: false, experimental: false, metadata: true, orgs: true, testing: true, users: true }
16
+ *
17
+ * @example
18
+ * // Enable only dynamic tools
19
+ * determineToolsetsToEnable([], true)
20
+ * // Returns: { core: true, data: false, dynamic: true, experimental: false, metadata: false, orgs: false, testing: false, users: false }
21
+ *
22
+ * @example
23
+ * // Enable specific toolsets
24
+ * determineToolsetsToEnable(['data', 'users'], false)
25
+ * // Returns: { core: true, data: true, dynamic: false, experimental: false, metadata: false, orgs: false, testing: false, users: true }
26
+ */
27
+ export declare function determineToolsetsToEnable(toolsets: Array<Toolset | 'all'>, dynamicTools: boolean): Record<Toolset | 'dynamic' | 'core', boolean>;
28
+ /**
29
+ * Add a tool to the cache
30
+ */
31
+ export declare function addTool(tool: RegisteredTool, name: string): Promise<{
32
+ success: boolean;
33
+ message: string;
34
+ }>;
35
+ /**
36
+ * Enable an individual tool
37
+ */
38
+ export declare function enableTool(toolName: string): Promise<{
39
+ success: boolean;
40
+ message: string;
41
+ }>;
42
+ /**
43
+ * Disable an individual tool
44
+ */
45
+ export declare function disableTool(toolName: string): Promise<{
46
+ success: boolean;
47
+ message: string;
48
+ }>;
49
+ /**
50
+ * Get individual tool status
51
+ */
52
+ export declare function getToolStatus(toolName: string): Promise<{
53
+ enabled: boolean;
54
+ description: string;
55
+ } | undefined>;
56
+ /**
57
+ * List all individual tools with their status
58
+ */
59
+ export declare function listAllTools(): Promise<Array<{
60
+ name: string;
61
+ enabled: boolean;
62
+ description: string;
63
+ }>>;
64
+ export {};
@@ -0,0 +1,156 @@
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 Cache from './cache.js';
17
+ export const TOOLSETS = ['orgs', 'data', 'users', 'metadata', 'testing', 'experimental'];
18
+ /*
19
+ * These are tools that are always enabled at startup. They cannot be disabled and they cannot be dynamically enabled.
20
+ */
21
+ export const CORE_TOOLS = ['sf-get-username', 'sf-resume', 'sf-enable-tool', 'sf-list-tools'];
22
+ /**
23
+ * Determines which toolsets should be enabled based on the provided toolsets array and dynamic tools flag.
24
+ *
25
+ * @param {Array<Toolset | 'all'>} toolsets - Array of toolsets to enable. Can include 'all' to enable all non-experimental toolsets.
26
+ * @param {boolean} dynamicTools - Flag indicating whether dynamic tools should be enabled. When true, only core and dynamic toolsets are enabled.
27
+ * @returns {Record<Toolset | 'dynamic' | 'core', boolean>} Object mapping each toolset to a boolean indicating whether it should be enabled.
28
+ *
29
+ * @example
30
+ * // Enable all toolsets except experimental
31
+ * determineToolsetsToEnable(['all'], false)
32
+ * // Returns: { core: true, data: true, dynamic: false, experimental: false, metadata: true, orgs: true, testing: true, users: true }
33
+ *
34
+ * @example
35
+ * // Enable only dynamic tools
36
+ * determineToolsetsToEnable([], true)
37
+ * // Returns: { core: true, data: false, dynamic: true, experimental: false, metadata: false, orgs: false, testing: false, users: false }
38
+ *
39
+ * @example
40
+ * // Enable specific toolsets
41
+ * determineToolsetsToEnable(['data', 'users'], false)
42
+ * // Returns: { core: true, data: true, dynamic: false, experimental: false, metadata: false, orgs: false, testing: false, users: true }
43
+ */
44
+ export function determineToolsetsToEnable(toolsets, dynamicTools) {
45
+ if (dynamicTools) {
46
+ return {
47
+ core: true,
48
+ data: true,
49
+ dynamic: true,
50
+ experimental: false,
51
+ metadata: true,
52
+ orgs: true,
53
+ testing: true,
54
+ users: true,
55
+ };
56
+ }
57
+ if (toolsets.includes('all')) {
58
+ return {
59
+ core: true,
60
+ data: true,
61
+ dynamic: false,
62
+ experimental: false,
63
+ metadata: true,
64
+ orgs: true,
65
+ testing: true,
66
+ users: true,
67
+ };
68
+ }
69
+ return {
70
+ core: true,
71
+ data: toolsets.includes('data'),
72
+ dynamic: false,
73
+ experimental: toolsets.includes('experimental'),
74
+ metadata: toolsets.includes('metadata'),
75
+ orgs: toolsets.includes('orgs'),
76
+ testing: toolsets.includes('testing'),
77
+ users: toolsets.includes('users'),
78
+ };
79
+ }
80
+ /**
81
+ * Add a tool to the cache
82
+ */
83
+ export async function addTool(tool, name) {
84
+ const existingTools = await Cache.safeGet('tools');
85
+ // Check if tool already exists
86
+ const existingTool = existingTools.find((t) => t.name === name);
87
+ if (existingTool) {
88
+ return { success: false, message: `Tool ${name} already exists` };
89
+ }
90
+ await Cache.safeUpdate('tools', (toolsArray) => {
91
+ const newTool = {
92
+ tool,
93
+ name,
94
+ };
95
+ return [...toolsArray, newTool];
96
+ });
97
+ return { success: true, message: `Added tool ${name}` };
98
+ }
99
+ /**
100
+ * Enable an individual tool
101
+ */
102
+ export async function enableTool(toolName) {
103
+ const tools = await Cache.safeGet('tools');
104
+ const toolInfo = tools.find((t) => t.name === toolName);
105
+ if (!toolInfo) {
106
+ return { success: false, message: `Tool ${toolName} not found` };
107
+ }
108
+ if (toolInfo.tool.enabled) {
109
+ return { success: false, message: `Tool ${toolName} is already enabled` };
110
+ }
111
+ // Enable the tool directly
112
+ toolInfo.tool.enable();
113
+ return { success: true, message: `Tool ${toolName} enabled` };
114
+ }
115
+ /**
116
+ * Disable an individual tool
117
+ */
118
+ export async function disableTool(toolName) {
119
+ const tools = await Cache.safeGet('tools');
120
+ const toolInfo = tools.find((t) => t.name === toolName);
121
+ if (!toolInfo) {
122
+ return { success: false, message: `Tool ${toolName} not found` };
123
+ }
124
+ if (!toolInfo.tool.enabled) {
125
+ return { success: false, message: `Tool ${toolName} is already disabled` };
126
+ }
127
+ // Disable the tool directly
128
+ toolInfo.tool.disable();
129
+ return { success: true, message: `Tool ${toolName} disabled` };
130
+ }
131
+ /**
132
+ * Get individual tool status
133
+ */
134
+ export async function getToolStatus(toolName) {
135
+ const tools = await Cache.safeGet('tools');
136
+ const toolInfo = tools.find((t) => t.name === toolName);
137
+ if (!toolInfo) {
138
+ return;
139
+ }
140
+ return {
141
+ enabled: toolInfo.tool.enabled,
142
+ description: toolInfo.tool.description ?? '',
143
+ };
144
+ }
145
+ /**
146
+ * List all individual tools with their status
147
+ */
148
+ export async function listAllTools() {
149
+ const tools = await Cache.safeGet('tools');
150
+ return tools.map((toolInfo) => ({
151
+ name: toolInfo.name,
152
+ enabled: toolInfo.tool.enabled,
153
+ description: toolInfo.tool.description ?? '',
154
+ }));
155
+ }
156
+ //# sourceMappingURL=tools.js.map
@@ -1,5 +1,6 @@
1
1
  import { ConfigInfo } from '@salesforce/core';
2
2
  import { type Nullable } from '@salesforce/ts-types';
3
+ import { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
3
4
  export type ConfigInfoWithCache = {
4
5
  key: string;
5
6
  location?: ConfigInfo['location'];
@@ -26,3 +27,7 @@ export type ToolTextResponse = {
26
27
  text: string;
27
28
  }>;
28
29
  };
30
+ export type ToolInfo = {
31
+ tool: RegisteredTool;
32
+ name: string;
33
+ };
@@ -0,0 +1,2 @@
1
+ export { registerToolEnableTool } from './sf-enable-tool.js';
2
+ export { registerToolListTools } from './sf-list-tools.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 { registerToolEnableTool } from './sf-enable-tool.js';
17
+ export { registerToolListTools } from './sf-list-tools.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ import { SfMcpServer } from '../../sf-mcp-server.js';
2
+ export declare function registerToolEnableTool(server: SfMcpServer): void;
@@ -0,0 +1,38 @@
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 { enableTool } from '../../shared/tools.js';
19
+ const enableToolParamsSchema = z.object({
20
+ tool: z.string().describe('The name of the tool to enable'),
21
+ });
22
+ export function registerToolEnableTool(server) {
23
+ server.tool('sf-enable-tool', `Enable one of the tools the Salesforce MCP server provides.
24
+
25
+ AGENT INSTRUCTIONS:
26
+ use sf-list-all-tools first to learn what tools are available for enabling'`, enableToolParamsSchema.shape, {
27
+ title: 'Enable an individual tool',
28
+ readOnlyHint: true,
29
+ openWorldHint: false,
30
+ }, async ({ tool }) => {
31
+ const result = await enableTool(tool);
32
+ if (result.success) {
33
+ server.sendToolListChanged();
34
+ }
35
+ return textResponse(result.message, !result.success);
36
+ });
37
+ }
38
+ //# sourceMappingURL=sf-enable-tool.js.map
@@ -0,0 +1,2 @@
1
+ import { SfMcpServer } from '../../sf-mcp-server.js';
2
+ export declare function registerToolListTools(server: SfMcpServer): void;