@imazhar101/salesforce-mcp-jsforce 0.1.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/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/auth.d.ts +35 -0
- package/dist/auth.js +85 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +119 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +89 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +6 -0
- package/dist/oauth.js +142 -0
- package/dist/oauth.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +122 -0
- package/dist/server.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 imazhar101
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# salesforce-mcp-jsforce
|
|
2
|
+
|
|
3
|
+
A **lite, single-org** [Model Context Protocol](https://modelcontextprotocol.io) server for Salesforce, built on [jsforce](https://github.com/jsforce/jsforce).
|
|
4
|
+
|
|
5
|
+
- **Bring your own token.** The server never stores a client secret, username, or password. You authenticate once with OAuth; it holds only an access token + instance URL.
|
|
6
|
+
- **Two ways to run.** Locally over **stdio** (for Claude Code and other MCP clients) or as a **dedicated streamable-HTTP server** where each request carries its own token.
|
|
7
|
+
- **Safe to host & open-source.** No org-specific config, no multi-environment credential matrix, no destructive metadata tooling. Optional read-only mode.
|
|
8
|
+
|
|
9
|
+
## Tools
|
|
10
|
+
|
|
11
|
+
| Tool | Mode | Description |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `salesforce_identity` | read | Identity of the supplied token (token validity check) |
|
|
14
|
+
| `salesforce_query` | read | Run a SOQL query |
|
|
15
|
+
| `salesforce_search` | read | Run a SOSL full-text search |
|
|
16
|
+
| `salesforce_list_objects` | read | List sObjects + key metadata |
|
|
17
|
+
| `salesforce_describe_object` | read | Trimmed describe of an sObject |
|
|
18
|
+
| `salesforce_get_record` | read | Retrieve a record by Id |
|
|
19
|
+
| `salesforce_create_record` | write | Create a record |
|
|
20
|
+
| `salesforce_update_record` | write | Update a record |
|
|
21
|
+
| `salesforce_delete_record` | write | Delete a record |
|
|
22
|
+
|
|
23
|
+
Set `SF_READONLY=1` to register the read tools only.
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g @imazhar101/salesforce-mcp-jsforce
|
|
29
|
+
|
|
30
|
+
# 1. Log in (PKCE against your External Client App)
|
|
31
|
+
salesforce-mcp-jsforce login --client-id <ECA_CONSUMER_KEY>
|
|
32
|
+
# sandbox: add --login-url https://test.salesforce.com
|
|
33
|
+
|
|
34
|
+
# 2. Use it from Claude Code
|
|
35
|
+
claude mcp add salesforce -- npx -y @imazhar101/salesforce-mcp-jsforce
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`login` opens a browser, completes the OAuth handshake, saves the token to
|
|
39
|
+
`~/.config/salesforce-mcp-jsforce/token.json`, and prints ready-to-paste config.
|
|
40
|
+
|
|
41
|
+
## Credentials
|
|
42
|
+
|
|
43
|
+
**stdio** — one of:
|
|
44
|
+
|
|
45
|
+
- `SF_ACCESS_TOKEN` + `SF_INSTANCE_URL` environment variables, or
|
|
46
|
+
- the token file written by `login` (read automatically).
|
|
47
|
+
|
|
48
|
+
**HTTP** — per request, via headers:
|
|
49
|
+
|
|
50
|
+
- `X-SF-Access-Token`
|
|
51
|
+
- `X-SF-Instance-Url`
|
|
52
|
+
- `X-SF-Api-Version` (optional)
|
|
53
|
+
|
|
54
|
+
## Run as a dedicated HTTP server
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
PORT=3000 salesforce-mcp-jsforce http
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Stateless streamable-HTTP at `POST /mcp`; health probe at `GET /health`. Each
|
|
61
|
+
request is handled by a throwaway server instance keyed to its own token — no
|
|
62
|
+
caller state is shared. Put it behind TLS; the access token is a live credential.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -s http://localhost:3000/mcp \
|
|
66
|
+
-H 'content-type: application/json' \
|
|
67
|
+
-H 'accept: application/json, text/event-stream' \
|
|
68
|
+
-H "X-SF-Access-Token: $SF_ACCESS_TOKEN" \
|
|
69
|
+
-H "X-SF-Instance-Url: $SF_INSTANCE_URL" \
|
|
70
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Environment variables
|
|
74
|
+
|
|
75
|
+
| Var | Default | Purpose |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `SF_ACCESS_TOKEN` | — | stdio access token |
|
|
78
|
+
| `SF_INSTANCE_URL` | — | stdio instance URL |
|
|
79
|
+
| `SF_API_VERSION` | `62.0` | REST API version |
|
|
80
|
+
| `SF_READONLY` | off | `1` strips write tools |
|
|
81
|
+
| `SF_LOGIN_URL` | `https://login.salesforce.com` | OAuth host (sandbox: `test.salesforce.com`) |
|
|
82
|
+
| `SF_CLIENT_ID` | — | ECA consumer key for `login` |
|
|
83
|
+
| `SF_CLIENT_SECRET` | — | only for confidential apps |
|
|
84
|
+
| `SF_SCOPE` | `api refresh_token` | OAuth scopes |
|
|
85
|
+
| `PORT` | `3000` | HTTP host port |
|
|
86
|
+
|
|
87
|
+
## Security model
|
|
88
|
+
|
|
89
|
+
- The token grants exactly the permissions of the user who authorized it — the server adds no privilege.
|
|
90
|
+
- In HTTP mode no credentials are persisted; the token lives only for the duration of one request.
|
|
91
|
+
- In stdio mode the saved token file is written `chmod 600`.
|
|
92
|
+
- Tokens are never logged.
|
|
93
|
+
|
|
94
|
+
## Build from source
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install
|
|
98
|
+
npm run build
|
|
99
|
+
node dist/index.js --help
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* The only credentials this server ever handles: an already-issued access
|
|
4
|
+
* token plus the org's instance URL. We never see a client secret, username,
|
|
5
|
+
* or password — that is the whole point of the BYO-token model.
|
|
6
|
+
*/
|
|
7
|
+
export interface SfCredentials {
|
|
8
|
+
accessToken: string;
|
|
9
|
+
instanceUrl: string;
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
/** Optional, only persisted by `login` so the token can be silently renewed. */
|
|
12
|
+
refreshToken?: string;
|
|
13
|
+
clientId?: string;
|
|
14
|
+
loginUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
/** A function the server calls (lazily, per request) to obtain credentials. */
|
|
17
|
+
export type CredentialResolver = () => SfCredentials;
|
|
18
|
+
export declare class MissingCredentialsError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/** stdio: read SF_ACCESS_TOKEN + SF_INSTANCE_URL from the environment. */
|
|
22
|
+
export declare function credsFromEnv(): SfCredentials | null;
|
|
23
|
+
/** stdio: read a token previously saved by the `login` command. */
|
|
24
|
+
export declare function credsFromFile(): SfCredentials | null;
|
|
25
|
+
/**
|
|
26
|
+
* HTTP: pull the per-request token off the headers. This is what makes the
|
|
27
|
+
* hosted server stateless — each caller brings their own token and gets data
|
|
28
|
+
* scoped to their own Salesforce permissions.
|
|
29
|
+
*/
|
|
30
|
+
export declare function credsFromHeaders(headers: IncomingHttpHeaders): SfCredentials | null;
|
|
31
|
+
/** stdio resolver: env wins, then the saved token file. */
|
|
32
|
+
export declare function resolveStdioCredentials(): SfCredentials;
|
|
33
|
+
/** Persist the token issued by `login` (chmod 600 — it is a live credential). */
|
|
34
|
+
export declare function saveToken(creds: SfCredentials): void;
|
|
35
|
+
export declare function tokenPath(): string;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CONFIG_DIR, TOKEN_FILE, DEFAULT_API_VERSION } from "./config.js";
|
|
4
|
+
export class MissingCredentialsError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "MissingCredentialsError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** stdio: read SF_ACCESS_TOKEN + SF_INSTANCE_URL from the environment. */
|
|
11
|
+
export function credsFromEnv() {
|
|
12
|
+
const accessToken = process.env.SF_ACCESS_TOKEN;
|
|
13
|
+
const instanceUrl = process.env.SF_INSTANCE_URL;
|
|
14
|
+
if (!accessToken || !instanceUrl)
|
|
15
|
+
return null;
|
|
16
|
+
return {
|
|
17
|
+
accessToken,
|
|
18
|
+
instanceUrl,
|
|
19
|
+
apiVersion: process.env.SF_API_VERSION || DEFAULT_API_VERSION,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** stdio: read a token previously saved by the `login` command. */
|
|
23
|
+
export function credsFromFile() {
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(TOKEN_FILE, "utf8");
|
|
26
|
+
const data = JSON.parse(raw);
|
|
27
|
+
if (!data.accessToken || !data.instanceUrl)
|
|
28
|
+
return null;
|
|
29
|
+
return {
|
|
30
|
+
accessToken: data.accessToken,
|
|
31
|
+
instanceUrl: data.instanceUrl,
|
|
32
|
+
apiVersion: data.apiVersion || DEFAULT_API_VERSION,
|
|
33
|
+
refreshToken: data.refreshToken,
|
|
34
|
+
clientId: data.clientId,
|
|
35
|
+
loginUrl: data.loginUrl,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* HTTP: pull the per-request token off the headers. This is what makes the
|
|
44
|
+
* hosted server stateless — each caller brings their own token and gets data
|
|
45
|
+
* scoped to their own Salesforce permissions.
|
|
46
|
+
*/
|
|
47
|
+
export function credsFromHeaders(headers) {
|
|
48
|
+
const accessToken = header(headers, "x-sf-access-token");
|
|
49
|
+
const instanceUrl = header(headers, "x-sf-instance-url");
|
|
50
|
+
if (!accessToken || !instanceUrl)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
accessToken,
|
|
54
|
+
instanceUrl,
|
|
55
|
+
apiVersion: header(headers, "x-sf-api-version") || DEFAULT_API_VERSION,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function header(headers, name) {
|
|
59
|
+
const v = headers[name];
|
|
60
|
+
return Array.isArray(v) ? v[0] : v;
|
|
61
|
+
}
|
|
62
|
+
/** stdio resolver: env wins, then the saved token file. */
|
|
63
|
+
export function resolveStdioCredentials() {
|
|
64
|
+
const creds = credsFromEnv() || credsFromFile();
|
|
65
|
+
if (!creds) {
|
|
66
|
+
throw new MissingCredentialsError("No Salesforce credentials found. Set SF_ACCESS_TOKEN + SF_INSTANCE_URL, " +
|
|
67
|
+
"or run `salesforce-mcp-jsforce login` first.");
|
|
68
|
+
}
|
|
69
|
+
return creds;
|
|
70
|
+
}
|
|
71
|
+
/** Persist the token issued by `login` (chmod 600 — it is a live credential). */
|
|
72
|
+
export function saveToken(creds) {
|
|
73
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
74
|
+
fs.writeFileSync(TOKEN_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
75
|
+
try {
|
|
76
|
+
fs.chmodSync(TOKEN_FILE, 0o600);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* best effort on platforms without chmod */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function tokenPath() {
|
|
83
|
+
return path.normalize(TOKEN_FILE);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import jsforce from "jsforce";
|
|
2
|
+
import type { SfCredentials } from "./auth.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a jsforce connection from BYO credentials. jsforce's constructor takes
|
|
5
|
+
* `{ accessToken, instanceUrl }` directly, which is exactly the per-request
|
|
6
|
+
* model — so a fresh connection per call is cheap and carries no shared state.
|
|
7
|
+
*/
|
|
8
|
+
export declare function makeConnection(creds: SfCredentials): jsforce.Connection;
|
|
9
|
+
export type Conn = jsforce.Connection;
|
|
10
|
+
/** Who does this token belong to? Cheapest possible token-validity check. */
|
|
11
|
+
export declare function identity(conn: Conn): Promise<{
|
|
12
|
+
user_id: string;
|
|
13
|
+
organization_id: string;
|
|
14
|
+
username: string;
|
|
15
|
+
display_name: string;
|
|
16
|
+
email: string;
|
|
17
|
+
instance_url: string;
|
|
18
|
+
api_version: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function soqlQuery(conn: Conn, soql: string): Promise<{
|
|
21
|
+
totalSize: number;
|
|
22
|
+
done: boolean;
|
|
23
|
+
nextRecordsUrl: string | null;
|
|
24
|
+
records: jsforce.Record[];
|
|
25
|
+
}>;
|
|
26
|
+
/** SOSL full-text search across objects. */
|
|
27
|
+
export declare function soslSearch(conn: Conn, sosl: string): Promise<{
|
|
28
|
+
searchRecords: any[];
|
|
29
|
+
}>;
|
|
30
|
+
export declare function listObjects(conn: Conn): Promise<{
|
|
31
|
+
count: number;
|
|
32
|
+
sobjects: {
|
|
33
|
+
name: string;
|
|
34
|
+
label: string;
|
|
35
|
+
custom: boolean;
|
|
36
|
+
keyPrefix: jsforce.Optional<string>;
|
|
37
|
+
queryable: boolean;
|
|
38
|
+
createable: boolean;
|
|
39
|
+
updateable: boolean;
|
|
40
|
+
deletable: boolean;
|
|
41
|
+
}[];
|
|
42
|
+
}>;
|
|
43
|
+
/** Lean describe — the full payload is enormous, so we trim to the essentials. */
|
|
44
|
+
export declare function describeObject(conn: Conn, objectName: string): Promise<{
|
|
45
|
+
name: string;
|
|
46
|
+
label: string;
|
|
47
|
+
keyPrefix: jsforce.Optional<string>;
|
|
48
|
+
custom: boolean;
|
|
49
|
+
createable: boolean;
|
|
50
|
+
updateable: boolean;
|
|
51
|
+
deletable: boolean;
|
|
52
|
+
queryable: boolean;
|
|
53
|
+
fields: {
|
|
54
|
+
name: string;
|
|
55
|
+
label: string;
|
|
56
|
+
type: string;
|
|
57
|
+
custom: boolean;
|
|
58
|
+
nillable: boolean;
|
|
59
|
+
updateable: boolean;
|
|
60
|
+
length: number | undefined;
|
|
61
|
+
referenceTo: string[] | undefined;
|
|
62
|
+
picklistValues: any[] | undefined;
|
|
63
|
+
}[];
|
|
64
|
+
}>;
|
|
65
|
+
export declare function getRecord(conn: Conn, objectName: string, recordId: string, fields?: string[]): Promise<any>;
|
|
66
|
+
export declare function createRecord(conn: Conn, objectName: string, data: Record<string, unknown>): Promise<jsforce.SaveResult[]>;
|
|
67
|
+
export declare function updateRecord(conn: Conn, objectName: string, recordId: string, data: Record<string, unknown>): Promise<jsforce.SaveResult[]>;
|
|
68
|
+
export declare function deleteRecord(conn: Conn, objectName: string, recordId: string): Promise<jsforce.SaveResult>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import jsforce from "jsforce";
|
|
2
|
+
import { DEFAULT_API_VERSION } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a jsforce connection from BYO credentials. jsforce's constructor takes
|
|
5
|
+
* `{ accessToken, instanceUrl }` directly, which is exactly the per-request
|
|
6
|
+
* model — so a fresh connection per call is cheap and carries no shared state.
|
|
7
|
+
*/
|
|
8
|
+
export function makeConnection(creds) {
|
|
9
|
+
return new jsforce.Connection({
|
|
10
|
+
instanceUrl: creds.instanceUrl,
|
|
11
|
+
accessToken: creds.accessToken,
|
|
12
|
+
version: creds.apiVersion || DEFAULT_API_VERSION,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
// ── Read operations ───────────────────────────────────────────────────────────
|
|
16
|
+
/** Who does this token belong to? Cheapest possible token-validity check. */
|
|
17
|
+
export async function identity(conn) {
|
|
18
|
+
const id = await conn.identity();
|
|
19
|
+
return {
|
|
20
|
+
user_id: id.user_id,
|
|
21
|
+
organization_id: id.organization_id,
|
|
22
|
+
username: id.username,
|
|
23
|
+
display_name: id.display_name,
|
|
24
|
+
email: id.email,
|
|
25
|
+
instance_url: conn.instanceUrl,
|
|
26
|
+
api_version: conn.version,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export async function soqlQuery(conn, soql) {
|
|
30
|
+
const result = await conn.query(soql);
|
|
31
|
+
return {
|
|
32
|
+
totalSize: result.totalSize,
|
|
33
|
+
done: result.done,
|
|
34
|
+
nextRecordsUrl: result.nextRecordsUrl ?? null,
|
|
35
|
+
records: stripAttributes(result.records),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** SOSL full-text search across objects. */
|
|
39
|
+
export async function soslSearch(conn, sosl) {
|
|
40
|
+
const result = await conn.search(sosl);
|
|
41
|
+
return { searchRecords: stripAttributes(result.searchRecords) };
|
|
42
|
+
}
|
|
43
|
+
export async function listObjects(conn) {
|
|
44
|
+
const g = await conn.describeGlobal();
|
|
45
|
+
return {
|
|
46
|
+
count: g.sobjects.length,
|
|
47
|
+
sobjects: g.sobjects.map((s) => ({
|
|
48
|
+
name: s.name,
|
|
49
|
+
label: s.label,
|
|
50
|
+
custom: s.custom,
|
|
51
|
+
keyPrefix: s.keyPrefix,
|
|
52
|
+
queryable: s.queryable,
|
|
53
|
+
createable: s.createable,
|
|
54
|
+
updateable: s.updateable,
|
|
55
|
+
deletable: s.deletable,
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Lean describe — the full payload is enormous, so we trim to the essentials. */
|
|
60
|
+
export async function describeObject(conn, objectName) {
|
|
61
|
+
const d = await conn.sobject(objectName).describe();
|
|
62
|
+
return {
|
|
63
|
+
name: d.name,
|
|
64
|
+
label: d.label,
|
|
65
|
+
keyPrefix: d.keyPrefix,
|
|
66
|
+
custom: d.custom,
|
|
67
|
+
createable: d.createable,
|
|
68
|
+
updateable: d.updateable,
|
|
69
|
+
deletable: d.deletable,
|
|
70
|
+
queryable: d.queryable,
|
|
71
|
+
fields: d.fields.map((f) => ({
|
|
72
|
+
name: f.name,
|
|
73
|
+
label: f.label,
|
|
74
|
+
type: f.type,
|
|
75
|
+
custom: f.custom,
|
|
76
|
+
nillable: f.nillable,
|
|
77
|
+
updateable: f.updateable,
|
|
78
|
+
length: f.length || undefined,
|
|
79
|
+
referenceTo: f.referenceTo?.length ? f.referenceTo : undefined,
|
|
80
|
+
picklistValues: f.picklistValues?.length
|
|
81
|
+
? f.picklistValues.filter((p) => p.active).map((p) => p.value)
|
|
82
|
+
: undefined,
|
|
83
|
+
})),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export async function getRecord(conn, objectName, recordId, fields) {
|
|
87
|
+
if (fields && fields.length) {
|
|
88
|
+
const rows = await conn
|
|
89
|
+
.sobject(objectName)
|
|
90
|
+
.find({ Id: recordId }, fields)
|
|
91
|
+
.limit(1)
|
|
92
|
+
.execute();
|
|
93
|
+
return stripAttributes(rows)[0] ?? null;
|
|
94
|
+
}
|
|
95
|
+
const rec = await conn.sobject(objectName).retrieve(recordId);
|
|
96
|
+
return stripAttributes([rec])[0] ?? null;
|
|
97
|
+
}
|
|
98
|
+
// ── Write operations (omitted in read-only mode) ───────────────────────────────
|
|
99
|
+
export async function createRecord(conn, objectName, data) {
|
|
100
|
+
return conn.sobject(objectName).create(data);
|
|
101
|
+
}
|
|
102
|
+
export async function updateRecord(conn, objectName, recordId, data) {
|
|
103
|
+
return conn.sobject(objectName).update({ Id: recordId, ...data });
|
|
104
|
+
}
|
|
105
|
+
export async function deleteRecord(conn, objectName, recordId) {
|
|
106
|
+
return conn.sobject(objectName).destroy(recordId);
|
|
107
|
+
}
|
|
108
|
+
// ── helpers ────────────────────────────────────────────────────────────────────
|
|
109
|
+
/** jsforce decorates every record with a noisy `attributes` block — drop it. */
|
|
110
|
+
function stripAttributes(records) {
|
|
111
|
+
return records.map((r) => {
|
|
112
|
+
if (r && typeof r === "object" && "attributes" in r) {
|
|
113
|
+
const { attributes, ...rest } = r;
|
|
114
|
+
return rest;
|
|
115
|
+
}
|
|
116
|
+
return r;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Package identity (kept in sync with package.json). */
|
|
2
|
+
export declare const PKG_NAME = "salesforce-mcp-jsforce";
|
|
3
|
+
export declare const PKG_VERSION = "0.1.0";
|
|
4
|
+
/** Salesforce REST API version used for all jsforce connections. */
|
|
5
|
+
export declare const DEFAULT_API_VERSION: string;
|
|
6
|
+
/**
|
|
7
|
+
* OAuth login host. Production: https://login.salesforce.com.
|
|
8
|
+
* Sandbox: https://test.salesforce.com (or your My Domain URL).
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_LOGIN_URL: string;
|
|
11
|
+
/** Where the `login` command persists the issued token for stdio use. */
|
|
12
|
+
export declare const CONFIG_DIR: string;
|
|
13
|
+
export declare const TOKEN_FILE: string;
|
|
14
|
+
/**
|
|
15
|
+
* Read-only mode strips all write tools (create/update/delete). Recommended
|
|
16
|
+
* for the publicly hosted server. Set SF_READONLY=1 to enable.
|
|
17
|
+
*/
|
|
18
|
+
export declare const READ_ONLY: boolean;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Package identity (kept in sync with package.json). */
|
|
4
|
+
export const PKG_NAME = "salesforce-mcp-jsforce";
|
|
5
|
+
export const PKG_VERSION = "0.1.0";
|
|
6
|
+
/** Salesforce REST API version used for all jsforce connections. */
|
|
7
|
+
export const DEFAULT_API_VERSION = process.env.SF_API_VERSION || "62.0";
|
|
8
|
+
/**
|
|
9
|
+
* OAuth login host. Production: https://login.salesforce.com.
|
|
10
|
+
* Sandbox: https://test.salesforce.com (or your My Domain URL).
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_LOGIN_URL = process.env.SF_LOGIN_URL || "https://login.salesforce.com";
|
|
13
|
+
/** Where the `login` command persists the issued token for stdio use. */
|
|
14
|
+
export const CONFIG_DIR = process.env.SF_MCP_CONFIG_DIR || path.join(os.homedir(), ".config", PKG_NAME);
|
|
15
|
+
export const TOKEN_FILE = path.join(CONFIG_DIR, "token.json");
|
|
16
|
+
/**
|
|
17
|
+
* Read-only mode strips all write tools (create/update/delete). Recommended
|
|
18
|
+
* for the publicly hosted server. Set SF_READONLY=1 to enable.
|
|
19
|
+
*/
|
|
20
|
+
export const READ_ONLY = process.env.SF_READONLY === "1" || process.env.SF_READONLY === "true";
|
|
21
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,yDAAyD;AACzD,MAAM,CAAC,MAAM,QAAQ,GAAG,wBAAwB,CAAC;AACjD,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AAEnC,oEAAoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,MAAM,CAAC;AAExE;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,8BAA8B,CAAC;AAE7D,yEAAyE;AACzE,MAAM,CAAC,MAAM,UAAU,GACrB,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAE9D;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GACpB,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,MAAM,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedicated, stateless streamable-HTTP host. Every request brings its own
|
|
3
|
+
* Salesforce token via headers, so we build a throwaway server + transport per
|
|
4
|
+
* request — nothing about one caller leaks into another.
|
|
5
|
+
*
|
|
6
|
+
* Headers: X-SF-Access-Token, X-SF-Instance-Url (X-SF-Api-Version optional).
|
|
7
|
+
*/
|
|
8
|
+
export declare function startHttp(port: number): Promise<void>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
|
+
import { buildServer } from "./server.js";
|
|
4
|
+
import { credsFromHeaders, MissingCredentialsError } from "./auth.js";
|
|
5
|
+
import { PKG_NAME, PKG_VERSION } from "./config.js";
|
|
6
|
+
const MCP_PATH = "/mcp";
|
|
7
|
+
function send(res, status, body) {
|
|
8
|
+
const text = JSON.stringify(body);
|
|
9
|
+
res.writeHead(status, { "content-type": "application/json" }).end(text);
|
|
10
|
+
}
|
|
11
|
+
async function readBody(req) {
|
|
12
|
+
const chunks = [];
|
|
13
|
+
for await (const chunk of req)
|
|
14
|
+
chunks.push(chunk);
|
|
15
|
+
if (!chunks.length)
|
|
16
|
+
return undefined;
|
|
17
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Dedicated, stateless streamable-HTTP host. Every request brings its own
|
|
21
|
+
* Salesforce token via headers, so we build a throwaway server + transport per
|
|
22
|
+
* request — nothing about one caller leaks into another.
|
|
23
|
+
*
|
|
24
|
+
* Headers: X-SF-Access-Token, X-SF-Instance-Url (X-SF-Api-Version optional).
|
|
25
|
+
*/
|
|
26
|
+
export async function startHttp(port) {
|
|
27
|
+
const server = http.createServer(async (req, res) => {
|
|
28
|
+
// Liveness probe for load balancers / container health checks.
|
|
29
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
30
|
+
return send(res, 200, { status: "ok", name: PKG_NAME, version: PKG_VERSION });
|
|
31
|
+
}
|
|
32
|
+
if (!req.url || !req.url.startsWith(MCP_PATH)) {
|
|
33
|
+
return send(res, 404, { error: "Not found" });
|
|
34
|
+
}
|
|
35
|
+
if (req.method !== "POST") {
|
|
36
|
+
// Stateless mode does not support the GET/DELETE session endpoints.
|
|
37
|
+
return send(res, 405, {
|
|
38
|
+
jsonrpc: "2.0",
|
|
39
|
+
error: { code: -32000, message: "Method not allowed; POST to /mcp" },
|
|
40
|
+
id: null,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const creds = credsFromHeaders(req.headers);
|
|
44
|
+
if (!creds) {
|
|
45
|
+
return send(res, 401, {
|
|
46
|
+
jsonrpc: "2.0",
|
|
47
|
+
error: {
|
|
48
|
+
code: -32001,
|
|
49
|
+
message: "Missing credentials. Provide X-SF-Access-Token and X-SF-Instance-Url headers.",
|
|
50
|
+
},
|
|
51
|
+
id: null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const body = await readBody(req);
|
|
56
|
+
// Stateless: a fresh server + transport per request, no session id.
|
|
57
|
+
const mcp = buildServer(() => {
|
|
58
|
+
if (!creds)
|
|
59
|
+
throw new MissingCredentialsError("No credentials");
|
|
60
|
+
return creds;
|
|
61
|
+
});
|
|
62
|
+
const transport = new StreamableHTTPServerTransport({
|
|
63
|
+
sessionIdGenerator: undefined,
|
|
64
|
+
});
|
|
65
|
+
res.on("close", () => {
|
|
66
|
+
transport.close();
|
|
67
|
+
mcp.close();
|
|
68
|
+
});
|
|
69
|
+
await mcp.connect(transport);
|
|
70
|
+
await transport.handleRequest(req, res, body);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (!res.headersSent) {
|
|
74
|
+
send(res, 500, {
|
|
75
|
+
jsonrpc: "2.0",
|
|
76
|
+
error: {
|
|
77
|
+
code: -32603,
|
|
78
|
+
message: err instanceof Error ? err.message : "Internal error",
|
|
79
|
+
},
|
|
80
|
+
id: null,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
server.listen(port, () => {
|
|
86
|
+
console.error(`${PKG_NAME} v${PKG_VERSION} listening on http://0.0.0.0:${port}${MCP_PATH} (stateless, BYO token)`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { buildServer } from "./server.js";
|
|
4
|
+
import { resolveStdioCredentials } from "./auth.js";
|
|
5
|
+
import { runLogin } from "./oauth.js";
|
|
6
|
+
import { startHttp } from "./http.js";
|
|
7
|
+
import { PKG_NAME, PKG_VERSION } from "./config.js";
|
|
8
|
+
async function runStdio() {
|
|
9
|
+
// Credentials are resolved lazily per tool call, so `tools/list` works even
|
|
10
|
+
// before a token is configured.
|
|
11
|
+
const server = buildServer(() => resolveStdioCredentials());
|
|
12
|
+
const transport = new StdioServerTransport();
|
|
13
|
+
await server.connect(transport);
|
|
14
|
+
console.error(`${PKG_NAME} v${PKG_VERSION} ready (stdio)`);
|
|
15
|
+
}
|
|
16
|
+
function printHelp() {
|
|
17
|
+
console.error(`${PKG_NAME} v${PKG_VERSION}
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
salesforce-mcp-jsforce Run the MCP server over stdio (default)
|
|
21
|
+
salesforce-mcp-jsforce http Run the dedicated streamable-HTTP server
|
|
22
|
+
salesforce-mcp-jsforce login OAuth login (PKCE) and save a token
|
|
23
|
+
|
|
24
|
+
stdio credentials (one of):
|
|
25
|
+
SF_ACCESS_TOKEN + SF_INSTANCE_URL environment variables
|
|
26
|
+
~/.config/${PKG_NAME}/token.json written by \`login\`
|
|
27
|
+
|
|
28
|
+
http credentials (per request):
|
|
29
|
+
X-SF-Access-Token, X-SF-Instance-Url headers
|
|
30
|
+
|
|
31
|
+
Common env:
|
|
32
|
+
SF_API_VERSION default 62.0
|
|
33
|
+
SF_READONLY=1 disable create/update/delete tools
|
|
34
|
+
SF_LOGIN_URL default https://login.salesforce.com (sandbox: test.salesforce.com)
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
async function main() {
|
|
38
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
39
|
+
switch (cmd) {
|
|
40
|
+
case "login":
|
|
41
|
+
await runLogin(rest);
|
|
42
|
+
return;
|
|
43
|
+
case "http": {
|
|
44
|
+
const port = Number(process.env.PORT || rest[0] || 3000);
|
|
45
|
+
await startHttp(port);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
case "-h":
|
|
49
|
+
case "--help":
|
|
50
|
+
printHelp();
|
|
51
|
+
return;
|
|
52
|
+
default:
|
|
53
|
+
if (process.env.SF_MCP_TRANSPORT === "http") {
|
|
54
|
+
await startHttp(Number(process.env.PORT || 3000));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await runStdio();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
main().catch((err) => {
|
|
61
|
+
console.error(`Fatal: ${err instanceof Error ? err.message : err}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpD,KAAK,UAAU,QAAQ;IACrB,4EAA4E;IAC5E,gCAAgC;IAChC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,GAAG,QAAQ,KAAK,WAAW,gBAAgB,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,KAAK,CAAC,GAAG,QAAQ,KAAK,WAAW;;;;;;;;;cAS7B,QAAQ;;;;;;;;;CASrB,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE7C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;YACrB,OAAO;QACT,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YACzD,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;YACtB,OAAO;QACT,CAAC;QACD,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACX,SAAS,EAAE,CAAC;YACZ,OAAO;QACT;YACE,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM,EAAE,CAAC;gBAC5C,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the authorization-code + PKCE flow against a Salesforce External Client
|
|
3
|
+
* App, exchange the code for a token, persist it, and print ready-to-paste
|
|
4
|
+
* Claude Code config. The gateway never sees the client secret in this model.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runLogin(argv: string[]): Promise<void>;
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import { URL } from "node:url";
|
|
4
|
+
import { exec } from "node:child_process";
|
|
5
|
+
import { DEFAULT_LOGIN_URL, DEFAULT_API_VERSION } from "./config.js";
|
|
6
|
+
import { saveToken, tokenPath } from "./auth.js";
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const get = (flag) => {
|
|
9
|
+
const i = argv.indexOf(flag);
|
|
10
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
11
|
+
};
|
|
12
|
+
const clientId = get("--client-id") || process.env.SF_CLIENT_ID;
|
|
13
|
+
if (!clientId) {
|
|
14
|
+
throw new Error("Missing client id. Pass --client-id <consumerKey> or set SF_CLIENT_ID " +
|
|
15
|
+
"(the consumer key of your Salesforce External Client App).");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
clientId,
|
|
19
|
+
clientSecret: get("--client-secret") || process.env.SF_CLIENT_SECRET,
|
|
20
|
+
loginUrl: get("--login-url") || DEFAULT_LOGIN_URL,
|
|
21
|
+
scope: get("--scope") || process.env.SF_SCOPE || "api refresh_token",
|
|
22
|
+
callbackPort: Number(get("--port") || process.env.SF_CALLBACK_PORT || 1717),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function pkce() {
|
|
26
|
+
const verifier = crypto.randomBytes(32).toString("base64url");
|
|
27
|
+
const challenge = crypto
|
|
28
|
+
.createHash("sha256")
|
|
29
|
+
.update(verifier)
|
|
30
|
+
.digest("base64url");
|
|
31
|
+
return { verifier, challenge };
|
|
32
|
+
}
|
|
33
|
+
function openBrowser(url) {
|
|
34
|
+
const cmd = process.platform === "darwin"
|
|
35
|
+
? "open"
|
|
36
|
+
: process.platform === "win32"
|
|
37
|
+
? "start"
|
|
38
|
+
: "xdg-open";
|
|
39
|
+
exec(`${cmd} "${url}"`, () => {
|
|
40
|
+
/* if it fails, the URL is already printed for manual use */
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run the authorization-code + PKCE flow against a Salesforce External Client
|
|
45
|
+
* App, exchange the code for a token, persist it, and print ready-to-paste
|
|
46
|
+
* Claude Code config. The gateway never sees the client secret in this model.
|
|
47
|
+
*/
|
|
48
|
+
export async function runLogin(argv) {
|
|
49
|
+
const opts = parseArgs(argv);
|
|
50
|
+
const { verifier, challenge } = pkce();
|
|
51
|
+
const redirectUri = `http://localhost:${opts.callbackPort}/callback`;
|
|
52
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
53
|
+
const authUrl = new URL(`${opts.loginUrl}/services/oauth2/authorize`);
|
|
54
|
+
authUrl.searchParams.set("response_type", "code");
|
|
55
|
+
authUrl.searchParams.set("client_id", opts.clientId);
|
|
56
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
57
|
+
authUrl.searchParams.set("scope", opts.scope);
|
|
58
|
+
authUrl.searchParams.set("code_challenge", challenge);
|
|
59
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
60
|
+
authUrl.searchParams.set("state", state);
|
|
61
|
+
const code = await new Promise((resolve, reject) => {
|
|
62
|
+
const server = http.createServer((req, res) => {
|
|
63
|
+
if (!req.url?.startsWith("/callback")) {
|
|
64
|
+
res.writeHead(404).end();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const url = new URL(req.url, redirectUri);
|
|
68
|
+
const err = url.searchParams.get("error");
|
|
69
|
+
if (err) {
|
|
70
|
+
res.writeHead(400, { "content-type": "text/html" }).end(`<h2>Login failed</h2><p>${err}: ${url.searchParams.get("error_description") ?? ""}</p>`);
|
|
71
|
+
server.close();
|
|
72
|
+
reject(new Error(`OAuth error: ${err}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (url.searchParams.get("state") !== state) {
|
|
76
|
+
res.writeHead(400, { "content-type": "text/html" }).end("<h2>State mismatch</h2>");
|
|
77
|
+
server.close();
|
|
78
|
+
reject(new Error("OAuth state mismatch — possible CSRF, aborting."));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const got = url.searchParams.get("code");
|
|
82
|
+
res.writeHead(200, { "content-type": "text/html" }).end("<h2>Authenticated ✓</h2><p>You can close this tab and return to your terminal.</p>");
|
|
83
|
+
server.close();
|
|
84
|
+
if (got)
|
|
85
|
+
resolve(got);
|
|
86
|
+
else
|
|
87
|
+
reject(new Error("No authorization code returned."));
|
|
88
|
+
});
|
|
89
|
+
server.listen(opts.callbackPort, () => {
|
|
90
|
+
console.error(`\nOpening Salesforce login in your browser…`);
|
|
91
|
+
console.error(`If it does not open, visit:\n${authUrl.toString()}\n`);
|
|
92
|
+
openBrowser(authUrl.toString());
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
// Exchange the code for a token (PKCE; secret only sent if provided).
|
|
96
|
+
const body = new URLSearchParams({
|
|
97
|
+
grant_type: "authorization_code",
|
|
98
|
+
code,
|
|
99
|
+
client_id: opts.clientId,
|
|
100
|
+
redirect_uri: redirectUri,
|
|
101
|
+
code_verifier: verifier,
|
|
102
|
+
});
|
|
103
|
+
if (opts.clientSecret)
|
|
104
|
+
body.set("client_secret", opts.clientSecret);
|
|
105
|
+
const resp = await fetch(`${opts.loginUrl}/services/oauth2/token`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
108
|
+
body,
|
|
109
|
+
});
|
|
110
|
+
const json = (await resp.json());
|
|
111
|
+
if (!resp.ok || !json.access_token) {
|
|
112
|
+
throw new Error(`Token exchange failed: ${json.error ?? resp.status} ${json.error_description ?? ""}`);
|
|
113
|
+
}
|
|
114
|
+
const creds = {
|
|
115
|
+
accessToken: json.access_token,
|
|
116
|
+
instanceUrl: json.instance_url,
|
|
117
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
118
|
+
refreshToken: json.refresh_token,
|
|
119
|
+
clientId: opts.clientId,
|
|
120
|
+
loginUrl: opts.loginUrl,
|
|
121
|
+
};
|
|
122
|
+
saveToken(creds);
|
|
123
|
+
console.error(`\n✓ Logged in. Token saved to ${tokenPath()}`);
|
|
124
|
+
console.error(` Instance: ${creds.instanceUrl}\n`);
|
|
125
|
+
console.error("Add to Claude Code (stdio) with:\n");
|
|
126
|
+
console.error(" claude mcp add salesforce -- npx -y @imazhar101/salesforce-mcp-jsforce\n");
|
|
127
|
+
console.error("…or paste into .mcp.json:\n");
|
|
128
|
+
console.error(JSON.stringify({
|
|
129
|
+
mcpServers: {
|
|
130
|
+
salesforce: {
|
|
131
|
+
command: "npx",
|
|
132
|
+
args: ["-y", "@imazhar101/salesforce-mcp-jsforce"],
|
|
133
|
+
env: {
|
|
134
|
+
SF_ACCESS_TOKEN: creds.accessToken,
|
|
135
|
+
SF_INSTANCE_URL: creds.instanceUrl,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}, null, 2));
|
|
140
|
+
console.error("\n(The token file is already read automatically, so the env block above is optional.)\n");
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CredentialResolver } from "./auth.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a fully wired MCP server. `getCreds` is invoked lazily, once per tool
|
|
5
|
+
* call — so `tools/list` works with no token present, and the HTTP host can
|
|
6
|
+
* hand each request its own per-caller credentials.
|
|
7
|
+
*
|
|
8
|
+
* @param readOnly when true, write tools are not registered at all.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildServer(getCreds: CredentialResolver, readOnly?: boolean): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { PKG_NAME, PKG_VERSION, READ_ONLY } from "./config.js";
|
|
4
|
+
import * as sf from "./client.js";
|
|
5
|
+
function ok(data) {
|
|
6
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
7
|
+
}
|
|
8
|
+
function fail(error) {
|
|
9
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }, null, 2) }],
|
|
12
|
+
isError: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a fully wired MCP server. `getCreds` is invoked lazily, once per tool
|
|
17
|
+
* call — so `tools/list` works with no token present, and the HTTP host can
|
|
18
|
+
* hand each request its own per-caller credentials.
|
|
19
|
+
*
|
|
20
|
+
* @param readOnly when true, write tools are not registered at all.
|
|
21
|
+
*/
|
|
22
|
+
export function buildServer(getCreds, readOnly = READ_ONLY) {
|
|
23
|
+
const server = new McpServer({ name: PKG_NAME, version: PKG_VERSION }, { capabilities: { tools: {} } });
|
|
24
|
+
const conn = () => sf.makeConnection(getCreds());
|
|
25
|
+
// ── Read tools ───────────────────────────────────────────────────────────────
|
|
26
|
+
server.tool("salesforce_identity", "Return the identity (user, org, instance) of the supplied token. Use this to confirm the connection is authenticated.", {}, async () => {
|
|
27
|
+
try {
|
|
28
|
+
return ok(await sf.identity(conn()));
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return fail(e);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
server.tool("salesforce_query", "Run a SOQL query and return matching records.", { soql: z.string().describe("A SOQL query, e.g. SELECT Id, Name FROM Account LIMIT 10") }, async ({ soql }) => {
|
|
35
|
+
try {
|
|
36
|
+
return ok(await sf.soqlQuery(conn(), soql));
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return fail(e);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
server.tool("salesforce_search", "Run a SOSL full-text search across objects.", { sosl: z.string().describe("A SOSL search, e.g. FIND {Acme} IN ALL FIELDS RETURNING Account(Id, Name)") }, async ({ sosl }) => {
|
|
43
|
+
try {
|
|
44
|
+
return ok(await sf.soslSearch(conn(), sosl));
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return fail(e);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
server.tool("salesforce_list_objects", "List all sObjects available in the org with their key metadata.", {}, async () => {
|
|
51
|
+
try {
|
|
52
|
+
return ok(await sf.listObjects(conn()));
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return fail(e);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
server.tool("salesforce_describe_object", "Describe an sObject: its fields, types, picklist values, and references (trimmed payload).", { object_name: z.string().describe("API name of the object, e.g. Account or Custom__c") }, async ({ object_name }) => {
|
|
59
|
+
try {
|
|
60
|
+
return ok(await sf.describeObject(conn(), object_name));
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return fail(e);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.tool("salesforce_get_record", "Retrieve a single record by Id, optionally limited to specific fields.", {
|
|
67
|
+
object_name: z.string().describe("API name of the object, e.g. Account"),
|
|
68
|
+
record_id: z.string().describe("The 15- or 18-char record Id"),
|
|
69
|
+
fields: z
|
|
70
|
+
.array(z.string())
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Optional list of field API names; omit for all fields"),
|
|
73
|
+
}, async ({ object_name, record_id, fields }) => {
|
|
74
|
+
try {
|
|
75
|
+
return ok(await sf.getRecord(conn(), object_name, record_id, fields));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return fail(e);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// ── Write tools (skipped entirely in read-only mode) ──────────────────────────
|
|
82
|
+
if (!readOnly) {
|
|
83
|
+
server.tool("salesforce_create_record", "Create a new record on the given object.", {
|
|
84
|
+
object_name: z.string().describe("API name of the object, e.g. Contact"),
|
|
85
|
+
data: z
|
|
86
|
+
.record(z.any())
|
|
87
|
+
.describe("Field API name → value map for the new record"),
|
|
88
|
+
}, async ({ object_name, data }) => {
|
|
89
|
+
try {
|
|
90
|
+
return ok(await sf.createRecord(conn(), object_name, data));
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return fail(e);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
server.tool("salesforce_update_record", "Update fields on an existing record.", {
|
|
97
|
+
object_name: z.string().describe("API name of the object"),
|
|
98
|
+
record_id: z.string().describe("The Id of the record to update"),
|
|
99
|
+
data: z.record(z.any()).describe("Field API name → new value map"),
|
|
100
|
+
}, async ({ object_name, record_id, data }) => {
|
|
101
|
+
try {
|
|
102
|
+
return ok(await sf.updateRecord(conn(), object_name, record_id, data));
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
return fail(e);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
server.tool("salesforce_delete_record", "Delete a record by Id.", {
|
|
109
|
+
object_name: z.string().describe("API name of the object"),
|
|
110
|
+
record_id: z.string().describe("The Id of the record to delete"),
|
|
111
|
+
}, async ({ object_name, record_id }) => {
|
|
112
|
+
try {
|
|
113
|
+
return ok(await sf.deleteRecord(conn(), object_name, record_id));
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
return fail(e);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return server;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@imazhar101/salesforce-mcp-jsforce",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lite, single-org Salesforce MCP server built on jsforce. Bring-your-own OAuth token — no credentials stored server-side. Runs over stdio (Claude Code) or as a dedicated streamable-HTTP server.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "imazhar101",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/imazhar101/salesforce-mcp-jsforce.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/imazhar101/salesforce-mcp-jsforce#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/imazhar101/salesforce-mcp-jsforce/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"salesforce",
|
|
20
|
+
"jsforce",
|
|
21
|
+
"claude",
|
|
22
|
+
"oauth"
|
|
23
|
+
],
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"salesforce-mcp-jsforce": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"dev": "tsx src/index.ts",
|
|
39
|
+
"login": "tsx src/index.ts login",
|
|
40
|
+
"http": "tsx src/index.ts http",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
45
|
+
"jsforce": "^3.6.3",
|
|
46
|
+
"zod": "^3.23.8"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^20.14.0",
|
|
50
|
+
"tsx": "^4.16.0",
|
|
51
|
+
"typescript": "^5.5.0"
|
|
52
|
+
}
|
|
53
|
+
}
|