delegate-sf-mcp 0.2.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/.eslintrc.json +20 -0
- package/LICENSE +24 -0
- package/README.md +76 -0
- package/auth.js +148 -0
- package/bin/config-helper.js +51 -0
- package/bin/mcp-salesforce.js +12 -0
- package/bin/setup.js +266 -0
- package/bin/status.js +134 -0
- package/docs/README.md +52 -0
- package/docs/step1.png +0 -0
- package/docs/step2.png +0 -0
- package/docs/step3.png +0 -0
- package/docs/step4.png +0 -0
- package/examples/README.md +35 -0
- package/package.json +16 -0
- package/scripts/README.md +30 -0
- package/src/auth/file-storage.js +447 -0
- package/src/auth/oauth.js +417 -0
- package/src/auth/token-manager.js +207 -0
- package/src/backup/manager.js +949 -0
- package/src/index.js +168 -0
- package/src/salesforce/client.js +388 -0
- package/src/sf-client.js +79 -0
- package/src/tools/auth.js +190 -0
- package/src/tools/backup.js +486 -0
- package/src/tools/create.js +109 -0
- package/src/tools/delegate-hygiene.js +268 -0
- package/src/tools/delegate-validate.js +212 -0
- package/src/tools/delegate-verify.js +143 -0
- package/src/tools/delete.js +72 -0
- package/src/tools/describe.js +132 -0
- package/src/tools/installation-info.js +656 -0
- package/src/tools/learn-context.js +1077 -0
- package/src/tools/learn.js +351 -0
- package/src/tools/query.js +82 -0
- package/src/tools/repair-credentials.js +77 -0
- package/src/tools/setup.js +120 -0
- package/src/tools/time_machine.js +347 -0
- package/src/tools/update.js +138 -0
- package/src/tools.js +214 -0
- package/src/utils/cache.js +120 -0
- package/src/utils/debug.js +52 -0
- package/src/utils/logger.js +19 -0
- package/tokens.json +8 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"es2022": true,
|
|
4
|
+
"node": true
|
|
5
|
+
},
|
|
6
|
+
"extends": [
|
|
7
|
+
"eslint:recommended"
|
|
8
|
+
],
|
|
9
|
+
"parserOptions": {
|
|
10
|
+
"ecmaVersion": 2022,
|
|
11
|
+
"sourceType": "module"
|
|
12
|
+
},
|
|
13
|
+
"rules": {
|
|
14
|
+
"indent": ["error", 2],
|
|
15
|
+
"quotes": ["error", "single"],
|
|
16
|
+
"semi": ["error", "always"],
|
|
17
|
+
"no-console": "off",
|
|
18
|
+
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
|
|
19
|
+
}
|
|
20
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Aionda GmbH
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Delegate SF MCP — v0.2
|
|
2
|
+
|
|
3
|
+
Nightly Salesforce hygiene sync. Human approval required before anything commits.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Salesforce Connected App
|
|
14
|
+
|
|
15
|
+
In Salesforce Setup → App Manager → New Connected App:
|
|
16
|
+
- Enable OAuth Settings: ✅
|
|
17
|
+
- Callback URL: `http://localhost:8482/callback`
|
|
18
|
+
- OAuth Scopes: `api`, `refresh_token`
|
|
19
|
+
- Save → wait 2-10 min to propagate
|
|
20
|
+
|
|
21
|
+
Copy the **Consumer Key** and **Consumer Secret**.
|
|
22
|
+
|
|
23
|
+
### 3. Authenticate
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node auth.js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Browser opens → log in → approve → tokens saved to `tokens.json`. Done.
|
|
30
|
+
|
|
31
|
+
### 4. Configure Claude Desktop
|
|
32
|
+
|
|
33
|
+
In `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"delegate-sf": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["/ABSOLUTE/PATH/TO/delegate-mcp/src/index.js"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Replace `/ABSOLUTE/PATH/TO/delegate-mcp` with the actual path.
|
|
47
|
+
|
|
48
|
+
### 5. Restart Claude Desktop
|
|
49
|
+
|
|
50
|
+
Quit fully (⌘Q) and reopen. The tools should appear.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Tools
|
|
55
|
+
|
|
56
|
+
| Tool | What it does |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `sf_query` | Run any SOQL query |
|
|
59
|
+
| `sf_get_record` | Get a record by ID |
|
|
60
|
+
| `sf_search` | SOSL search by name/keyword |
|
|
61
|
+
| `sf_describe` | Get field metadata for any object |
|
|
62
|
+
| `sf_create` | Create a record (runs verify first) |
|
|
63
|
+
| `sf_update` | Update a record |
|
|
64
|
+
| `sf_verify_exists` | Duplicate check before create |
|
|
65
|
+
| `sf_hygiene_score` | Account hygiene score 0–100 |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Re-authenticate
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node auth.js
|
|
73
|
+
# Answer 'y' to re-authenticate
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Tokens refresh automatically during normal use.
|
package/auth.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.js — Delegate SF MCP Auth
|
|
3
|
+
*
|
|
4
|
+
* Run once: node auth.js
|
|
5
|
+
* Browser opens → approve in Salesforce → tokens saved to tokens.json
|
|
6
|
+
* Then start the MCP: node src/index.js
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import jsforce from 'jsforce';
|
|
10
|
+
import http from 'http';
|
|
11
|
+
import { exec } from 'child_process';
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import readline from 'readline';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const TOKEN_FILE = path.join(__dirname, 'tokens.json');
|
|
19
|
+
const PORT = 8482;
|
|
20
|
+
const REDIRECT_URI = `http://localhost:${PORT}/callback`;
|
|
21
|
+
|
|
22
|
+
// ── Prompt helpers ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
25
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
26
|
+
|
|
27
|
+
function openBrowser(url) {
|
|
28
|
+
const cmd =
|
|
29
|
+
process.platform === 'darwin' ? `open "${url}"` :
|
|
30
|
+
process.platform === 'win32' ? `start "" "${url}"` :
|
|
31
|
+
`xdg-open "${url}"`;
|
|
32
|
+
exec(cmd, err => { if (err) console.log(`\nCould not auto-open browser. Visit manually:\n${url}\n`); });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
console.log('\n━━━ Delegate SF MCP — Auth Setup ━━━\n');
|
|
38
|
+
|
|
39
|
+
// Check for existing tokens
|
|
40
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
41
|
+
const existing = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf8'));
|
|
42
|
+
console.log(`Found existing tokens for: ${existing.instanceUrl}`);
|
|
43
|
+
const reauth = await ask('Re-authenticate? (y/N): ');
|
|
44
|
+
if (reauth.toLowerCase() !== 'y') {
|
|
45
|
+
console.log('Using existing tokens. Run: node src/index.js');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get credentials
|
|
51
|
+
console.log('\nYou need a Salesforce Connected App with:');
|
|
52
|
+
console.log(` Callback URL: ${REDIRECT_URI}`);
|
|
53
|
+
console.log(' OAuth Scopes: api, refresh_token\n');
|
|
54
|
+
|
|
55
|
+
const loginUrl = await ask('Login URL (press Enter for https://login.salesforce.com): ');
|
|
56
|
+
const clientId = await ask('Consumer Key: ');
|
|
57
|
+
const clientSecret = await ask('Consumer Secret: ');
|
|
58
|
+
rl.close();
|
|
59
|
+
|
|
60
|
+
const oauth2 = new jsforce.OAuth2({
|
|
61
|
+
loginUrl: loginUrl.trim() || 'https://login.salesforce.com',
|
|
62
|
+
clientId: clientId.trim(),
|
|
63
|
+
clientSecret: clientSecret.trim(),
|
|
64
|
+
redirectUri: REDIRECT_URI,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const authUrl = oauth2.getAuthorizationUrl({ scope: 'api refresh_token sfap_api mcp_api' });
|
|
68
|
+
|
|
69
|
+
// ── Local callback server ─────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const server = http.createServer(async (req, res) => {
|
|
72
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
73
|
+
|
|
74
|
+
if (url.pathname !== '/callback') {
|
|
75
|
+
res.writeHead(404);
|
|
76
|
+
res.end('Not found');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const code = url.searchParams.get('code');
|
|
81
|
+
const error = url.searchParams.get('error');
|
|
82
|
+
|
|
83
|
+
if (error || !code) {
|
|
84
|
+
const msg = error || 'No code returned';
|
|
85
|
+
res.writeHead(400);
|
|
86
|
+
res.end(`<h2 style="font-family:sans-serif;color:red">❌ Auth failed: ${msg}</h2>`);
|
|
87
|
+
server.close();
|
|
88
|
+
console.error(`\n❌ Auth failed: ${msg}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const conn = new jsforce.Connection({ oauth2 });
|
|
94
|
+
await conn.authorize(code);
|
|
95
|
+
|
|
96
|
+
const tokens = {
|
|
97
|
+
instanceUrl: conn.instanceUrl,
|
|
98
|
+
accessToken: conn.accessToken,
|
|
99
|
+
refreshToken: conn.refreshToken,
|
|
100
|
+
clientId: clientId.trim(),
|
|
101
|
+
clientSecret: clientSecret.trim(),
|
|
102
|
+
loginUrl: loginUrl.trim() || 'https://login.salesforce.com',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2));
|
|
106
|
+
|
|
107
|
+
// Quick test — get user info
|
|
108
|
+
const identity = await conn.identity();
|
|
109
|
+
|
|
110
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
111
|
+
res.end(`
|
|
112
|
+
<html><body style="font-family:sans-serif;padding:40px;max-width:500px">
|
|
113
|
+
<h2>✅ Authorized!</h2>
|
|
114
|
+
<p>Logged in as <strong>${identity.display_name}</strong> (${identity.email})</p>
|
|
115
|
+
<p>Instance: <code>${conn.instanceUrl}</code></p>
|
|
116
|
+
<p>Tokens saved to <code>tokens.json</code>.</p>
|
|
117
|
+
<p>You can close this tab.<br>Start the MCP server: <code>node src/index.js</code></p>
|
|
118
|
+
</body></html>
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
server.close();
|
|
122
|
+
console.log(`\n✅ Authorized as: ${identity.display_name} (${identity.email})`);
|
|
123
|
+
console.log(` Instance: ${conn.instanceUrl}`);
|
|
124
|
+
console.log(' Tokens saved to tokens.json\n');
|
|
125
|
+
console.log('Next step: node src/index.js\n');
|
|
126
|
+
process.exit(0);
|
|
127
|
+
|
|
128
|
+
} catch (err) {
|
|
129
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
130
|
+
res.end(`<html><body style="font-family:sans-serif;padding:40px"><h2 style="color:red">❌ Error</h2><pre>${err.message}</pre></body></html>`);
|
|
131
|
+
server.close();
|
|
132
|
+
console.error('\n❌ Token exchange failed:', err.message);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
server.listen(PORT, () => {
|
|
138
|
+
console.log(`\nOpening Salesforce login in your browser...`);
|
|
139
|
+
console.log(`(If browser doesn't open, visit: ${authUrl})\n`);
|
|
140
|
+
openBrowser(authUrl);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Timeout after 2 minutes
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
console.error('\n⏱ Timed out waiting for auth. Run auth.js again.');
|
|
146
|
+
server.close();
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}, 120_000);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
import { FileStorageManager } from '../src/auth/file-storage.js';
|
|
5
|
+
|
|
6
|
+
// Load environment variables
|
|
7
|
+
config();
|
|
8
|
+
|
|
9
|
+
async function showConfigHelper() {
|
|
10
|
+
console.log('🔧 MCP Salesforce Connected App Configuration Helper');
|
|
11
|
+
console.log('===================================================\n');
|
|
12
|
+
|
|
13
|
+
console.log('Your Salesforce Connected App needs to be configured with the following callback URLs:\n');
|
|
14
|
+
|
|
15
|
+
// Show multiple callback URLs to cover different scenarios
|
|
16
|
+
const ports = [8080, 8000, 8001, 8002, 8003, 8004, 8005];
|
|
17
|
+
console.log('Callback URLs to add to your Salesforce Connected App:');
|
|
18
|
+
ports.forEach(port => {
|
|
19
|
+
console.log(`• http://localhost:${port}/callback`);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
console.log('\nInstructions:');
|
|
23
|
+
console.log('1. Go to Salesforce Setup → App Manager');
|
|
24
|
+
console.log('2. Find your Connected App and click "View"');
|
|
25
|
+
console.log('3. Click "Manage" → "Edit Policies"');
|
|
26
|
+
console.log('4. In "Callback URL" section, add ALL the URLs listed above');
|
|
27
|
+
console.log('5. Save the changes');
|
|
28
|
+
console.log('6. Wait 2-10 minutes for changes to propagate\n');
|
|
29
|
+
|
|
30
|
+
console.log('Current Configuration:');
|
|
31
|
+
try {
|
|
32
|
+
const fileStorage = new FileStorageManager();
|
|
33
|
+
const credentials = await fileStorage.getCredentials();
|
|
34
|
+
const apiConfig = await fileStorage.getApiConfig();
|
|
35
|
+
|
|
36
|
+
if (credentials) {
|
|
37
|
+
console.log(`• Instance URL: ${credentials.instanceUrl}`);
|
|
38
|
+
console.log(`• Client ID: ${credentials.clientId?.substring(0, 20)}...`);
|
|
39
|
+
console.log(`• Preferred Port: ${apiConfig.callbackPort}`);
|
|
40
|
+
} else {
|
|
41
|
+
console.log('• No configuration found in ~/.mcp-salesforce.json');
|
|
42
|
+
console.log('• Please run the salesforce_setup tool first');
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(`• Error reading configuration: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('\nAfter updating your Connected App, run: npm run setup');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
showConfigHelper().catch(console.error);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Simple wrapper to run the main index.js file
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Import and run the main module
|
|
11
|
+
const mainModule = path.join(__dirname, '..', 'src', 'index.js');
|
|
12
|
+
import(mainModule);
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
import { TokenManager } from '../src/auth/token-manager.js';
|
|
5
|
+
import { FileStorageManager } from '../src/auth/file-storage.js';
|
|
6
|
+
import { readFile } from 'fs/promises';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { dirname, join } from 'path';
|
|
9
|
+
|
|
10
|
+
// Load environment variables
|
|
11
|
+
config();
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
class SetupTool {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.fileStorage = new FileStorageManager();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Display welcome message and instructions
|
|
23
|
+
*/
|
|
24
|
+
displayWelcome() {
|
|
25
|
+
console.log('🚀 MCP Salesforce Server Setup Tool');
|
|
26
|
+
console.log('=====================================\n');
|
|
27
|
+
|
|
28
|
+
console.log('This tool will help you authenticate with Salesforce using OAuth.');
|
|
29
|
+
console.log('You will need:\n');
|
|
30
|
+
|
|
31
|
+
console.log('1. A Salesforce Connected App with OAuth enabled');
|
|
32
|
+
console.log('2. The Consumer Key (Client ID) and Consumer Secret');
|
|
33
|
+
console.log('3. Your Salesforce instance URL (e.g., https://yourorg.salesforce.com)\n');
|
|
34
|
+
|
|
35
|
+
console.log('📖 For setup instructions, see: docs/oauth-guide.md\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate required configuration
|
|
40
|
+
*/
|
|
41
|
+
async validateConfiguration() {
|
|
42
|
+
console.log('🔍 Checking configuration...');
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const credentials = await this.fileStorage.getCredentials();
|
|
46
|
+
|
|
47
|
+
if (!credentials) {
|
|
48
|
+
console.error('\n❌ No Salesforce credentials found in ~/.mcp-salesforce.json');
|
|
49
|
+
console.error('📝 Please run the salesforce_setup tool first to configure your credentials.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { clientId, clientSecret, instanceUrl } = credentials;
|
|
54
|
+
|
|
55
|
+
if (!clientId || !clientSecret || !instanceUrl) {
|
|
56
|
+
console.error('\n❌ Incomplete credentials in ~/.mcp-salesforce.json');
|
|
57
|
+
console.error('📝 Please run the salesforce_setup tool to reconfigure your credentials.');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`✅ Client ID: ${this.maskSensitive('CLIENT_ID', clientId)}`);
|
|
62
|
+
console.log(`✅ Client Secret: ${this.maskSensitive('CLIENT_SECRET', clientSecret)}`);
|
|
63
|
+
console.log(`✅ Instance URL: ${instanceUrl}`);
|
|
64
|
+
|
|
65
|
+
console.log('✅ Configuration valid\n');
|
|
66
|
+
return credentials;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`\n❌ Error reading configuration: ${error.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Mask sensitive values for display
|
|
75
|
+
*/
|
|
76
|
+
maskSensitive(key, value) {
|
|
77
|
+
if (key.includes('SECRET')) {
|
|
78
|
+
return value.substring(0, 8) + '***';
|
|
79
|
+
}
|
|
80
|
+
if (key.includes('CLIENT_ID')) {
|
|
81
|
+
return value.substring(0, 12) + '***';
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Test Salesforce instance connectivity
|
|
88
|
+
*/
|
|
89
|
+
async testConnectivity(instanceUrl) {
|
|
90
|
+
console.log('🔗 Testing Salesforce instance connectivity...');
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch(`${instanceUrl}/services/data/`, {
|
|
94
|
+
method: 'GET',
|
|
95
|
+
timeout: 10000
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (response.ok) {
|
|
99
|
+
const data = await response.json();
|
|
100
|
+
console.log(`✅ Successfully connected to ${instanceUrl}`);
|
|
101
|
+
console.log(`📊 Available API versions: ${data.length}`);
|
|
102
|
+
return true;
|
|
103
|
+
} else {
|
|
104
|
+
console.error(`❌ HTTP ${response.status}: ${response.statusText}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(`❌ Connection failed: ${error.message}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Perform OAuth authentication flow
|
|
115
|
+
*/
|
|
116
|
+
async performOAuth(clientId, clientSecret, instanceUrl) {
|
|
117
|
+
console.log('🔐 Starting OAuth authentication flow...\n');
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const tokenManager = new TokenManager(clientId, clientSecret, instanceUrl);
|
|
121
|
+
|
|
122
|
+
console.log('📝 Instructions:');
|
|
123
|
+
console.log('1. Your browser will open to Salesforce login');
|
|
124
|
+
console.log('2. Log in with your Salesforce credentials');
|
|
125
|
+
console.log('3. If prompted, approve the OAuth request');
|
|
126
|
+
console.log('4. The browser will redirect back automatically\n');
|
|
127
|
+
|
|
128
|
+
// Wait for user to be ready
|
|
129
|
+
console.log('Press Enter when ready to continue...');
|
|
130
|
+
await this.waitForInput();
|
|
131
|
+
|
|
132
|
+
// Perform authentication
|
|
133
|
+
const tokens = await tokenManager.authenticateWithOAuth();
|
|
134
|
+
|
|
135
|
+
console.log('\n✅ OAuth authentication successful!');
|
|
136
|
+
console.log(`🏢 Instance: ${tokens.instance_url}`);
|
|
137
|
+
console.log(`⏰ Token expires: ${new Date(tokens.expires_at).toLocaleString()}`);
|
|
138
|
+
|
|
139
|
+
return tokens;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(`\n❌ OAuth authentication failed: ${error.message}`);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Test the stored tokens by making an API call
|
|
148
|
+
*/
|
|
149
|
+
async testAuthentication(credentials) {
|
|
150
|
+
console.log('\n🧪 Testing authentication...');
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const tokenManager = new TokenManager(credentials.clientId, credentials.clientSecret, credentials.instanceUrl);
|
|
154
|
+
await tokenManager.initialize();
|
|
155
|
+
|
|
156
|
+
const result = await tokenManager.testTokens();
|
|
157
|
+
|
|
158
|
+
if (result.valid) {
|
|
159
|
+
console.log('✅ Authentication test successful!');
|
|
160
|
+
console.log(`📡 API versions available: ${result.apiVersions}`);
|
|
161
|
+
return true;
|
|
162
|
+
} else {
|
|
163
|
+
console.error(`❌ Authentication test failed: ${result.error}`);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(`❌ Authentication test error: ${error.message}`);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Display final setup summary
|
|
174
|
+
*/
|
|
175
|
+
async displaySummary() {
|
|
176
|
+
console.log('\n🎉 Setup Complete!');
|
|
177
|
+
console.log('==================\n');
|
|
178
|
+
|
|
179
|
+
console.log('Your MCP Salesforce server is now configured and ready to use.\n');
|
|
180
|
+
|
|
181
|
+
console.log('Next steps:');
|
|
182
|
+
console.log('1. Add the server to your Claude Desktop configuration');
|
|
183
|
+
console.log('2. Restart Claude Desktop');
|
|
184
|
+
console.log('3. Start using Salesforce tools in your conversations\n');
|
|
185
|
+
|
|
186
|
+
console.log('Configuration for Claude Desktop:');
|
|
187
|
+
console.log('```json');
|
|
188
|
+
console.log('{');
|
|
189
|
+
console.log(' "mcpServers": {');
|
|
190
|
+
console.log(' "salesforce": {');
|
|
191
|
+
console.log(' "command": "node",');
|
|
192
|
+
console.log(` "args": ["${join(process.cwd(), 'src/index.js')}"]`);
|
|
193
|
+
console.log(' }');
|
|
194
|
+
console.log(' }');
|
|
195
|
+
console.log('}```\n');
|
|
196
|
+
|
|
197
|
+
console.log('📚 For more information, see the documentation in the docs/ folder.');
|
|
198
|
+
console.log('🔧 Your credentials are stored securely in ~/.mcp-salesforce.json');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Wait for user input
|
|
203
|
+
*/
|
|
204
|
+
async waitForInput() {
|
|
205
|
+
return new Promise((resolve) => {
|
|
206
|
+
process.stdin.once('data', () => {
|
|
207
|
+
resolve();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Main setup process
|
|
214
|
+
*/
|
|
215
|
+
async run() {
|
|
216
|
+
try {
|
|
217
|
+
this.displayWelcome();
|
|
218
|
+
|
|
219
|
+
// Validate configuration
|
|
220
|
+
const credentials = await this.validateConfiguration();
|
|
221
|
+
|
|
222
|
+
// Test connectivity
|
|
223
|
+
const connected = await this.testConnectivity(credentials.instanceUrl);
|
|
224
|
+
if (!connected) {
|
|
225
|
+
console.error('\n❌ Cannot connect to Salesforce instance. Please check your instance URL.');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Perform OAuth flow
|
|
230
|
+
await this.performOAuth(
|
|
231
|
+
credentials.clientId,
|
|
232
|
+
credentials.clientSecret,
|
|
233
|
+
credentials.instanceUrl
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// Test authentication
|
|
237
|
+
const authValid = await this.testAuthentication(credentials);
|
|
238
|
+
|
|
239
|
+
if (!authValid) {
|
|
240
|
+
console.error('\n❌ Authentication test failed. Please try running setup again.');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Show summary
|
|
245
|
+
await this.displaySummary();
|
|
246
|
+
|
|
247
|
+
console.log('✅ Setup completed successfully!');
|
|
248
|
+
process.exit(0);
|
|
249
|
+
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(`\n💥 Setup failed: ${error.message}`);
|
|
252
|
+
console.error('\n🔧 Troubleshooting:');
|
|
253
|
+
console.error('1. Check your ~/.mcp-salesforce.json configuration');
|
|
254
|
+
console.error('2. Verify your Salesforce Connected App settings');
|
|
255
|
+
console.error('3. Ensure you have the correct permissions');
|
|
256
|
+
console.error('4. Check your network connection\n');
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Run setup if called directly
|
|
263
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
264
|
+
const setup = new SetupTool();
|
|
265
|
+
setup.run();
|
|
266
|
+
}
|