@salesforce/mcp 0.2.9-qa.1 → 0.4.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 +11 -11
- package/lib/index.d.ts +4 -0
- package/lib/index.js +51 -6
- package/lib/sf-mcp-server.d.ts +32 -0
- package/lib/sf-mcp-server.js +82 -0
- package/lib/shared/params.js +1 -0
- package/lib/telemetry.d.ts +22 -0
- package/lib/telemetry.js +129 -0
- package/lib/tools/core/sf-get-username.d.ts +2 -2
- package/lib/tools/data/sf-query-org.d.ts +2 -2
- package/lib/tools/data/sf-query-org.js +2 -0
- package/lib/tools/metadata/sf-deploy-metadata.d.ts +2 -2
- package/lib/tools/metadata/sf-deploy-metadata.js +2 -0
- package/lib/tools/metadata/sf-retrieve-metadata.d.ts +2 -2
- package/lib/tools/metadata/sf-retrieve-metadata.js +2 -0
- package/lib/tools/orgs/sf-list-all-orgs.d.ts +9 -0
- package/lib/tools/orgs/sf-list-all-orgs.js +8 -2
- package/lib/tools/users/sf-assign-permission-set.d.ts +2 -2
- package/lib/tools/users/sf-assign-permission-set.js +2 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ MCP Server for Interacting with Salesforce Orgs
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@salesforce/mcp) [](https://opensource.org/license/apache-2-0)
|
|
6
6
|
|
|
7
|
-
## Overview of the Salesforce MCP Server (Developer Preview)
|
|
7
|
+
## Overview of the Salesforce DX MCP Server (Developer Preview)
|
|
8
8
|
|
|
9
|
-
The Salesforce MCP Server is a specialized Model Context Protocol (MCP) implementation designed to facilitate seamless interaction between large language models (LLMs) and Salesforce orgs. This MCP server provides a robust set of tools and capabilities that enable LLMs to read, manage, and operate Salesforce resources securely.
|
|
9
|
+
The Salesforce DX MCP Server is a specialized Model Context Protocol (MCP) implementation designed to facilitate seamless interaction between large language models (LLMs) and Salesforce orgs. This MCP server provides a robust set of tools and capabilities that enable LLMs to read, manage, and operate Salesforce resources securely.
|
|
10
10
|
|
|
11
11
|
Key Features:
|
|
12
12
|
|
|
@@ -16,11 +16,11 @@ Key Features:
|
|
|
16
16
|
- Granular access control with org allowlisting.
|
|
17
17
|
- Modular tool architecture for easy extensibility.
|
|
18
18
|
|
|
19
|
-
**NOTE**: The Salesforce MCP Server is available as a developer preview. The feature isn’t generally available unless or until Salesforce announces its general availability in documentation or in press releases or public statements. All commands, parameters, and other features are subject to change or deprecation at any time, with or without notice. Don't implement functionality developed with these commands or tools. As we continue to enhance and refine the implementation, the available functionality and tools may evolve. We welcome feedback and contributions to help shape the future of this project.
|
|
19
|
+
**NOTE**: The Salesforce DX MCP Server is available as a developer preview. The feature isn’t generally available unless or until Salesforce announces its general availability in documentation or in press releases or public statements. All commands, parameters, and other features are subject to change or deprecation at any time, with or without notice. Don't implement functionality developed with these commands or tools. As we continue to enhance and refine the implementation, the available functionality and tools may evolve. We welcome feedback and contributions to help shape the future of this project.
|
|
20
20
|
|
|
21
21
|
### Security Features
|
|
22
22
|
|
|
23
|
-
The Salesforce MCP Server was designed with security as a top priority.
|
|
23
|
+
The Salesforce DX MCP Server was designed with security as a top priority.
|
|
24
24
|
|
|
25
25
|
- **Uses TypeScript libraries directly**
|
|
26
26
|
|
|
@@ -47,9 +47,9 @@ The Salesforce MCP Server was designed with security as a top priority.
|
|
|
47
47
|
|
|
48
48
|
Want to jump in and see what all the fuss is about? Read on!
|
|
49
49
|
|
|
50
|
-
This example uses Visual Studio Code (VS Code) as the MCP client because it's a standard Salesforce DX development tool. After you configure it with the Salesforce MCP
|
|
50
|
+
This example uses Visual Studio Code (VS Code) as the MCP client because it's a standard Salesforce DX development tool. After you configure it with the Salesforce DX MCP Server, you then use GitHub Copilot and natural language to easily execute typical Salesforce DX development tasks, such as listing your authorized orgs, viewing org records, and deploying or retrieving metadata.
|
|
51
51
|
|
|
52
|
-
But you're not limited to using only VS Code and Copilot! You can [configure many other clients](README.md#configure-other-clients-to-use-the-salesforce-mcp-server) to use the Salesforce MCP
|
|
52
|
+
But you're not limited to using only VS Code and Copilot! You can [configure many other clients](README.md#configure-other-clients-to-use-the-salesforce-mcp-server) to use the Salesforce DX MCP Server, such as Cursor, Cline, Claude Desktop, Zed, Windsurf, and more.
|
|
53
53
|
|
|
54
54
|
**Before You Begin**
|
|
55
55
|
|
|
@@ -104,7 +104,7 @@ For the best getting-started experience, make sure that you have a Salesforce DX
|
|
|
104
104
|
|
|
105
105
|
## Configure Orgs and Toolsets
|
|
106
106
|
|
|
107
|
-
You configure the Salesforce MCP
|
|
107
|
+
You configure the Salesforce DX MCP Server by specifying at least one authorized org and an optional list of MCP toolsets.
|
|
108
108
|
|
|
109
109
|
### Configure Orgs
|
|
110
110
|
|
|
@@ -147,9 +147,9 @@ This sample snippet shows how to configure access to two orgs for which you spec
|
|
|
147
147
|
|
|
148
148
|
### Configure Toolsets
|
|
149
149
|
|
|
150
|
-
The Salesforce MCP Server supports **toolsets** - a way to selectively enable different groups of MCP tools based on your needs. This allows you to run the MCP server with only the tools you require, which in turn reduces the context.
|
|
150
|
+
The Salesforce DX MCP Server supports **toolsets** - a way to selectively enable different groups of MCP tools based on your needs. This allows you to run the MCP server with only the tools you require, which in turn reduces the context.
|
|
151
151
|
|
|
152
|
-
Use the `--toolsets` (or short name `-t`) argument to specify the toolsets when you configure the Salesforce MCP
|
|
152
|
+
Use the `--toolsets` (or short name `-t`) argument to specify the toolsets when you configure the Salesforce DX MCP Server. Separate multiple toolsets with commas. The `--toolsets` argument is optional; if you don't specify it, the MCP server is configured with all toolsets.
|
|
153
153
|
|
|
154
154
|
These are the available toolsets:
|
|
155
155
|
|
|
@@ -204,11 +204,11 @@ Includes these tools:
|
|
|
204
204
|
- `sf-deploy-metadata` - Deploys metadata from your DX project to an org.
|
|
205
205
|
- `sf-retrieve-metadata` - Retrieves metadata from your org to your DX project.
|
|
206
206
|
|
|
207
|
-
## Configure Other Clients to Use the Salesforce MCP Server
|
|
207
|
+
## Configure Other Clients to Use the Salesforce DX MCP Server
|
|
208
208
|
|
|
209
209
|
**Cursor**
|
|
210
210
|
|
|
211
|
-
To configure [Cursor](https://www.cursor.com/) to work with Salesforce MCP
|
|
211
|
+
To configure [Cursor](https://www.cursor.com/) to work with Salesforce DX MCP Server, add this snippet to your Cursor `mcp.json` file:
|
|
212
212
|
|
|
213
213
|
```json
|
|
214
214
|
{
|
package/lib/index.d.ts
CHANGED
|
@@ -5,10 +5,14 @@ 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<("data" | "metadata" | "all" | "users" | "orgs")[], import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
version: import("@oclif/core/interfaces").BooleanFlag<void>;
|
|
9
|
+
'no-telemetry': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
10
|
};
|
|
9
11
|
static examples: {
|
|
10
12
|
description: string;
|
|
11
13
|
command: string;
|
|
12
14
|
}[];
|
|
15
|
+
private telemetry?;
|
|
13
16
|
run(): Promise<void>;
|
|
17
|
+
protected catch(error: Error): Promise<void>;
|
|
14
18
|
}
|
package/lib/index.js
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
/* eslint-disable no-console */
|
|
17
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
17
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
18
|
import { Command, Flags, ux } from '@oclif/core';
|
|
20
19
|
import * as core from './tools/core/index.js';
|
|
@@ -23,7 +22,26 @@ import * as data from './tools/data/index.js';
|
|
|
23
22
|
import * as users from './tools/users/index.js';
|
|
24
23
|
import * as metadata from './tools/metadata/index.js';
|
|
25
24
|
import Cache from './shared/cache.js';
|
|
25
|
+
import { Telemetry } from './telemetry.js';
|
|
26
|
+
import { SfMcpServer } from './sf-mcp-server.js';
|
|
26
27
|
const TOOLSETS = ['all', 'orgs', 'data', 'users', 'metadata'];
|
|
28
|
+
/**
|
|
29
|
+
* Sanitizes an array of org usernames by replacing specific orgs with a placeholder.
|
|
30
|
+
* Special values (DEFAULT_TARGET_ORG, DEFAULT_TARGET_DEV_HUB, ALLOW_ALL_ORGS) are preserved.
|
|
31
|
+
*
|
|
32
|
+
* @param {string[]} input - Array of org identifiers to sanitize
|
|
33
|
+
* @returns {string} Comma-separated string of sanitized org identifiers
|
|
34
|
+
*/
|
|
35
|
+
function sanitizeOrgInput(input) {
|
|
36
|
+
return input
|
|
37
|
+
.map((org) => {
|
|
38
|
+
if (org === 'DEFAULT_TARGET_ORG' || org === 'DEFAULT_TARGET_DEV_HUB' || org === 'ALLOW_ALL_ORGS') {
|
|
39
|
+
return org;
|
|
40
|
+
}
|
|
41
|
+
return 'SANITIZED_ORG';
|
|
42
|
+
})
|
|
43
|
+
.join(', ');
|
|
44
|
+
}
|
|
27
45
|
export default class McpServerCommand extends Command {
|
|
28
46
|
static summary = 'Start the Salesforce MCP server';
|
|
29
47
|
static description = `This command starts the Model Context Protocol (MCP) server for Salesforce, allowing access to various tools and orgs.
|
|
@@ -45,7 +63,7 @@ You can also use special values to control access to orgs:
|
|
|
45
63
|
delimiter: ',',
|
|
46
64
|
parse: async (input) => {
|
|
47
65
|
if (input === 'ALLOW_ALL_ORGS') {
|
|
48
|
-
ux.warn('
|
|
66
|
+
ux.warn('ALLOW_ALL_ORGS is set. This allows access to all authenticated orgs. Use with caution.');
|
|
49
67
|
}
|
|
50
68
|
if (input === 'DEFAULT_TARGET_ORG' ||
|
|
51
69
|
input === 'DEFAULT_TARGET_DEV_HUB' ||
|
|
@@ -64,6 +82,10 @@ You can also use special values to control access to orgs:
|
|
|
64
82
|
delimiter: ',',
|
|
65
83
|
default: ['all'],
|
|
66
84
|
})(),
|
|
85
|
+
version: Flags.version(),
|
|
86
|
+
'no-telemetry': Flags.boolean({
|
|
87
|
+
summary: 'Disable telemetry',
|
|
88
|
+
}),
|
|
67
89
|
};
|
|
68
90
|
static examples = [
|
|
69
91
|
{
|
|
@@ -79,18 +101,30 @@ You can also use special values to control access to orgs:
|
|
|
79
101
|
command: '<%= config.bin %> --orgs test-org@example.com,my-dev-hub,my-alias',
|
|
80
102
|
},
|
|
81
103
|
];
|
|
104
|
+
telemetry;
|
|
82
105
|
async run() {
|
|
83
106
|
const { flags } = await this.parse(McpServerCommand);
|
|
107
|
+
if (!flags['no-telemetry']) {
|
|
108
|
+
this.telemetry = new Telemetry(this.config, {
|
|
109
|
+
toolsets: flags.toolsets.join(', '),
|
|
110
|
+
orgs: sanitizeOrgInput(flags.orgs),
|
|
111
|
+
});
|
|
112
|
+
await this.telemetry.start();
|
|
113
|
+
process.stdin.on('close', (err) => {
|
|
114
|
+
this.telemetry?.sendEvent(err ? 'SERVER_STOPPED_ERROR' : 'SERVER_STOPPED_SUCCESS');
|
|
115
|
+
this.telemetry?.stop();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
84
118
|
Cache.getInstance().set('allowedOrgs', new Set(flags.orgs));
|
|
85
119
|
this.logToStderr(`Allowed orgs:\n${flags.orgs.map((org) => `- ${org}`).join('\n')}`);
|
|
86
|
-
const server = new
|
|
120
|
+
const server = new SfMcpServer({
|
|
87
121
|
name: 'sf-mcp-server',
|
|
88
|
-
version:
|
|
122
|
+
version: this.config.version,
|
|
89
123
|
capabilities: {
|
|
90
124
|
resources: {},
|
|
91
125
|
tools: {},
|
|
92
126
|
},
|
|
93
|
-
});
|
|
127
|
+
}, { telemetry: this.telemetry });
|
|
94
128
|
// // TODO: Should we add annotations to our tools? https://modelcontextprotocol.io/docs/concepts/tools#tool-definition-structure
|
|
95
129
|
// // TODO: Move tool names into a shared file, that way if we reference them in multiple places, we can update them in one place
|
|
96
130
|
const enabledToolsets = new Set(flags.toolsets);
|
|
@@ -132,7 +166,18 @@ You can also use special values to control access to orgs:
|
|
|
132
166
|
}
|
|
133
167
|
const transport = new StdioServerTransport();
|
|
134
168
|
await server.connect(transport);
|
|
135
|
-
console.error(
|
|
169
|
+
console.error(`✅ Salesforce MCP Server v${this.config.version} running on stdio`);
|
|
170
|
+
}
|
|
171
|
+
async catch(error) {
|
|
172
|
+
if (!this.telemetry && !process.argv.includes('--no-telemetry')) {
|
|
173
|
+
this.telemetry = new Telemetry(this.config);
|
|
174
|
+
await this.telemetry.start();
|
|
175
|
+
}
|
|
176
|
+
this.telemetry?.sendEvent('START_ERROR', {
|
|
177
|
+
error: error.message,
|
|
178
|
+
stack: error.stack,
|
|
179
|
+
});
|
|
180
|
+
await super.catch(error);
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
183
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { Telemetry } from './telemetry.js';
|
|
5
|
+
type ToolMethodSignatures = {
|
|
6
|
+
tool: McpServer['tool'];
|
|
7
|
+
connect: McpServer['connect'];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A server implementation that extends the base MCP server with telemetry capabilities.
|
|
11
|
+
*
|
|
12
|
+
* The method overloads for `tool` are taken directly from the source code for the original McpServer. They're
|
|
13
|
+
* copied here so that the types don't get lost.
|
|
14
|
+
*
|
|
15
|
+
* @extends {McpServer}
|
|
16
|
+
*/
|
|
17
|
+
export declare class SfMcpServer extends McpServer implements ToolMethodSignatures {
|
|
18
|
+
/** Optional telemetry instance for tracking server events */
|
|
19
|
+
private telemetry?;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new SfMcpServer instance
|
|
22
|
+
*
|
|
23
|
+
* @param {Implementation} serverInfo - The server implementation details
|
|
24
|
+
* @param {ServerOptions & { telemetry?: Telemetry }} [options] - Optional server configuration including telemetry
|
|
25
|
+
*/
|
|
26
|
+
constructor(serverInfo: Implementation, options?: ServerOptions & {
|
|
27
|
+
telemetry?: Telemetry;
|
|
28
|
+
});
|
|
29
|
+
connect: McpServer['connect'];
|
|
30
|
+
tool: McpServer['tool'];
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
17
|
+
/**
|
|
18
|
+
* A server implementation that extends the base MCP server with telemetry capabilities.
|
|
19
|
+
*
|
|
20
|
+
* The method overloads for `tool` are taken directly from the source code for the original McpServer. They're
|
|
21
|
+
* copied here so that the types don't get lost.
|
|
22
|
+
*
|
|
23
|
+
* @extends {McpServer}
|
|
24
|
+
*/
|
|
25
|
+
export class SfMcpServer extends McpServer {
|
|
26
|
+
/** Optional telemetry instance for tracking server events */
|
|
27
|
+
telemetry;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new SfMcpServer instance
|
|
30
|
+
*
|
|
31
|
+
* @param {Implementation} serverInfo - The server implementation details
|
|
32
|
+
* @param {ServerOptions & { telemetry?: Telemetry }} [options] - Optional server configuration including telemetry
|
|
33
|
+
*/
|
|
34
|
+
constructor(serverInfo, options) {
|
|
35
|
+
super(serverInfo, options);
|
|
36
|
+
this.telemetry = options?.telemetry;
|
|
37
|
+
this.server.oninitialized = () => {
|
|
38
|
+
const clientInfo = this.server.getClientVersion();
|
|
39
|
+
if (clientInfo) {
|
|
40
|
+
this.telemetry?.addAttributes({
|
|
41
|
+
clientName: clientInfo.name,
|
|
42
|
+
clientVersion: clientInfo.version,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
this.telemetry?.sendEvent('SERVER_START_SUCCESS');
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
connect = async (transport) => {
|
|
49
|
+
try {
|
|
50
|
+
await super.connect(transport);
|
|
51
|
+
if (!this.isConnected()) {
|
|
52
|
+
this.telemetry?.sendEvent('SERVER_START_ERROR', {
|
|
53
|
+
error: 'Server not connected',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.telemetry?.sendEvent('SERVER_START_ERROR', {
|
|
59
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
60
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
tool = (name, ...rest) => {
|
|
65
|
+
// Given the signature of the tool function, the last argument is always the callback
|
|
66
|
+
const cb = rest[rest.length - 1];
|
|
67
|
+
const wrappedCb = async (args) => {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const result = await cb(args);
|
|
70
|
+
const runtimeMs = Date.now() - startTime;
|
|
71
|
+
this.telemetry?.sendEvent('TOOL_CALLED', {
|
|
72
|
+
name,
|
|
73
|
+
runtimeMs,
|
|
74
|
+
isError: result.isError,
|
|
75
|
+
});
|
|
76
|
+
return result;
|
|
77
|
+
};
|
|
78
|
+
// @ts-expect-error because we no longer know what the type of rest is
|
|
79
|
+
return super.tool(name, ...rest.slice(0, -1), wrappedCb);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=sf-mcp-server.js.map
|
package/lib/shared/params.js
CHANGED
|
@@ -23,6 +23,7 @@ export const usernameOrAliasParam = z.string()
|
|
|
23
23
|
|
|
24
24
|
AGENT INSTRUCTIONS:
|
|
25
25
|
If it is not clear what username or alias is, run the #sf-get-username tool.
|
|
26
|
+
NEVER guess or make-up a username or alias, use #sf-get-username if you are not sure.
|
|
26
27
|
DO NOT use #sf-get-username if the user mentions an alias or username, like "for my an-alias org" or "for my test-prgelc2petd9@example.com org".
|
|
27
28
|
|
|
28
29
|
USAGE:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Attributes } from '@salesforce/telemetry';
|
|
2
|
+
import { Config } from '@oclif/core';
|
|
3
|
+
export declare class Telemetry {
|
|
4
|
+
private readonly config;
|
|
5
|
+
private attributes;
|
|
6
|
+
/**
|
|
7
|
+
* A unique identifier for the session.
|
|
8
|
+
*/
|
|
9
|
+
private sessionId;
|
|
10
|
+
/**
|
|
11
|
+
* The unique identifier generated for the user by the `sf` CLI.
|
|
12
|
+
* If it doesn't exist, or we can't read it, we'll generate a new one.
|
|
13
|
+
*/
|
|
14
|
+
private cliId;
|
|
15
|
+
private started;
|
|
16
|
+
private reporter?;
|
|
17
|
+
constructor(config: Config, attributes?: Attributes);
|
|
18
|
+
addAttributes(attributes: Attributes): void;
|
|
19
|
+
sendEvent(eventName: string, attributes?: Attributes): void;
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
stop(): void;
|
|
22
|
+
}
|
package/lib/telemetry.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
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 { randomBytes } from 'node:crypto';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { TelemetryReporter } from '@salesforce/telemetry';
|
|
20
|
+
import { warn } from '@oclif/core/ux';
|
|
21
|
+
const PROJECT = 'salesforce-mcp-server';
|
|
22
|
+
const APP_INSIGHTS_KEY = 'InstrumentationKey=2ca64abb-6123-4c7b-bd9e-4fe73e71fe9c;IngestionEndpoint=https://eastus-1.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/;ApplicationId=ecd8fa7a-0e0d-4109-94db-4d7878ada862';
|
|
23
|
+
const generateRandomId = () => randomBytes(20).toString('hex');
|
|
24
|
+
const getCliId = (cacheDir) => {
|
|
25
|
+
// We need to find sf's cache directory and read the CLIID.txt file from there.
|
|
26
|
+
// The problem is that sf's cache directory is OS specific and we don't want to
|
|
27
|
+
// hardcode all the potential paths. oclif does this for us already during startup
|
|
28
|
+
// so we can simply replace sf-mcp-server with sf in the cache directory path and
|
|
29
|
+
// end up with the correct OS specific path.
|
|
30
|
+
//
|
|
31
|
+
// The only downside to this approach is that the user could have a different
|
|
32
|
+
// cache directory set via env var. In that case, we'll just generate a new CLIID.
|
|
33
|
+
// This is a very rare case and we can live with it for now.
|
|
34
|
+
const sfCacheDir = cacheDir.replace('sf-mcp-server', 'sf');
|
|
35
|
+
const cliIdPath = join(sfCacheDir, 'CLIID.txt');
|
|
36
|
+
try {
|
|
37
|
+
return readFileSync(cliIdPath, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return generateRandomId();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
class McpTelemetryReporter extends TelemetryReporter {
|
|
44
|
+
/**
|
|
45
|
+
* TelemetryReporter references sf's config to determine if telemetry is enabled.
|
|
46
|
+
* We want to always send telemetry events, so we override the method to always return true.
|
|
47
|
+
* This is okay to do since the Telemetry class won't be instantiated in the MCP server if telemetry is disabled.
|
|
48
|
+
*
|
|
49
|
+
* @returns true
|
|
50
|
+
*/
|
|
51
|
+
// eslint-disable-next-line class-methods-use-this
|
|
52
|
+
isSfdxTelemetryEnabled() {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class Telemetry {
|
|
57
|
+
config;
|
|
58
|
+
attributes;
|
|
59
|
+
/**
|
|
60
|
+
* A unique identifier for the session.
|
|
61
|
+
*/
|
|
62
|
+
sessionId;
|
|
63
|
+
/**
|
|
64
|
+
* The unique identifier generated for the user by the `sf` CLI.
|
|
65
|
+
* If it doesn't exist, or we can't read it, we'll generate a new one.
|
|
66
|
+
*/
|
|
67
|
+
cliId;
|
|
68
|
+
started = false;
|
|
69
|
+
reporter;
|
|
70
|
+
constructor(config, attributes = {}) {
|
|
71
|
+
this.config = config;
|
|
72
|
+
this.attributes = attributes;
|
|
73
|
+
warn('You acknowledge and agree that the MCP server may collect usage information, user environment, and crash reports for the purposes of providing services or functions that are relevant to use of the MCP server and product improvements.');
|
|
74
|
+
this.sessionId = generateRandomId();
|
|
75
|
+
this.cliId = getCliId(config.cacheDir);
|
|
76
|
+
}
|
|
77
|
+
addAttributes(attributes) {
|
|
78
|
+
this.attributes = { ...this.attributes, ...attributes };
|
|
79
|
+
}
|
|
80
|
+
sendEvent(eventName, attributes) {
|
|
81
|
+
try {
|
|
82
|
+
this.reporter?.sendTelemetryEvent(eventName, {
|
|
83
|
+
...this.attributes,
|
|
84
|
+
...attributes,
|
|
85
|
+
// Identifiers
|
|
86
|
+
sessionId: this.sessionId,
|
|
87
|
+
cliId: this.cliId,
|
|
88
|
+
// System information
|
|
89
|
+
version: this.config.version,
|
|
90
|
+
platform: this.config.platform,
|
|
91
|
+
arch: this.config.arch,
|
|
92
|
+
nodeVersion: process.version,
|
|
93
|
+
nodeEnv: process.env.NODE_ENV,
|
|
94
|
+
origin: this.config.userAgent,
|
|
95
|
+
// Timestamps
|
|
96
|
+
date: new Date().toUTCString(),
|
|
97
|
+
timestamp: String(Date.now()),
|
|
98
|
+
processUptime: process.uptime() * 1000,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* empty */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async start() {
|
|
106
|
+
if (this.started)
|
|
107
|
+
return;
|
|
108
|
+
this.started = true;
|
|
109
|
+
try {
|
|
110
|
+
this.reporter = await McpTelemetryReporter.create({
|
|
111
|
+
project: PROJECT,
|
|
112
|
+
key: APP_INSIGHTS_KEY,
|
|
113
|
+
userId: this.cliId,
|
|
114
|
+
waitForConnection: true,
|
|
115
|
+
});
|
|
116
|
+
this.reporter.start();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// connection probably failed, but we can continue without telemetry
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
stop() {
|
|
123
|
+
if (!this.started)
|
|
124
|
+
return;
|
|
125
|
+
this.started = false;
|
|
126
|
+
this.reporter?.stop();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { SfMcpServer } from '../../sf-mcp-server.js';
|
|
3
3
|
export declare const getUsernameParamsSchema: z.ZodObject<{
|
|
4
4
|
defaultTargetOrg: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
5
5
|
defaultDevHub: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
@@ -14,4 +14,4 @@ export declare const getUsernameParamsSchema: z.ZodObject<{
|
|
|
14
14
|
defaultDevHub?: boolean | undefined;
|
|
15
15
|
}>;
|
|
16
16
|
export type GetUsernameParamsSchema = z.infer<typeof getUsernameParamsSchema>;
|
|
17
|
-
export declare const registerToolGetUsername: (server:
|
|
17
|
+
export declare const registerToolGetUsername: (server: SfMcpServer) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { SfMcpServer } from '../../sf-mcp-server.js';
|
|
3
3
|
export declare const queryOrgParamsSchema: z.ZodObject<{
|
|
4
4
|
query: z.ZodString;
|
|
5
5
|
usernameOrAlias: z.ZodString;
|
|
@@ -14,4 +14,4 @@ export declare const queryOrgParamsSchema: z.ZodObject<{
|
|
|
14
14
|
usernameOrAlias: string;
|
|
15
15
|
}>;
|
|
16
16
|
export type QueryOrgOptions = z.infer<typeof queryOrgParamsSchema>;
|
|
17
|
-
export declare const registerToolQueryOrg: (server:
|
|
17
|
+
export declare const registerToolQueryOrg: (server: SfMcpServer) => void;
|
|
@@ -37,6 +37,8 @@ export const queryOrgParamsSchema = z.object({
|
|
|
37
37
|
export const registerToolQueryOrg = (server) => {
|
|
38
38
|
server.tool('sf-query-org', 'Run a SOQL query against a Salesforce org.', queryOrgParamsSchema.shape, async ({ query, usernameOrAlias, directory }) => {
|
|
39
39
|
try {
|
|
40
|
+
if (!usernameOrAlias)
|
|
41
|
+
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
40
42
|
process.chdir(directory);
|
|
41
43
|
const connection = await getConnection(usernameOrAlias);
|
|
42
44
|
const result = await connection.query(query);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { SfMcpServer } from '../../sf-mcp-server.js';
|
|
3
3
|
declare const deployMetadataParams: z.ZodObject<{
|
|
4
4
|
sourceDir: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
5
5
|
manifest: z.ZodOptional<z.ZodString>;
|
|
@@ -23,5 +23,5 @@ declare const deployMetadataParams: z.ZodObject<{
|
|
|
23
23
|
apexTests?: string[] | undefined;
|
|
24
24
|
}>;
|
|
25
25
|
export type DeployMetadata = z.infer<typeof deployMetadataParams>;
|
|
26
|
-
export declare const registerToolDeployMetadata: (server:
|
|
26
|
+
export declare const registerToolDeployMetadata: (server: SfMcpServer) => void;
|
|
27
27
|
export {};
|
|
@@ -89,6 +89,8 @@ Deploy X to my org and run A,B and C apex tests.
|
|
|
89
89
|
if (sourceDir && manifest) {
|
|
90
90
|
return textResponse("You can't specify both `sourceDir` and `manifest` parameters.", true);
|
|
91
91
|
}
|
|
92
|
+
if (!usernameOrAlias)
|
|
93
|
+
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
92
94
|
// needed for org allowlist to work
|
|
93
95
|
process.chdir(directory);
|
|
94
96
|
const connection = await getConnection(usernameOrAlias);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const registerToolRetrieveMetadata: (server:
|
|
1
|
+
import { SfMcpServer } from '../../sf-mcp-server.js';
|
|
2
|
+
export declare const registerToolRetrieveMetadata: (server: SfMcpServer) => void;
|
|
@@ -59,6 +59,8 @@ Retrieve X metadata from my org
|
|
|
59
59
|
if (sourceDir && manifest) {
|
|
60
60
|
return textResponse("You can't specify both `sourceDir` and `manifest` parameters.", true);
|
|
61
61
|
}
|
|
62
|
+
if (!usernameOrAlias)
|
|
63
|
+
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
62
64
|
// needed for org allowlist to work
|
|
63
65
|
process.chdir(directory);
|
|
64
66
|
const connection = await getConnection(usernameOrAlias);
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
export declare const listAllOrgsParamsSchema: z.ZodObject<{
|
|
4
|
+
directory: z.ZodEffects<z.ZodString, string, string>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
directory: string;
|
|
7
|
+
}, {
|
|
8
|
+
directory: string;
|
|
9
|
+
}>;
|
|
10
|
+
export type ListAllOrgsOptions = z.infer<typeof listAllOrgsParamsSchema>;
|
|
2
11
|
export declare const registerToolListAllOrgs: (server: McpServer) => void;
|
|
@@ -13,19 +13,24 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
+
import { z } from 'zod';
|
|
16
17
|
import { getAllAllowedOrgs } from '../../shared/auth.js';
|
|
17
18
|
import { textResponse } from '../../shared/utils.js';
|
|
19
|
+
import { directoryParam } from '../../shared/params.js';
|
|
18
20
|
/*
|
|
19
21
|
* List all Salesforce orgs
|
|
20
22
|
*
|
|
21
23
|
* Lists all configured Salesforce orgs.
|
|
22
24
|
*
|
|
23
25
|
* Parameters:
|
|
24
|
-
* -
|
|
26
|
+
* - directory: directory to change to before running the command
|
|
25
27
|
*
|
|
26
28
|
* Returns:
|
|
27
29
|
* - textResponse: List of configured Salesforce orgs
|
|
28
30
|
*/
|
|
31
|
+
export const listAllOrgsParamsSchema = z.object({
|
|
32
|
+
directory: directoryParam,
|
|
33
|
+
});
|
|
29
34
|
export const registerToolListAllOrgs = (server) => {
|
|
30
35
|
server.tool('sf-list-all-orgs', `Lists all configured Salesforce orgs.
|
|
31
36
|
|
|
@@ -36,8 +41,9 @@ Example usage:
|
|
|
36
41
|
Can you list all Salesforce orgs for me
|
|
37
42
|
List all Salesforce orgs
|
|
38
43
|
List all orgs
|
|
39
|
-
`,
|
|
44
|
+
`, listAllOrgsParamsSchema.shape, async ({ directory }) => {
|
|
40
45
|
try {
|
|
46
|
+
process.chdir(directory);
|
|
41
47
|
const orgs = await getAllAllowedOrgs();
|
|
42
48
|
return textResponse(`List of configured Salesforce orgs:\n\n${JSON.stringify(orgs, null, 2)}`);
|
|
43
49
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
1
|
import { z } from 'zod';
|
|
2
|
+
import { SfMcpServer } from '../../sf-mcp-server.js';
|
|
3
3
|
export declare const assignPermissionSetParamsSchema: z.ZodObject<{
|
|
4
4
|
permissionSetName: z.ZodString;
|
|
5
5
|
usernameOrAlias: z.ZodString;
|
|
@@ -17,4 +17,4 @@ export declare const assignPermissionSetParamsSchema: z.ZodObject<{
|
|
|
17
17
|
onBehalfOf?: string | undefined;
|
|
18
18
|
}>;
|
|
19
19
|
export type AssignPermissionSetOptions = z.infer<typeof assignPermissionSetParamsSchema>;
|
|
20
|
-
export declare const registerToolAssignPermissionSet: (server:
|
|
20
|
+
export declare const registerToolAssignPermissionSet: (server: SfMcpServer) => void;
|
|
@@ -59,6 +59,8 @@ Set the permission set MyPermSet on behalf of my-alias.`),
|
|
|
59
59
|
export const registerToolAssignPermissionSet = (server) => {
|
|
60
60
|
server.tool('sf-assign-permission-set', 'Assign a permission set to one or more org users.', assignPermissionSetParamsSchema.shape, async ({ permissionSetName, usernameOrAlias, onBehalfOf, directory }) => {
|
|
61
61
|
try {
|
|
62
|
+
if (!usernameOrAlias)
|
|
63
|
+
return textResponse('The usernameOrAlias parameter is required, if the user did not specify one use the #sf-get-username tool', true);
|
|
62
64
|
process.chdir(directory);
|
|
63
65
|
// We build the connection from the usernameOrAlias
|
|
64
66
|
const connection = await getConnection(usernameOrAlias);
|