@mindstone/mcp-server-salesforce 0.1.3 → 0.2.1
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 +28 -3
- package/dist/client.js +2 -1
- package/dist/server.js +7 -1
- package/dist/tools/accounts.js +4 -4
- package/dist/tools/auth-tools.js +47 -12
- package/dist/tools/campaigns.d.ts +3 -0
- package/dist/tools/campaigns.js +78 -0
- package/dist/tools/cases.d.ts +3 -0
- package/dist/tools/cases.js +121 -0
- package/dist/tools/contacts.js +4 -4
- package/dist/tools/events.d.ts +3 -0
- package/dist/tools/events.js +90 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/leads.js +16 -8
- package/dist/tools/notes.d.ts +3 -0
- package/dist/tools/notes.js +111 -0
- package/dist/tools/opportunities.js +4 -4
- package/dist/tools/query.js +13 -10
- package/dist/tools/reports.d.ts +3 -0
- package/dist/tools/reports.js +39 -0
- package/dist/tools/search.d.ts +3 -0
- package/dist/tools/search.js +50 -0
- package/dist/tools/tasks.js +4 -4
- package/dist/tools/users.js +2 -2
- package/dist/types.d.ts +8 -0
- package/dist/types.js +8 -0
- package/dist/untrusted-content.d.ts +37 -0
- package/dist/untrusted-content.js +70 -0
- package/dist/utils.d.ts +34 -0
- package/dist/utils.js +130 -3
- package/package.json +1 -1
package/dist/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConnectorError } from './types.js';
|
|
2
|
+
import { wrapUntrusted } from './untrusted-content.js';
|
|
2
3
|
/**
|
|
3
4
|
* Wraps a tool handler with standard error handling.
|
|
4
5
|
*/
|
|
@@ -25,9 +26,44 @@ export function withErrorHandling(fn) {
|
|
|
25
26
|
isError: true,
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
// Unexpected errors: log the raw detail locally for diagnostics, then
|
|
30
|
+
// decide what the model may see.
|
|
31
|
+
console.error('[Salesforce MCP] Unexpected error while handling tool call:', error);
|
|
32
|
+
// jsforce API errors (HttpApiError shape: string errorCode): the message
|
|
33
|
+
// is authored by the vendor API (validation-rule text, error bodies) and
|
|
34
|
+
// stays actionable for the caller, but it is org-controlled text, so it
|
|
35
|
+
// MUST be enveloped before it reaches model-visible output (invariant #6).
|
|
36
|
+
const vendorCode = error.errorCode;
|
|
37
|
+
if (error instanceof Error && typeof vendorCode === 'string') {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: JSON.stringify({
|
|
43
|
+
ok: false,
|
|
44
|
+
error: wrapUntrusted(error.message, 'salesforce:vendor_errors'),
|
|
45
|
+
code: 'VENDOR_ERROR',
|
|
46
|
+
vendor_code: vendorCode,
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Anything else (runtime failures, network errors, …): return a
|
|
54
|
+
// sanitised message — ad-hoc error text can embed environment details
|
|
55
|
+
// such as tokens or file paths.
|
|
29
56
|
return {
|
|
30
|
-
content: [
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text',
|
|
60
|
+
text: JSON.stringify({
|
|
61
|
+
ok: false,
|
|
62
|
+
error: 'Unexpected internal error while handling the request',
|
|
63
|
+
code: 'INTERNAL_ERROR',
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
31
67
|
isError: true,
|
|
32
68
|
};
|
|
33
69
|
}
|
|
@@ -57,6 +93,19 @@ export function escapeSOQL(value) {
|
|
|
57
93
|
.replace(/%/g, '\\%')
|
|
58
94
|
.replace(/_/g, '\\_');
|
|
59
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Escape a user-supplied search term for interpolation inside a SOSL
|
|
98
|
+
* `FIND {term}` clause. SOSL gives special meaning to a different character
|
|
99
|
+
* set than SOQL — every reserved character is backslash-escaped so the term
|
|
100
|
+
* is always a single literal token and can never break out of the braces or
|
|
101
|
+
* inject operators (AND/OR groupings, wildcards, field scoping).
|
|
102
|
+
*/
|
|
103
|
+
export function escapeSOSL(term) {
|
|
104
|
+
if (!term)
|
|
105
|
+
return '';
|
|
106
|
+
// Backslash MUST be escaped first to avoid double-escaping.
|
|
107
|
+
return term.replace(/\\/g, '\\\\').replace(/([?&|!{}[\]()^~*:"+-])/g, '\\$1');
|
|
108
|
+
}
|
|
60
109
|
/**
|
|
61
110
|
* Escape a user-supplied substring for interpolation inside a SOQL
|
|
62
111
|
* `LIKE '%...%'` clause. Identical to `escapeSOQL` semantically (both
|
|
@@ -96,6 +145,24 @@ export function formatSOQLDate(dateStr, paramName) {
|
|
|
96
145
|
}
|
|
97
146
|
return match[1];
|
|
98
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Format a date or datetime string as a SOQL datetime literal (UTC).
|
|
150
|
+
* Accepts a plain date ("2026-01-09", treated as midnight UTC) or an ISO 8601
|
|
151
|
+
* datetime ("2026-01-09T14:30:00Z", offsets supported).
|
|
152
|
+
*/
|
|
153
|
+
export function formatSOQLDateTime(value, paramName) {
|
|
154
|
+
const dateOnly = value.match(/^(\d{4}-\d{2}-\d{2})$/);
|
|
155
|
+
if (dateOnly)
|
|
156
|
+
return `${dateOnly[1]}T00:00:00Z`;
|
|
157
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d{1,3})?(Z|[+-]\d{2}:?\d{2})?$/.test(value)) {
|
|
158
|
+
throw new ConnectorError(`Invalid ${paramName}: "${value}"`, 'INVALID_DATE_FORMAT', 'Use ISO 8601 (e.g., "2026-01-09T14:30:00Z") or a plain date ("2026-01-09", treated as midnight UTC)');
|
|
159
|
+
}
|
|
160
|
+
const parsed = new Date(value);
|
|
161
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
162
|
+
throw new ConnectorError(`Invalid ${paramName}: "${value}"`, 'INVALID_DATE_FORMAT', 'Use ISO 8601 (e.g., "2026-01-09T14:30:00Z") or a plain date ("2026-01-09", treated as midnight UTC)');
|
|
163
|
+
}
|
|
164
|
+
return parsed.toISOString();
|
|
165
|
+
}
|
|
99
166
|
/**
|
|
100
167
|
* Validate field names and return validated or default fields.
|
|
101
168
|
*/
|
|
@@ -125,10 +192,70 @@ export function validateAndMergeCustomFields(updateData, fields) {
|
|
|
125
192
|
}
|
|
126
193
|
}
|
|
127
194
|
export const ALLOWED_FILTER_OPERATORS = new Set(['=', '!=', '<', '>', '<=', '>=', 'LIKE']);
|
|
195
|
+
/**
|
|
196
|
+
* Vendor error payloads (e.g. `SaveResult.errors`) are org-authored text:
|
|
197
|
+
* validation-rule messages and error bodies are defined in the customer's
|
|
198
|
+
* Salesforce org and can carry arbitrary content. Envelope them before they
|
|
199
|
+
* reach model-visible output (AGENTS.md invariant #6, FOX-3490).
|
|
200
|
+
*/
|
|
201
|
+
export function formatVendorErrors(errors) {
|
|
202
|
+
const detail = typeof errors === 'string' ? errors : JSON.stringify(errors);
|
|
203
|
+
return wrapUntrusted(detail ?? 'Unknown vendor error', 'salesforce:vendor_errors');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 15- or 18-character alphanumeric Salesforce record ID.
|
|
207
|
+
*/
|
|
208
|
+
const SALESFORCE_ID_SHAPE = /^[a-zA-Z0-9]{15}(?:[a-zA-Z0-9]{3})?$/;
|
|
209
|
+
/**
|
|
210
|
+
* The structural exemption applies ONLY when a `*Id`-keyed value actually has
|
|
211
|
+
* the shape of a Salesforce ID: record IDs are copied verbatim into follow-up
|
|
212
|
+
* tool calls (update, convert, link), so enveloping them would corrupt that
|
|
213
|
+
* flow. An org-authored string under an Id-named key that is not a valid ID
|
|
214
|
+
* is external text and is enveloped like any other. `attributes` is not
|
|
215
|
+
* exempt as a whole either — its nested values (type, url) are sanitized
|
|
216
|
+
* recursively. Everything else reachable inside a Salesforce record (names,
|
|
217
|
+
* emails, descriptions, subjects, …) is authored in the external system and
|
|
218
|
+
* MUST be enveloped (AGENTS.md invariant #6, FOX-3490).
|
|
219
|
+
*/
|
|
220
|
+
function isStructuralRecordValue(key, value) {
|
|
221
|
+
if (key !== 'Id' && !key.endsWith('Id'))
|
|
222
|
+
return false;
|
|
223
|
+
return typeof value === 'string' && SALESFORCE_ID_SHAPE.test(value);
|
|
224
|
+
}
|
|
225
|
+
function wrapRecordValue(value, source) {
|
|
226
|
+
if (typeof value === 'string')
|
|
227
|
+
return wrapUntrusted(value, source);
|
|
228
|
+
if (Array.isArray(value))
|
|
229
|
+
return value.map((item) => wrapRecordValue(item, source));
|
|
230
|
+
if (value && typeof value === 'object') {
|
|
231
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
232
|
+
key,
|
|
233
|
+
isStructuralRecordValue(key, item) ? item : wrapRecordValue(item, source),
|
|
234
|
+
]));
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Envelope every external-text field in a list of Salesforce records before
|
|
240
|
+
* they are returned to the LLM. Only values under `Id`/`*Id` keys that are
|
|
241
|
+
* actually shaped like Salesforce IDs pass through raw, so downstream tool
|
|
242
|
+
* calls can use them as identifiers.
|
|
243
|
+
*/
|
|
244
|
+
export function sanitizeRecords(records, source) {
|
|
245
|
+
return records.map((record) => wrapRecordValue(record, source));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Envelope every string inside an arbitrary external-data blob (report
|
|
249
|
+
* results, metadata payloads), leaving only shape-validated Salesforce IDs
|
|
250
|
+
* raw. Use for non-record response shapes where every value is org-authored.
|
|
251
|
+
*/
|
|
252
|
+
export function sanitizeExternalData(value, source) {
|
|
253
|
+
return wrapRecordValue(value, source);
|
|
254
|
+
}
|
|
128
255
|
export function checkSaveResult(result, errorMessage) {
|
|
129
256
|
const res = Array.isArray(result) ? result[0] : result;
|
|
130
257
|
if (!res.success) {
|
|
131
|
-
throw new ConnectorError(errorMessage, 'UPDATE_ERROR',
|
|
258
|
+
throw new ConnectorError(errorMessage, 'UPDATE_ERROR', formatVendorErrors(res.errors));
|
|
132
259
|
}
|
|
133
260
|
}
|
|
134
261
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindstone/mcp-server-salesforce",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.mindstone/mcp-server-salesforce",
|
|
5
5
|
"description": "Salesforce CRM MCP server — accounts, contacts, opportunities, leads, tasks, and custom objects via Salesforce API",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|